Skip to content

Commit 6cad87d

Browse files
teunbrandYunuuuu
andauthored
Watch potential partial matches in Geom/Stat parameters (#6428)
* use snapshot test for partially matching arguments * protect against `arrow`/`arrow.fill` partial match * protect against $n partial matches * protect against $method partial match * protect against $fun partial matches * fix other partial matching --------- Co-authored-by: Yunuuuu <yunyunpp96@outlook.com>
1 parent 0d9c298 commit 6cad87d

14 files changed

+135
-36
lines changed

R/facet-.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,7 +1436,7 @@ map_facet_data <- function(data, layout, params) {
14361436
return(vec_cbind(data %|W|% NULL, PANEL = integer(0)))
14371437
}
14381438

1439-
vars <- params$facet %||% c(params$rows, params$cols)
1439+
vars <- params$facets %||% c(params$rows, params$cols)
14401440

14411441
if (length(vars) == 0) {
14421442
data$PANEL <- layout$PANEL
@@ -1455,7 +1455,7 @@ map_facet_data <- function(data, layout, params) {
14551455
# Compute faceting values
14561456
facet_vals <- eval_facets(vars, data, params$.possible_columns)
14571457

1458-
include_margins <- !isFALSE(params$margin %||% FALSE) &&
1458+
include_margins <- !isFALSE(params$margins %||% FALSE) &&
14591459
nrow(facet_vals) == nrow(data) && grid_layout
14601460
if (include_margins) {
14611461
# Margins are computed on evaluated faceting values (#1864).

R/geom-smooth.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ geom_smooth <- function(mapping = NULL, data = NULL,
164164
...
165165
)
166166
if (identical(stat, "smooth")) {
167-
params$method <- method
167+
params[["method"]] <- method
168168
params$formula <- formula
169169
}
170170

R/labels.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ get_alt_text.gtable <- function(p, ...) {
351351
#'
352352
generate_alt_text <- function(p) {
353353
# Combine titles
354-
if (!is.null(p$label$title %||% p$labels$subtitle)) {
354+
if (!is.null(p$labels$title %||% p$labels$subtitle)) {
355355
title <- sub("\\.?$", "", c(p$labels$title, p$labels$subtitle))
356356
if (length(title) == 2) {
357357
title <- paste0(title[1], ": ", title[2])

R/legend-draw.R

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,11 @@ draw_key_path <- function(data, params, size) {
207207
lty = data$linetype %||% 1,
208208
lineend = params$lineend %||% "butt"
209209
),
210-
arrow = params$arrow
210+
arrow = params[["arrow"]]
211211
)
212-
if (!is.null(params$arrow)) {
213-
angle <- deg2rad(params$arrow$angle)
214-
length <- convertUnit(params$arrow$length, "cm", valueOnly = TRUE)
212+
if (!is.null(params[["arrow"]])) {
213+
angle <- deg2rad(params[["arrow"]]$angle)
214+
length <- convertUnit(params[["arrow"]]$length, "cm", valueOnly = TRUE)
215215
attr(grob, "width") <- cos(angle) * length * 1.25
216216
attr(grob, "height") <- sin(angle) * length * 2
217217
}
@@ -228,11 +228,11 @@ draw_key_vpath <- function(data, params, size) {
228228
lty = data$linetype %||% 1,
229229
lineend = params$lineend %||% "butt"
230230
),
231-
arrow = params$arrow
231+
arrow = params[["arrow"]]
232232
)
233-
if (!is.null(params$arrow)) {
234-
angle <- deg2rad(params$arrow$angle)
235-
length <- convertUnit(params$arrow$length, "cm", valueOnly = TRUE)
233+
if (!is.null(params[["arrow"]])) {
234+
angle <- deg2rad(params[["arrow"]]$angle)
235+
length <- convertUnit(params[["arrow"]]$length, "cm", valueOnly = TRUE)
236236
attr(grob, "width") <- sin(angle) * length * 2
237237
attr(grob, "height") <- cos(angle) * length * 1.25
238238
}

R/stat-density-2d.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ StatDensity2d <- ggproto(
8080
df$group <- data$group[1]
8181
df$ndensity <- df$density / max(df$density, na.rm = TRUE)
8282
df$count <- nx * df$density
83-
df$n <- nx
83+
df[["n"]] <- nx
8484
df$level <- 1
8585
df$piece <- 1
8686
df

R/stat-manual.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ StatManual <- ggproto(
66
"StatManual", Stat,
77

88
setup_params = function(data, params) {
9-
params$fun <- allow_lambda(params$fun)
10-
check_function(params$fun, arg = "fun")
9+
params[["fun"]] <- allow_lambda(params[["fun"]])
10+
check_function(params[["fun"]], arg = "fun")
1111
params
1212
},
1313

R/stat-smooth.R

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ StatSmooth <- ggproto(
77
setup_params = function(data, params) {
88
params$flipped_aes <- has_flipped_aes(data, params, ambiguous = TRUE)
99
msg <- character()
10-
method <- params$method
10+
method <- params[["method"]]
1111
if (is.null(method) || identical(method, "auto")) {
1212
# Use loess for small datasets, gam with a cubic regression basis for
1313
# larger. Based on size of the _largest_ group to avoid bad memory
@@ -56,14 +56,14 @@ StatSmooth <- ggproto(
5656
}
5757
# If gam and gam's method is not specified by the user then use REML
5858
if (identical(method, gam_method())) {
59-
params$method.args$method <- params$method.args$method %||% "REML"
59+
params$method.args[["method"]] <- params$method.args[["method"]] %||% "REML"
6060
}
6161

6262
if (length(msg) > 0) {
6363
cli::cli_inform("{.fn geom_smooth} using {msg}")
6464
}
6565

66-
params$method <- method
66+
params[["method"]] <- method
6767
params
6868
},
6969

R/stat-summary-bin.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ StatSummaryBin <- ggproto("StatSummaryBin", Stat,
6868

6969
setup_params = function(data, params) {
7070
params$flipped_aes <- has_flipped_aes(data, params)
71-
params$fun <- make_summary_fun(
72-
params$fun.data, params$fun,
71+
params[["fun"]] <- make_summary_fun(
72+
params$fun.data, params[["fun"]],
7373
params$fun.max, params$fun.min,
7474
params$fun.args %||% list()
7575
)

R/stat-summary.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,8 @@ StatSummary <- ggproto("StatSummary", Stat,
185185

186186
setup_params = function(data, params) {
187187
params$flipped_aes <- has_flipped_aes(data, params)
188-
params$fun <- make_summary_fun(
189-
params$fun.data, params$fun,
188+
params[["fun"]] <- make_summary_fun(
189+
params$fun.data, params[["fun"]],
190190
params$fun.max, params$fun.min,
191191
params$fun.args %||% list()
192192
)

R/stat-ydensity.R

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,23 +102,24 @@ StatYdensity <- ggproto(
102102
trim = trim, na.rm = na.rm, drop = drop, bounds = bounds,
103103
quantiles = quantiles
104104
)
105-
if (!drop && any(data$n < 2)) {
105+
if (!drop && any(data[["n"]] < 2)) {
106106
cli::cli_warn(
107107
"Cannot compute density for groups with fewer than two datapoints."
108108
)
109109
}
110110

111111
# choose how violins are scaled relative to each other
112-
data$violinwidth <- switch(scale,
113-
# area : keep the original densities but scale them to a max width of 1
114-
# for plotting purposes only
115-
area = data$density / max(data$density, na.rm = TRUE),
116-
# count: use the original densities scaled to a maximum of 1 (as above)
117-
# and then scale them according to the number of observations
118-
count = data$density / max(data$density, na.rm = TRUE) *
119-
data$n / max(data$n),
120-
# width: constant width (density scaled to a maximum of 1)
121-
width = data$scaled
112+
data$violinwidth <- switch(
113+
scale,
114+
# area : keep the original densities but scale them to a max width of 1
115+
# for plotting purposes only
116+
area = data$density / max(data$density, na.rm = TRUE),
117+
# count: use the original densities scaled to a maximum of 1 (as above)
118+
# and then scale them according to the number of observations
119+
count = data$density / max(data$density, na.rm = TRUE) *
120+
data[["n"]] / max(data[["n"]]),
121+
# width: constant width (density scaled to a maximum of 1)
122+
width = data$scaled
122123
)
123124
data$flipped_aes <- flipped_aes
124125
flip_data(data, flipped_aes)

R/theme-elements.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,10 +389,10 @@ element_grob.element_line <- function(element, x = 0:1, y = 0:1,
389389
linewidth <- size
390390
}
391391

392-
arrow <- if (is.logical(element$arrow) && !element$arrow) {
392+
arrow <- if (is.logical(element[["arrow"]]) && !element[["arrow"]]) {
393393
NULL
394394
} else {
395-
element$arrow
395+
element[["arrow"]]
396396
}
397397
if (is.null(arrow)) {
398398
arrow.fill <- colour
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# GeomXxx$parameters() does not contain partial matches
2+
3+
Code
4+
problems
5+
Output
6+
[1] "GeomBoxplot : `notch` with `notchwidth`"
7+
[2] "GeomContour : `arrow` with `arrow.fill`"
8+
[3] "GeomCurve : `arrow` with `arrow.fill`"
9+
[4] "GeomDensity2d: `arrow` with `arrow.fill`"
10+
[5] "GeomFunction : `arrow` with `arrow.fill`"
11+
[6] "GeomLine : `arrow` with `arrow.fill`"
12+
[7] "GeomPath : `arrow` with `arrow.fill`"
13+
[8] "GeomQuantile : `arrow` with `arrow.fill`"
14+
[9] "GeomSegment : `arrow` with `arrow.fill`"
15+
[10] "GeomSf : `arrow` with `arrow.fill`"
16+
[11] "GeomSpoke : `arrow` with `arrow.fill`"
17+
[12] "GeomStep : `arrow` with `arrow.fill`"
18+
19+
# StatXxx$parameters() does not contain partial matches
20+
21+
Code
22+
problems
23+
Output
24+
[1] "StatDensity : `n` with `na.rm`"
25+
[2] "StatDensity2d : `na.rm` with `n`"
26+
[3] "StatDensity2dFilled: `na.rm` with `n`"
27+
[4] "StatQuantile : `method` with `method.args`"
28+
[5] "StatSmooth : `method` with `method.args`, `n` with `na.rm`"
29+
[6] "StatSummary2d : `fun` with `fun.args`"
30+
[7] "StatSummaryHex : `fun` with `fun.args`"
31+

tests/testthat/test-function-args.R

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@ filter_args <- function(x) {
44
x[all_names]
55
}
66

7+
find_partial_match_pairs <- function(args) {
8+
if (length(args) < 2) {
9+
return(NULL)
10+
}
11+
combinations <- combn(args, 2L)
12+
contains <- startsWith(combinations[1, ], combinations[2, ]) |
13+
startsWith(combinations[2, ], combinations[1, ])
14+
15+
if (!any(contains)) {
16+
return(NULL)
17+
}
18+
19+
problem <- combinations[, contains, drop = FALSE]
20+
paste0("`", problem[1, ], "` with `", problem[2, ], "`")
21+
}
22+
23+
724
test_that("geom_xxx and GeomXxx$draw arg defaults match", {
825
ggplot2_ns <- asNamespace("ggplot2")
926
objs <- ls(ggplot2_ns)
@@ -73,3 +90,53 @@ test_that("stat_xxx and StatXxx$compute_panel arg defaults match", {
7390
)
7491
})
7592
})
93+
94+
# If the following tests fail, you may have introduced a potential partial match
95+
# in argument names. The code should be double checked that is doesn't
96+
# accidentally use `list$arg` when `list$arg_name` also exists. If that doesn't
97+
# occur, the snapshot can be updated.
98+
99+
test_that("GeomXxx$parameters() does not contain partial matches", {
100+
ggplot2_ns <- asNamespace("ggplot2")
101+
objs <- ls(ggplot2_ns)
102+
geom_class_names <- grep("^Geom", objs, value = TRUE)
103+
geom_class_names <- setdiff(geom_class_names, c("Geom"))
104+
105+
problems <- list()
106+
107+
for (geom_class_name in geom_class_names) {
108+
geom_obj <- ggplot2_ns[[geom_class_name]]
109+
params <- geom_obj$parameters()
110+
issues <- find_partial_match_pairs(params)
111+
if (length(issues) == 0) {
112+
next
113+
}
114+
problems[[geom_class_name]] <- issues
115+
}
116+
117+
problems <- vapply(problems, paste0, character(1), collapse = ", ")
118+
problems <- paste0(format(names(problems)), ": ", problems)
119+
expect_snapshot(problems)
120+
})
121+
122+
test_that("StatXxx$parameters() does not contain partial matches", {
123+
ggplot2_ns <- asNamespace("ggplot2")
124+
objs <- ls(ggplot2_ns)
125+
stat_class_names <- grep("^Stat", objs, value = TRUE)
126+
stat_class_names <- setdiff(stat_class_names, c("Stat"))
127+
128+
problems <- list()
129+
130+
for (stat_class_name in stat_class_names) {
131+
stat_obj <- ggplot2_ns[[stat_class_name]]
132+
params <- stat_obj$parameters()
133+
issues <- find_partial_match_pairs(params)
134+
if (length(issues) == 0) {
135+
next
136+
}
137+
problems[[stat_class_name]] <- issues
138+
}
139+
problems <- vapply(problems, paste0, character(1), collapse = ", ")
140+
problems <- paste0(format(names(problems)), ": ", problems)
141+
expect_snapshot(problems)
142+
})

tests/testthat/test-legend-draw.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ test_that("all keys can be drawn without 'params'", {
3636
expect_in(nse, names(keys))
3737

3838
# Add title to every key
39-
template <- gtable(width = unit(size, "mm"), heights = unit(c(1, size), c("lines", "mm")))
39+
template <- gtable(widths = unit(size, "mm"), heights = unit(c(1, size), c("lines", "mm")))
4040
keys <- Map(
4141
function(key, name) {
4242
text <- textGrob(name, gp = gpar(fontsize = 8))

0 commit comments

Comments
 (0)