Skip to content

Commit 988a96b

Browse files
authored
Improve support for comma separated fields (#1827)
Unify fields and active bindings, and add some helpers to make it easier to consistently work with comma separated tag names. Fixes #1600
1 parent 7a1dd39 commit 988a96b

10 files changed

Lines changed: 108 additions & 143 deletions

File tree

NAMESPACE

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ S3method(escape,"NULL")
1515
S3method(escape,character)
1616
S3method(escape,rd)
1717
S3method(format,object)
18-
S3method(format,rd_r6_bindings)
1918
S3method(format,rd_r6_class)
2019
S3method(format,rd_r6_field)
2120
S3method(format,rd_r6_fields)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* R6 method usage now shows `ClassName$new(args)` for constructors and `obj$method(args)` for other methods, making it clearer how each method is actually called (#1026).
88
* `@returns` now works as a method-level tag in R6 classes, just like `@return` (#1148).
99
* The "Super classes" section now omits the `pkg::` prefix for parent classes from the same package, making the inheritance chain easier to read (#1567).
10+
* `@field` with comma-separated names (e.g., `@field var_1,var_2 description`) no longer produces spurious warnings about undocumented active bindings or unknown fields (#1600).
1011
* R6 classes with only active bindings and `cloneable = FALSE` no longer error during documentation (#1610).
1112
* `@example` (singular, with a file path) now works correctly in R6 class documentation (#1158).
1213
* Inherited method links now only link to parent classes that have documentation, avoiding broken links when parent classes are undocumented (#963, #1155).

R/rd-r6-class.R

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ rd_r6_class <- function(
22
class,
33
alias = class,
44
superclasses = rd_r6_super(class),
5-
fields = rd_r6_fields(),
6-
active_bindings = rd_r6_bindings(),
5+
fields = rd_r6_fields(type = "field"),
6+
active_bindings = rd_r6_fields(type = "active"),
77
methods = rd_r6_methods(alias)
88
) {
99
structure(
@@ -38,8 +38,8 @@ r6_class_from_block <- function(block, env) {
3838
class = class,
3939
alias = alias,
4040
superclasses = r6_extract_superclasses(r6data, env, class),
41-
fields = r6_extract_fields(block, r6data),
42-
active_bindings = r6_extract_active_bindings(block, r6data),
41+
fields = r6_extract_field_tags(block, r6data, type = "field"),
42+
active_bindings = r6_extract_field_tags(block, r6data, type = "active"),
4343
methods = r6_extract_methods(r6data, alias, block)
4444
)
4545
}

R/rd-r6-field.R

Lines changed: 28 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,47 @@
1-
r6_extract_fields <- function(block, r6data) {
2-
self <- r6data$self
3-
fields <- self$name[self$type == "field"]
4-
active <- self$name[self$type == "active"]
1+
r6_extract_field_tags <- function(block, r6data, type = c("field", "active")) {
2+
type <- match.arg(type)
3+
other_type <- if (type == "field") "active" else "field"
4+
label <- if (type == "field") "field" else "active binding"
55

6-
tags <- keep(
7-
block$tags,
8-
function(t) t$tag == "field" && !t$val$name %in% active
9-
)
6+
self <- r6data$self
7+
expected <- self$name[self$type == type]
8+
other <- self$name[self$type == other_type]
109

11-
labels <- gsub(",", ", ", map_chr(tags, \(x) x[["val"]][["name"]]))
12-
docd <- str_trim(unlist(strsplit(labels, ",")))
10+
tags <- keep(block$tags, \(t) tag_is(t, "field") && !tag_has_name(t, other))
11+
docd <- unlist(lapply(tags, tag_names))
1312

14-
miss <- setdiff(fields, docd)
13+
miss <- setdiff(expected, docd)
1514
if (length(miss) > 0) {
16-
warn_roxy_block(block, "Undocumented R6 field{?s}: {miss}")
15+
warn_roxy_block(block, "Undocumented R6 {label}{?s}: {miss}")
1716
}
1817

1918
dup <- unique(docd[duplicated(docd)])
2019
if (length(dup) > 0) {
21-
warn_roxy_block(block, "R6 field{?s} documented multiple times: {dup}")
20+
warn_roxy_block(block, "R6 {label}{?s} documented multiple times: {dup}")
2221
}
2322

24-
xtra <- setdiff(docd, fields)
25-
if (length(xtra) > 0) {
26-
warn_roxy_block(block, "Unknown R6 field{?s}: {xtra}")
23+
if (type == "field") {
24+
xtra <- setdiff(docd, expected)
25+
if (length(xtra) > 0) {
26+
warn_roxy_block(block, "Unknown R6 {label}{?s}: {xtra}")
27+
}
2728
}
2829

29-
rd_r6_fields(lapply(tags, function(t) {
30+
items <- lapply(tags, function(t) {
3031
rd_r6_field(
3132
name = gsub(",", ", ", t$val$name),
3233
description = t$val$description
3334
)
34-
}))
35-
}
36-
37-
r6_extract_active_bindings <- function(block, r6data) {
38-
self <- r6data$self
39-
fields <- self$name[self$type == "field"]
40-
active <- self$name[self$type == "active"]
41-
42-
tags <- keep(
43-
block$tags,
44-
function(t) t$tag == "field" && !t$val$name %in% fields
45-
)
46-
47-
labels <- gsub(",", ", ", map_chr(tags, \(x) x[["val"]][["name"]]))
48-
docd <- str_trim(unlist(strsplit(labels, ",")))
35+
})
4936

50-
miss <- setdiff(active, docd)
51-
if (length(miss) > 0) {
52-
warn_roxy_block(block, "Undocumented R6 active binding{?s}: {miss}")
53-
}
54-
55-
dup <- unique(docd[duplicated(docd)])
56-
if (length(dup) > 0) {
57-
warn_roxy_block(
58-
block,
59-
"R6 active binding{?s} documented multiple times: {dup}"
60-
)
61-
}
62-
63-
rd_r6_bindings(lapply(tags, function(t) {
64-
rd_r6_field(
65-
name = gsub(",", ", ", t$val$name),
66-
description = t$val$description
67-
)
68-
}))
37+
rd_r6_fields(items, type = type)
6938
}
7039

7140
# Rd ---------------------------------------------------------------------------
7241

73-
rd_r6_fields <- function(fields = list()) {
74-
structure(list(fields = fields), class = "rd_r6_fields")
75-
}
76-
77-
rd_r6_bindings <- function(bindings = list()) {
78-
structure(list(fields = bindings), class = "rd_r6_bindings")
42+
rd_r6_fields <- function(fields = list(), type = c("field", "active")) {
43+
type <- match.arg(type)
44+
structure(list(fields = fields, type = type), class = "rd_r6_fields")
7945
}
8046

8147
rd_r6_field <- function(name, description) {
@@ -92,12 +58,11 @@ format.rd_r6_field <- function(x, ...) {
9258

9359
#' @export
9460
format.rd_r6_fields <- function(x, ...) {
95-
format_r6_field_section(x$fields, "Public fields", "r6-fields")
96-
}
97-
98-
#' @export
99-
format.rd_r6_bindings <- function(x, ...) {
100-
format_r6_field_section(x$fields, "Active bindings", "r6-active-bindings")
61+
if (x$type == "field") {
62+
format_r6_field_section(x$fields, "Public fields", "r6-fields")
63+
} else {
64+
format_r6_field_section(x$fields, "Active bindings", "r6-active-bindings")
65+
}
10166
}
10267

10368
format_r6_field_section <- function(fields, title, css_class) {

R/rd-r6-methods-self.R

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,7 @@ r6_method_from_row <- function(method, block) {
146146
r6_resolve_params <- function(method, block) {
147147
tags <- method$tags[[1]]
148148
par <- keep(tags, \(t) t$tag == "param")
149-
nms <- gsub(",", ", ", map_chr(par, \(x) x[["val"]][["name"]]))
150-
151-
mnames <- str_trim(unlist(strsplit(nms, ",")))
149+
mnames <- unlist(lapply(par, tag_names))
152150
dup <- unique(mnames[duplicated(mnames)])
153151
for (m in dup) {
154152
warn_roxy_block(
@@ -172,21 +170,20 @@ r6_resolve_params <- function(method, block) {
172170
function(t) {
173171
!is.na(t$line) &&
174172
t$line < block$line &&
175-
t$tag == "param" &&
176-
t$val$name %in% miss
173+
tag_is(t, "param") &&
174+
tag_has_name(t, miss)
177175
}
178176
)
179177
par <- c(par, block$tags[is_in_cls])
180178

181179
# For initialize(), inherit from @field tags for any still-missing params
182180
if (method$name == "initialize") {
183-
nms <- gsub(",", ", ", map_chr(par, \(x) x[["val"]][["name"]]))
184-
mnames <- str_trim(unlist(strsplit(nms, ",")))
181+
mnames <- unlist(lapply(par, tag_names))
185182
miss <- setdiff(fnames, mnames)
186183

187184
if (length(miss) > 0) {
188185
field_tags <- keep(block$tags, function(t) {
189-
t$tag == "field" && t$val$name %in% miss
186+
tag_is(t, "field") && tag_has_name(t, miss)
190187
})
191188
field_as_param <- lapply(field_tags, function(t) {
192189
val <- list(name = t$val$name, description = t$val$description)
@@ -197,8 +194,7 @@ r6_resolve_params <- function(method, block) {
197194
}
198195

199196
# Check if anything is still missing
200-
nms <- gsub(",", ", ", map_chr(par, \(x) x[["val"]][["name"]]))
201-
mnames <- str_trim(unlist(strsplit(nms, ",")))
197+
mnames <- unlist(lapply(par, tag_names))
202198
miss <- setdiff(fnames, mnames)
203199
for (m in miss) {
204200
warn_roxy_block(
@@ -211,8 +207,7 @@ r6_resolve_params <- function(method, block) {
211207
}
212208

213209
# Order them according to formals
214-
firstnames <- str_trim(
215-
map_chr(strsplit(map_chr(par, \(x) x[["val"]][["name"]]), ","), \(x) x[[1]])
210+
firstnames <- map_chr(par, \(t) tag_names(t)[[1]]
216211
)
217212
par <- par[order(match(firstnames, fnames))]
218213

R/rd-r6.R

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,15 @@ r6_tag_type <- function(tag, block) {
6565
"class"
6666
}
6767
}
68+
69+
tag_is <- function(tag, name) {
70+
tag$tag == name
71+
}
72+
73+
tag_names <- function(tag) {
74+
str_trim(strsplit(tag$val$name, ",")[[1]])
75+
}
76+
77+
tag_has_name <- function(tag, names) {
78+
any(tag_names(tag) %in% names)
79+
}

tests/testthat/_snaps/rd-r6-field.md

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,14 @@
2020
x <text>:4: Undocumented R6 field: x.
2121
x <text>:4: Unknown R6 field: nosuch.
2222

23-
# warns about undocumented active bindings
24-
25-
Code
26-
docs <- r6_doc(text)
27-
Message
28-
x <text>:3: Undocumented R6 active binding: undocumented.
29-
30-
# warns about active bindings documented multiple times
31-
32-
Code
33-
docs <- r6_doc(text)
34-
Message
35-
x <text>:5: R6 active binding documented multiple times: b.
36-
3723
# format.rd_r6_field produces \item markup
3824

3925
Code
4026
cat(format(rd_r6_field("x", "A number.")))
4127
Output
4228
\item{\code{x}}{A number.}
4329

44-
# format.rd_r6_fields produces Public fields section
30+
# format.rd_r6_fields produces fields & bindings sections
4531

4632
Code
4733
cat(format(fields), sep = "\n")
@@ -56,7 +42,7 @@
5642
\if{html}{\out{</div>}}
5743
}
5844

59-
# format.rd_r6_bindings produces Active bindings section
45+
---
6046

6147
Code
6248
cat(format(bindings), sep = "\n")

tests/testthat/test-rd-r6-class.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ test_that("can construct empty class", {
99
expect_s3_class(docs, "rd_r6_class")
1010
expect_equal(docs$methods, rd_r6_methods("C"))
1111
expect_equal(docs$fields, rd_r6_fields())
12-
expect_equal(docs$active_bindings, rd_r6_bindings())
12+
expect_equal(docs$active_bindings, rd_r6_fields(type = "active"))
1313
})
1414

1515
test_that("class description is not duplicated", {
@@ -92,7 +92,7 @@ test_that("format.rd_r6_class with fields", {
9292
test_that("format.rd_r6_class with active bindings", {
9393
docs <- rd_r6_class(
9494
class = "Foo",
95-
active_bindings = rd_r6_bindings(list(rd_r6_field("val", "A value.")))
95+
active_bindings = rd_r6_fields(list(rd_r6_field("val", "A value.")), type = "active")
9696
)
9797
expect_snapshot(cat(format(docs), sep = "\n"))
9898
})

0 commit comments

Comments
 (0)