diff --git a/NEWS.md b/NEWS.md index 6a335060..6909d6dd 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). * The "Super classes" section now omits the `pkg::` prefix for parent classes from the same package, making the inheritance chain easier to read (#1567). * R6 classes with only active bindings and `cloneable = FALSE` no longer error during documentation (#1610). * `@example` (singular, with a file path) now works correctly in R6 class documentation (#1158). 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 274b6cf4..89e45e70 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 548da3cb..9cc20398 100644 --- a/R/rd-r6.R +++ b/R/rd-r6.R @@ -3,7 +3,7 @@ topic_add_r6_methods <- function(rd, block, env, base_path) { # 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, base_path) { } } -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/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}{ 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 0fcf7de8..64586579 100644 --- a/tests/testthat/_snaps/rd-r6.md +++ b/tests/testthat/_snaps/rd-r6.md @@ -8,23 +8,14 @@ \name{A} \alias{A} \title{Class A} - \value{ - A value. - } \description{ - A method 1. - - Method 2 description. - - Method 3. + Class A description. } \details{ - Method 2 details. + Class A details } \examples{ a <- A$new() - ## Example for meth1 - ## Example for meth2 ## ------------------------------------------------ ## Method `A$meth1` @@ -185,9 +176,10 @@ \alias{B} \title{Class B} \description{ - B method 1. - - B method 4. + Class B Description. + } + \details{ + Class B details. } \section{Super class}{ \code{A} -> \code{B} @@ -291,9 +283,10 @@ \alias{C} \title{Class C} \description{ - C method 2. - - C method 5. + Class C Description. + } + \details{ + Class C details. } \section{Super classes}{ \code{A} -> \code{B} -> \code{C} @@ -366,4 +359,3 @@ } } - 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