Skip to content

Commit 4451609

Browse files
authored
Inherit parameter/field documentation from superclass (#1833)
Fixes #996
1 parent b294d63 commit 4451609

15 files changed

Lines changed: 563 additions & 88 deletions

NAMESPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ S3method(format,rd_section_formals)
3838
S3method(format,rd_section_format)
3939
S3method(format,rd_section_inherit)
4040
S3method(format,rd_section_inherit_dot_params)
41+
S3method(format,rd_section_inherit_params_args)
4142
S3method(format,rd_section_inherit_section)
4243
S3method(format,rd_section_keyword)
4344
S3method(format,rd_section_minidesc)
@@ -46,6 +47,7 @@ S3method(format,rd_section_note)
4647
S3method(format,rd_section_package)
4748
S3method(format,rd_section_param)
4849
S3method(format,rd_section_prop)
50+
S3method(format,rd_section_r6_class)
4951
S3method(format,rd_section_rawRd)
5052
S3method(format,rd_section_rcmethods)
5153
S3method(format,rd_section_reexport)
@@ -62,6 +64,7 @@ S3method(merge,rd_section)
6264
S3method(merge,rd_section_author)
6365
S3method(merge,rd_section_inherit)
6466
S3method(merge,rd_section_inherit_dot_params)
67+
S3method(merge,rd_section_inherit_params_args)
6568
S3method(merge,rd_section_inherit_section)
6669
S3method(merge,rd_section_minidesc)
6770
S3method(merge,rd_section_param)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* You can now use `@noRd` before an R6 method to suppress its documentation, and `@field name NULL` to suppress documentation for a field or active binding (#1067).
2525
* Inherited method links now only link to parent classes that have documentation, avoiding broken links when parent classes are undocumented (#963, #1155).
2626
* `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).
27+
* R6 subclass methods now automatically inherit parameter documentation from the same-named superclass method when parameters are not documented locally. Fields and active bindings also inherit documentation from superclasses (#996).
2728
* 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).
2829
* 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).
2930
* Reexported functions now display with `()` appended (e.g., `fun()` instead of `fun`) on the reexports page, except for infix operators like `%>%` (#1222). They also use modern (>= 4.1.0) linking style.

R/rd-r6-field.R

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,6 @@ r6_extract_field_tags <- function(block, r6data, type = c("field", "active")) {
1010
tags <- keep(block$tags, \(t) tag_is(t, "field") && !tag_has_name(t, other))
1111
docd <- unlist(lapply(tags, tag_names))
1212

13-
miss <- setdiff(expected, docd)
14-
if (length(miss) > 0) {
15-
warn_roxy_block(block, "Undocumented R6 {label}{?s}: {miss}")
16-
}
17-
1813
dup <- unique(docd[duplicated(docd)])
1914
if (length(dup) > 0) {
2015
warn_roxy_block(block, "R6 {label}{?s} documented multiple times: {dup}")
@@ -37,14 +32,29 @@ r6_extract_field_tags <- function(block, r6data, type = c("field", "active")) {
3732
)
3833
})
3934

40-
rd_r6_fields(items, type = type)
35+
rd_r6_fields(items, type = type, expected = expected)
36+
}
37+
38+
r6_field_names <- function(rd_fields) {
39+
if (length(rd_fields) == 0) {
40+
return(character())
41+
}
42+
labels <- map_chr(rd_fields, \(x) x$name)
43+
trimws(unlist(strsplit(labels, ",")))
4144
}
4245

4346
# Rd ---------------------------------------------------------------------------
4447

45-
rd_r6_fields <- function(fields = list(), type = c("field", "active")) {
48+
rd_r6_fields <- function(
49+
fields = list(),
50+
type = c("field", "active"),
51+
expected = character()
52+
) {
4653
type <- match.arg(type)
47-
structure(list(fields = fields, type = type), class = "rd_r6_fields")
54+
structure(
55+
list(fields = fields, type = type, expected = expected),
56+
class = "rd_r6_fields"
57+
)
4858
}
4959

5060
rd_r6_field <- function(name, description) {

R/rd-r6-methods-self.R

Lines changed: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,17 @@ r6_method_from_row <- function(method, block) {
143143
)
144144
}
145145

146+
# Resolve params within a class: method @param -> class-level @param -> @field.
147+
# Cross-class inheritance from parent classes is handled later by
148+
# r6_resolve_method_params().
146149
r6_resolve_params <- function(method, block) {
147150
tags <- method$tags[[1]]
148-
par <- keep(tags, \(t) t$tag == "param")
149-
mnames <- unlist(lapply(par, tag_names))
150-
dup <- unique(mnames[duplicated(mnames)])
151+
par_tags <- keep(tags, \(t) t$tag == "param")
152+
153+
params <- r6_tags_to_params(par_tags)
154+
pnames <- r6_param_names(params)
155+
156+
dup <- unique(pnames[duplicated(pnames)])
151157
for (m in dup) {
152158
warn_roxy_block(
153159
block,
@@ -163,61 +169,52 @@ r6_resolve_params <- function(method, block) {
163169
return(list())
164170
}
165171

166-
# Add missing from class-level @param
167-
miss <- setdiff(fnames, mnames)
168-
is_in_cls <- map_lgl(
169-
block$tags,
170-
function(t) {
171-
!is.na(t$line) &&
172-
t$line < block$line &&
173-
tag_is(t, "param") &&
174-
tag_has_name(t, miss)
175-
}
176-
)
177-
par <- c(par, block$tags[is_in_cls])
172+
# 1. Add class-level @param
173+
miss <- setdiff(fnames, pnames)
174+
cls_tags <- keep(block$tags, function(t) {
175+
!is.na(t$line) &&
176+
t$line < block$line &&
177+
tag_is(t, "param") &&
178+
tag_has_name(t, miss)
179+
})
180+
params <- c(params, r6_tags_to_params(cls_tags))
178181

179-
# For initialize(), inherit from @field tags for any still-missing params
182+
# 2. For initialize() only, inherit from @field
180183
if (method$name == "initialize") {
181-
mnames <- unlist(lapply(par, tag_names))
182-
miss <- setdiff(fnames, mnames)
184+
miss <- setdiff(fnames, r6_param_names(params))
183185

184186
if (length(miss) > 0) {
185187
field_tags <- keep(block$tags, function(t) {
186188
tag_is(t, "field") && tag_has_name(t, miss)
187189
})
188-
field_as_param <- lapply(field_tags, function(t) {
189-
val <- list(name = t$val$name, description = t$val$description)
190-
roxy_generated_tag(block, "param", val)
191-
})
192-
par <- c(par, field_as_param)
190+
params <- c(params, r6_tags_to_params(field_tags))
193191
}
194192
}
195193

196-
# Check if anything is still missing
197-
mnames <- unlist(lapply(par, tag_names))
198-
miss <- setdiff(fnames, mnames)
199-
for (m in miss) {
200-
warn_roxy_block(
201-
block,
202-
c(
203-
"Must use one @param for each argument",
204-
x = "${method$name}({m}) is not documented"
205-
)
206-
)
207-
}
208-
209194
# Order them according to formals
210-
firstnames <- map_chr(par, \(t) tag_names(t)[[1]])
211-
par <- par[order(match(firstnames, fnames))]
195+
firstnames <- map_chr(
196+
strsplit(map_chr(params, \(x) x$name), ","),
197+
\(x) trimws(x[[1]])
198+
)
199+
params[order(match(firstnames, fnames))]
200+
}
212201

213-
lapply(par, function(t) {
202+
r6_tags_to_params <- function(tags) {
203+
lapply(tags, function(t) {
214204
list(
215205
name = gsub(",", ", ", t$val$name),
216206
description = t$val$description
217207
)
218208
})
219209
}
220210

211+
r6_param_names <- function(params) {
212+
if (length(params) == 0) {
213+
return(character())
214+
}
215+
trimws(unlist(strsplit(map_chr(params, \(x) x$name), ",")))
216+
}
217+
221218
r6_method_name <- function(class, method) {
222219
paste0(class, "$", ifelse(method == "initialize", "new", method))
223220
}

0 commit comments

Comments
 (0)