From bd3151fe10d5de096e72b450e3b04b5bf2f28e01 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Tue, 17 Mar 2026 11:43:09 -0500 Subject: [PATCH 1/2] Support method-level `@returns` Includes some refactoring to make it clear that there are three types of R6 tags: tags that belong to the class, tags that belong to a method, and tags that are handled elsewhere. Fixes #1148 --- NEWS.md | 1 + R/rd-r6-methods-self.R | 4 +-- R/rd-r6-methods.R | 6 +--- R/rd-r6.R | 25 ++++++++++++----- tests/testthat/_snaps/rd-r6-methods-self.md | 7 +++++ tests/testthat/_snaps/rd-r6.md | 31 ++++++++------------- tests/testthat/test-rd-r6-methods-self.R | 29 +++++++++++++++++++ 7 files changed, 69 insertions(+), 34 deletions(-) diff --git a/NEWS.md b/NEWS.md index dda68fbb..1f134332 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,7 @@ * `@examplesIf` now warns when there is no example code after the condition (#1695). * `tag_words_line()` is deprecated in favour of `tag_words()`, which now checks for single-line content by default. Use `tag_words(x, multiline = TRUE)` or `tag_value(x, multiline = TRUE)` if your tag legitimately spans multiple lines. * R6 improvements: + * `@returns` now works as a method-level tag in R6 classes, just like `@return` (#1148). * `initialize()` method parameters now automatically inherit documentation from `@field` tags with the same name, reducing the need to duplicate descriptions. Explicit `@param` tags still take precedence (#1004). * New `needs_roxygenize()` provides a lightweight check that man pages are up-to-date by comparing modification times of `.Rd` files with their source files (#1411). * All generated links now use the same code path. This will lead to some minor differences when you re-document, but overall the links will now be more consistent (#1792). diff --git a/R/rd-r6-methods-self.R b/R/rd-r6-methods-self.R index fde2f1ab..e654c02a 100644 --- a/R/rd-r6-methods-self.R +++ b/R/rd-r6-methods-self.R @@ -119,9 +119,9 @@ r6_method_from_row <- function(method, alias, block) { params <- r6_resolve_params(method, block) - ret_tags <- keep(tags, \(t) t$tag == "return") + ret_tags <- keep(tags, \(t) t$tag %in% c("return", "returns")) if (length(ret_tags) > 1) { - warn_roxy_block(block, "Must use one @return per R6 method") + warn_roxy_block(block, "Must use one @return(s) per R6 method") } ret <- if (length(ret_tags) > 0) ret_tags[[1]]$val else NULL diff --git a/R/rd-r6-methods.R b/R/rd-r6-methods.R index a8622650..25811d61 100644 --- a/R/rd-r6-methods.R +++ b/R/rd-r6-methods.R @@ -43,13 +43,9 @@ r6_extract_methods <- function(r6data, alias, block) { methods_df$tags <- replicate(nrow(methods_df), list(), simplify = FALSE) # Associate inline tags with methods - r6_tags <- c("description", "details", "param", "return", "examples") for (i in seq_along(block$tags)) { tag <- block$tags[[i]] - if (is.na(tag$line) || tag$line < block$line) { - next - } - if (!tag$tag %in% r6_tags) { + if (r6_tag_type(tag, block) != "method") { next } meth <- find_method_for_tag(methods_df, tag) diff --git a/R/rd-r6.R b/R/rd-r6.R index 52d05054..a091bfa6 100644 --- a/R/rd-r6.R +++ b/R/rd-r6.R @@ -3,7 +3,7 @@ topic_add_r6_methods <- function(rd, block, env) { # Add class-level tags for (tag in block$tags) { - if (is_class_tag(tag, block)) { + if (r6_tag_type(tag, block) == "class") { rd$add(roxy_tag_rd(tag, env = env, base_path = base_path)) } } @@ -20,12 +20,23 @@ topic_add_r6_methods <- function(rd, block, env) { } } -is_class_tag <- function(tag, block) { - if (tag$tag %in% c("param", "field")) { - FALSE - } else if (tag$tag %in% c("description", "details", "return", "examples")) { - !is.na(tag$line) && tag$line >= block$line +# Classify an R6 block tag: +# - "class": top-level Rd (e.g. @title, @description before class body) +# - "method": inline tag associated with a method +# - "other": @field/@param tags consumed by field/param extraction +r6_tag_type <- function(tag, block) { + inline <- !is.na(tag$line) && tag$line >= block$line + method_tags <- c( + "description", "details", "param", "return", "returns", "examples" + ) + + if (tag$tag == "field") { + "other" + } else if (tag$tag %in% method_tags && inline) { + "method" + } else if (tag$tag == "param") { + "other" } else { - TRUE + "class" } } diff --git a/tests/testthat/_snaps/rd-r6-methods-self.md b/tests/testthat/_snaps/rd-r6-methods-self.md index 029aca46..9da54ab4 100644 --- a/tests/testthat/_snaps/rd-r6-methods-self.md +++ b/tests/testthat/_snaps/rd-r6-methods-self.md @@ -1,3 +1,10 @@ +# warns about multiple @return(s) tags + + Code + docs <- r6_doc(text) + Message + x :3: Must use one @return(s) per R6 method. + # warns about undocumented params Code diff --git a/tests/testthat/_snaps/rd-r6.md b/tests/testthat/_snaps/rd-r6.md index 1e217df8..d5ebd1b4 100644 --- a/tests/testthat/_snaps/rd-r6.md +++ b/tests/testthat/_snaps/rd-r6.md @@ -8,7 +8,7 @@ x $meth3(duplicate) is documented multiple times x classes.R:12: Must use one @param for each argument. x $meth3(missing) is not documented - x classes.R:12: Must use one @return per R6 method. + x classes.R:12: Must use one @return(s) per R6 method. x classes.R:91: Undocumented R6 fields: field2 and undocumented_field. x classes.R:91: R6 field documented multiple times: duplicatefield. x classes.R:91: Unknown R6 field: nosuchfield. @@ -30,24 +30,13 @@ \name{A} \alias{A} \title{Class A} - \value{ - twice. - - really? - } \description{ - A method 1. - - Method 2 description. + Class A description. } \details{ - Method 2 details. - - Method 3 details. + Class A details } \examples{ - ## Example for meth1 - ## Example for meth2 ## ------------------------------------------------ ## Method `A$meth1` @@ -212,9 +201,10 @@ \alias{B} \title{Class B} \description{ - B method 1. - - A method 4. + Class B Description. + } + \details{ + Class B details. } \section{Super class}{ \code{testR6::A} -> \code{B} @@ -318,9 +308,10 @@ \alias{C} \title{Class C} \description{ - C method 2. - - C method 5. + Class C Description. + } + \details{ + Classs C details. } \section{Super classes}{ \code{testR6::A} -> \code{testR6::B} -> \code{C} diff --git a/tests/testthat/test-rd-r6-methods-self.R b/tests/testthat/test-rd-r6-methods-self.R index 359059aa..f58df339 100644 --- a/tests/testthat/test-rd-r6-methods-self.R +++ b/tests/testthat/test-rd-r6-methods-self.R @@ -24,6 +24,35 @@ test_that("r6_method_from_row extracts all components", { expect_equal(method$examples, "c$greet('world')") }) +test_that("@returns works as method-level tag (#1148)", { + text <- " + #' Class + C <- R6::R6Class('C', cloneable = FALSE, + public = list( + #' @description Run. + #' @returns The result. + run = function() 1 + ) + )" + docs <- r6_doc(text) + expect_equal(docs$methods$self[[1]]$return, "The result.") +}) + +test_that("warns about multiple @return(s) tags", { + text <- " + #' Class + C <- R6::R6Class('C', cloneable = FALSE, + public = list( + #' @description Run. + #' @return First. + #' @returns Second. + run = function() 1 + ) + )" + expect_snapshot(docs <- r6_doc(text)) + expect_equal(docs$methods$self[[1]]$return, "First.") +}) + test_that("warns about undocumented params", { text <- " #' Class From 939dea378708def951fb7fa621ee73946ae01637 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Wed, 18 Mar 2026 07:50:24 -0500 Subject: [PATCH 2/2] Re-document --- man/RoxyTopic.Rd | 49 +----------------------------------------------- 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/man/RoxyTopic.Rd b/man/RoxyTopic.Rd index f45c28cb..8b86a84b 100644 --- a/man/RoxyTopic.Rd +++ b/man/RoxyTopic.Rd @@ -3,55 +3,8 @@ \name{RoxyTopic} \alias{RoxyTopic} \title{A \code{RoxyTopic} is an ordered collection of unique rd_sections} -\value{ -Character string. - -Logical flag, \code{TRUE} for valid \code{.Rd} files - -Logical flag. - -The \link{rd_section} object representing the section, or \code{NULL} -if the topic has no such section. - -Value. - -Character vector, one element per line. - -Character scalar. - -A character vector of topic names. - -A character vector of topic names. -} \description{ -Format the \code{.Rd} file. It considers the sections in -particular order, even though Rd tools will reorder them again. - -Check if an \code{.Rd} file is valid - -Check if an \code{.Rd} file has a certain section. - -Query a section. - -Query the value of a section. This is the value of -the \link{rd_section} object. - -Get the Rd code of a section. - -Get the value of the \code{name} section. This is the name -of the Rd topic. - -Query the topics this topic inherits \code{type} from. - -Query the topics this topic inherits sections from. - -Add one or more sections to the topic. - -Add a section. -} -\details{ -Ensures that each type of name (as given by its name), only appears -once in \code{self$sections}. This method if for internal use only. +A \code{RoxyTopic} object corresponds to a generated \code{.Rd} file. } \keyword{internal} \section{Public fields}{