Skip to content

Commit 3c21288

Browse files
authored
Add check function for named-ness (#6766)
* extract arg formatting logic in separate function * add check function * Apply check in functions * apply to `ggproto()` and deduplicate members
1 parent 60f5ed5 commit 3c21288

File tree

9 files changed

+48
-35
lines changed

9 files changed

+48
-35
lines changed

R/all-classes.R

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -253,21 +253,8 @@ class_labels <- S7::new_class(
253253
"labels", parent = class_S3_gg,
254254
constructor = function(labels = list(), ...) {
255255
warn_dots_empty()
256+
check_named(labels, I("labels"))
256257
S7::new_object(labels)
257-
},
258-
validator = function(self) {
259-
if (!is.list(self)) {
260-
return("labels must be a list.")
261-
}
262-
if (!is_named2(self)) {
263-
return("every label must be named.")
264-
}
265-
dups <- unique(names(self)[duplicated(names(self))])
266-
if (length(dups) > 0) {
267-
dups <- oxford_comma(dups, final = "and")
268-
return(paste0("labels cannot contain duplicate names (", dups, ")."))
269-
}
270-
return(NULL)
271258
}
272259
)
273260

R/ggproto.R

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,7 @@ ggproto <- function(`_class` = NULL, `_inherit` = NULL, ...) {
6868
e <- new.env(parent = emptyenv())
6969

7070
members <- list2(...)
71-
if (length(members) != sum(nzchar(names(members)))) {
72-
cli::cli_abort("All members of a {.cls ggproto} object must be named.")
73-
}
71+
check_named(members, I("Members of a {.cls ggproto} object"))
7472

7573
# R <3.1.2 will error when list2env() is given an empty list, so we need to
7674
# check length. https://github.com/tidyverse/ggplot2/issues/1444

R/limits.R

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,7 @@
8181
#'
8282
lims <- function(...) {
8383
args <- list2(...)
84-
85-
if (!is_named2(args)) {
86-
cli::cli_abort("All arguments must be named.")
87-
}
84+
check_named(args, arg = I("Arguments"))
8885
env <- current_env()
8986
Map(limits, args, names(args), rep(list(env), length(args)))
9087
}

R/stat-bindot.R

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ StatBindot <- ggproto("StatBindot", Stat,
66
required_aes = "x",
77
non_missing_aes = "weight",
88
default_aes = aes(y = after_stat(count)),
9-
dropped_aes = c("bin", "bincenter"), # these are temporary variables that are created and then removed by the stat
9+
10+
# these are temporary variables that are created and then removed by the stat
11+
dropped_aes = c("weight", "bin", "bincenter"),
1012

1113
setup_params = function(data, params) {
1214
if (is.null(params$binwidth)) {
@@ -122,9 +124,7 @@ StatBindot <- ggproto("StatBindot", Stat,
122124
data$x <- midline
123125
}
124126
return(data)
125-
},
126-
127-
dropped_aes = c("weight", "bin", "bincenter")
127+
}
128128
)
129129

130130

R/theme-elements.R

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -746,10 +746,7 @@ check_element_tree <- function(x, arg = caller_arg(x), call = caller_env()) {
746746
if (length(x) < 1) {
747747
return(invisible(NULL))
748748
}
749-
750-
if (!is_named(x)) {
751-
cli::cli_abort("{.arg {arg}} must have names.", call = call)
752-
}
749+
check_named(x, arg = arg, call = call)
753750

754751
# All elements should be constructed with `el_def()`
755752
fields <- names(el_def())

R/utilities-checks.R

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ check_length <- function(x, length = integer(), ..., min = 0, max = Inf,
8383
if (n %in% length) {
8484
return(invisible(NULL))
8585
}
86-
fmt <- if (inherits(arg, "AsIs")) identity else function(x) sprintf("`%s`", x)
8786
if (length(length) > 0) {
8887
type <- paste0("a vector of length ", oxford_comma(length))
8988
if (length(length) == 1) {
@@ -96,7 +95,7 @@ check_length <- function(x, length = integer(), ..., min = 0, max = Inf,
9695
}
9796
msg <- sprintf(
9897
"%s must be %s, not length %d.",
99-
fmt(arg), type, n
98+
fmt_arg(arg), type, n
10099
)
101100
cli::cli_abort(msg, call = call, arg = arg)
102101
}
@@ -122,11 +121,33 @@ check_length <- function(x, length = integer(), ..., min = 0, max = Inf,
122121

123122
msg <- sprintf(
124123
"`%s` must be a %s with %s, not length %d.",
125-
fmt(arg), type, what, n
124+
fmt_arg(arg), type, what, n
126125
)
127126
cli::cli_abort(msg, call = call, arg = arg)
128127
}
129128

129+
check_named <- function(x, arg = caller_arg(x), call = caller_env()) {
130+
if (missing(x)) {
131+
stop_input_type(x, "a vector", arg = arg, call = call)
132+
}
133+
if (length(x) < 1) {
134+
return(invisible())
135+
}
136+
msg <- character()
137+
if (!is_named2(x)) {
138+
msg <- sprintf("%s must have names.", fmt_arg(arg))
139+
} else if (anyDuplicated(names2(x))) {
140+
dups <- names2(x)
141+
dups <- sprintf('"%s"', unique(dups[duplicated(dups)]))
142+
dups <- oxford_comma(dups, final = "and")
143+
msg <- sprintf("%s cannot have duplicate names (%s).", fmt_arg(arg), dups)
144+
}
145+
if (length(msg) < 1) {
146+
return(invisible())
147+
}
148+
cli::cli_abort(msg, call = call, arg = arg)
149+
}
150+
130151
#' Check graphics device capabilities
131152
#'
132153
#' This function makes an attempt to estimate whether the graphics device is
@@ -419,3 +440,10 @@ check_device <- function(feature, action = "warn", op = NULL, maybe = FALSE,
419440
.blend_ops <- c("multiply", "screen", "overlay", "darken", "lighten",
420441
"color.dodge", "color.burn", "hard.light", "soft.light",
421442
"difference", "exclusion")
443+
444+
fmt_arg <- function(x) {
445+
if (inherits(x, "AsIs")) {
446+
return(x)
447+
}
448+
sprintf("`%s`", x)
449+
}

tests/testthat/_snaps/ggproto.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
# construction checks input
22

3-
All members of a <ggproto> object must be named.
3+
Members of a <ggproto> object must have names.
44

55
---
66

7-
All members of a <ggproto> object must be named.
7+
Members of a <ggproto> object must have names.
88

99
---
1010

1111
`_inherit` must be a <ggproto> object, not a <data.frame> object.
1212

13+
---
14+
15+
Members of a <ggproto> object cannot have duplicate names ("foo").
16+
1317
# ggproto objects print well
1418

1519
Code

tests/testthat/_snaps/limits.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# limits() throw meaningful errors
22

3-
All arguments must be named.
3+
Arguments must have names.
44

55
---
66

tests/testthat/test-ggproto.R

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ test_that("construction checks input", {
99
expect_snapshot_error(ggproto("Test", NULL, function(self, a) a))
1010
expect_snapshot_error(ggproto("Test", NULL, a <- function(self, a) a))
1111
expect_snapshot_error(ggproto("Test", mtcars, a = function(self, a) a))
12+
# Duplicate names
13+
expect_snapshot_error(ggproto("Test", NULL, foo = 20, foo = "A"))
1214
})
1315

1416
test_that("all ggproto methods start with `{` (#6459)", {

0 commit comments

Comments
 (0)