From ffb0f7c4f4cef3d09824bb936a089f3232727983 Mon Sep 17 00:00:00 2001 From: Alexander Rosenstock Date: Sun, 19 Jun 2022 20:13:05 +0200 Subject: [PATCH 01/12] refac: styles as named list of regexes --- R/object_name_linters.R | 5 +++-- tests/testthat/test-object_name_linter.R | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/object_name_linters.R b/R/object_name_linters.R index 9ed2b2cc92..3f55b9a6c7 100644 --- a/R/object_name_linters.R +++ b/R/object_name_linters.R @@ -49,10 +49,11 @@ object_name_xpath <- local({ #' @export object_name_linter <- function(styles = c("snake_case", "symbols")) { styles <- match.arg(styles, names(style_regexes), several.ok = TRUE) + styles <- style_regexes[styles] lint_message <- paste0( "Variable and function name style should be ", - glue::glue_collapse(styles, sep = ", ", last = " or "), "." + glue::glue_collapse(unique(names(styles)), sep = ", ", last = " or "), "." ) Linter(function(source_expression) { @@ -93,7 +94,7 @@ object_name_linter <- function(styles = c("snake_case", "symbols")) { } check_style <- function(nms, style, generics = character()) { - conforming <- re_matches(nms, style_regexes[[style]]) + conforming <- re_matches(nms, style) # mark empty names and NA names as conforming conforming[!nzchar(nms) | is.na(conforming)] <- TRUE diff --git a/tests/testthat/test-object_name_linter.R b/tests/testthat/test-object_name_linter.R index 79bb8900ad..741ec79430 100644 --- a/tests/testthat/test-object_name_linter.R +++ b/tests/testthat/test-object_name_linter.R @@ -1,6 +1,5 @@ test_that("styles are correctly identified", { - styles <- names(style_regexes) - do_style_check <- function(nms) lapply(styles, check_style, nms = nms) + do_style_check <- function(nms) lapply(unname(style_regexes), check_style, nms = nms) # symbl UpC lowC snake SNAKE dot allow ALLUP expect_identical(do_style_check("x"), list(FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, TRUE, FALSE)) expect_identical(do_style_check(".x"), list(FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, TRUE, FALSE)) From aa087199f9008fff6f0a4e3327892749788efec9 Mon Sep 17 00:00:00 2001 From: Alexander Rosenstock Date: Sun, 19 Jun 2022 20:40:20 +0200 Subject: [PATCH 02/12] add regexes= argument and strip fewer symbols --- R/object_name_linters.R | 48 ++++++++++++------ man/object_name_linter.Rd | 6 ++- tests/testthat/test-object_name_linter.R | 64 ++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 15 deletions(-) diff --git a/R/object_name_linters.R b/R/object_name_linters.R index 3f55b9a6c7..3c347ca3a4 100644 --- a/R/object_name_linters.R +++ b/R/object_name_linters.R @@ -44,12 +44,32 @@ object_name_xpath <- local({ #' @param styles A subset of #' \Sexpr[stage=render, results=rd]{lintr:::regexes_rd}. A name should #' match at least one of these styles. +#' @param regexes A (possibly named) character vector specifying a custom naming convention. +#' If named, the names will be used in the lint message. Otherwise, "custom" will be used as a name for the style. +#' Quotes (`` `"' ``) and the specials (`%` and trailing `<-`) are not considered part of the name to be matched. #' @evalRd rd_tags("object_name_linter") #' @seealso [linters] for a complete list of linters available in lintr. #' @export -object_name_linter <- function(styles = c("snake_case", "symbols")) { - styles <- match.arg(styles, names(style_regexes), several.ok = TRUE) - styles <- style_regexes[styles] +object_name_linter <- function(styles = c("snake_case", "symbols"), regexes = character()) { + if (length(styles) > 0L) { + # Allow `object_name_linter(NULL, "my_regex")` + styles <- match.arg(styles, names(style_regexes), several.ok = TRUE) + styles <- style_regexes[styles] + } else { + styles <- list() + } + if (length(regexes) > 0L) { + if (!is.character(regexes)) { + stop("`regexes` must be a character vector.") + } + if (is.null(names(regexes))) { + names(regexes) <- "custom" + } + styles <- c(styles, as.list(regexes)) + } + if (length(styles) == 0L) { + stop("At least one style must be specified using `styles` or `regexes`.") + } lint_message <- paste0( "Variable and function name style should be ", @@ -110,8 +130,8 @@ check_style <- function(nms, style, generics = character()) { # If they are not conforming, but are S3 methods then ignore them conforming[!conforming][has_generic] <- TRUE } - # exclude namespace hooks like .onLoad, .Last.lib, etc (#500) - is_special <- is_special_function(nms[!conforming]) + # exclude namespace hooks like .onLoad, .Last.lib, etc (#500) and ... + is_special <- is_special_function(nms[!conforming]) | nms[!conforming] == "..." conforming[!conforming][is_special] <- TRUE } conforming @@ -119,8 +139,8 @@ check_style <- function(nms, style, generics = character()) { # Remove quotes or other things from names strip_names <- function(x) { - x <- re_substitutes(x, rex(start, some_of(".", quote, "`", "%", "$", "@")), "") - x <- re_substitutes(x, rex(some_of(quote, "`", "<", "-", "%", "$", "@"), end), "") + x <- re_substitutes(x, rex(start, some_of(quote, "`", "%")), "") + x <- re_substitutes(x, rex(some_of(quote, "`", "<", "-", "%"), end), "") x } @@ -130,13 +150,13 @@ strip_names <- function(x) { # (they don't strictly _have_ to be defined in base, so could in principle be removed). # .Last.sys and .First.sys are part of base itself, so aren't included here. special_funs <- c( - "onLoad", - "onAttach", - "onUnload", - "onDetach", - "Last.lib", - "First", - "Last" + ".onLoad", + ".onAttach", + ".onUnload", + ".onDetach", + ".Last.lib", + ".First", + ".Last" ) is_special_function <- function(x) { diff --git a/man/object_name_linter.Rd b/man/object_name_linter.Rd index a7aed7e8b4..0649fb8856 100644 --- a/man/object_name_linter.Rd +++ b/man/object_name_linter.Rd @@ -4,12 +4,16 @@ \alias{object_name_linter} \title{Object name linter} \usage{ -object_name_linter(styles = c("snake_case", "symbols")) +object_name_linter(styles = c("snake_case", "symbols"), regexes = character()) } \arguments{ \item{styles}{A subset of \Sexpr[stage=render, results=rd]{lintr:::regexes_rd}. A name should match at least one of these styles.} + +\item{regexes}{A (possibly named) character vector specifying a custom naming convention. +If named, the names will be used in the lint message. Otherwise, "custom" will be used as a name for the style. +Quotes (\verb{`"'}) and the specials (\verb{\%} and trailing \verb{<-}) are not considered part of the name to be matched.} } \description{ Check that object names conform to a naming style. diff --git a/tests/testthat/test-object_name_linter.R b/tests/testthat/test-object_name_linter.R index 741ec79430..1c3e1c682c 100644 --- a/tests/testthat/test-object_name_linter.R +++ b/tests/testthat/test-object_name_linter.R @@ -150,3 +150,67 @@ test_that("object_name_linter won't fail if an imported namespace is unavailable 3L ) }) + +test_that("object_name_linter supports custom regexes", { + # snake_case and symbols also allowed + linter <- object_name_linter( + regexes = c("shinyModule" = rex(start, lower, zero_or_more(alnum), "UI" %or% "Server", end)) + ) + msg <- rex::rex("Variable and function name style should be snake_case, symbols or shinyModule.") + linter2 <- object_name_linter( + styles = NULL, + regexes = c("shinyModule" = rex(start, lower, zero_or_more(alnum), "UI" %or% "Server", end)) + ) + msg2 <- rex::rex("Variable and function name style should be shinyModule.") + + # Can't allow 0 styles + expect_error( + object_name_linter(NULL), + rex::rex("At least one style must be specified using `styles` or `regexes`.") + ) + + expect_lint( + trim_some(" + snake_case <- 42L + \"%+%\" <- function(...) ..1 + ..2 + + myModuleUI <- function(id) { + # blah + } + + myModuleServer <- function(id) { + # blah + } + + myBadName <- 20L + "), + list(line_number = 12L, message = msg), + linter + ) + + expect_lint( + trim_some(" + snake_case <- 42L + \"%+%\" <- function(...) ..1 + ..2 + + myModuleUI <- function(id) { + # blah + } + + myModuleServer <- function(id) { + # blah + } + + myBadName <- 20L + "), + list( + list(line_number = 1L, message = msg2), + list(line_number = 2L, message = msg2), + # argument "id" is linted if we only allow shinyModule names + list(line_number = 4L, column_number = 24L, message = msg2), + list(line_number = 8L, column_number = 28L, message = msg2), + list(line_number = 12L, message = msg2) + ), + linter2 + ) +}) From d626c2a895f6f33795ac97205e364c91adbe927b Mon Sep 17 00:00:00 2001 From: Alexander Rosenstock Date: Sun, 19 Jun 2022 20:49:40 +0200 Subject: [PATCH 03/12] do not overwrite formal argument styles --- R/object_name_linters.R | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/R/object_name_linters.R b/R/object_name_linters.R index 3c347ca3a4..37ee242075 100644 --- a/R/object_name_linters.R +++ b/R/object_name_linters.R @@ -54,9 +54,9 @@ object_name_linter <- function(styles = c("snake_case", "symbols"), regexes = ch if (length(styles) > 0L) { # Allow `object_name_linter(NULL, "my_regex")` styles <- match.arg(styles, names(style_regexes), several.ok = TRUE) - styles <- style_regexes[styles] + style_list <- style_regexes[styles] } else { - styles <- list() + style_list <- list() } if (length(regexes) > 0L) { if (!is.character(regexes)) { @@ -65,15 +65,15 @@ object_name_linter <- function(styles = c("snake_case", "symbols"), regexes = ch if (is.null(names(regexes))) { names(regexes) <- "custom" } - styles <- c(styles, as.list(regexes)) + style_list <- c(style_list, as.list(regexes)) } - if (length(styles) == 0L) { + if (length(style_list) == 0L) { stop("At least one style must be specified using `styles` or `regexes`.") } lint_message <- paste0( "Variable and function name style should be ", - glue::glue_collapse(unique(names(styles)), sep = ", ", last = " or "), "." + glue::glue_collapse(unique(names(style_list)), sep = ", ", last = " or "), "." ) Linter(function(source_expression) { @@ -98,7 +98,7 @@ object_name_linter <- function(styles = c("snake_case", "symbols"), regexes = ch ) generics <- unique(generics[nzchar(generics)]) - style_matches <- lapply(styles, function(style) { + style_matches <- lapply(style_list, function(style) { check_style(nms, style, generics) }) From 6822533c0e51f86d2e16864babd1795f506a2730 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Fri, 1 Jul 2022 22:34:11 -0700 Subject: [PATCH 04/12] grammar --- R/object_name_linters.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/object_name_linters.R b/R/object_name_linters.R index 37ee242075..084b9d565d 100644 --- a/R/object_name_linters.R +++ b/R/object_name_linters.R @@ -46,7 +46,7 @@ object_name_xpath <- local({ #' match at least one of these styles. #' @param regexes A (possibly named) character vector specifying a custom naming convention. #' If named, the names will be used in the lint message. Otherwise, "custom" will be used as a name for the style. -#' Quotes (`` `"' ``) and the specials (`%` and trailing `<-`) are not considered part of the name to be matched. +#' Quotes (`` `"' ``) and specials (`%` and trailing `<-`) are not considered part of the name to be matched. #' @evalRd rd_tags("object_name_linter") #' @seealso [linters] for a complete list of linters available in lintr. #' @export From 8bc3e3ce6a22cd9fcc6f93320e8b584434e5e6cd Mon Sep 17 00:00:00 2001 From: Alexander Rosenstock Date: Mon, 11 Jul 2022 10:14:47 +0200 Subject: [PATCH 05/12] change behaviour of missing(styles) && !missing(regexes), add tests for auto-named regexes. fix test failure pertaining to is_linter not being a linter factory. --- R/object_name_linters.R | 11 ++++-- man/object_name_linter.Rd | 4 +- tests/testthat/test-linter_tags.R | 2 +- tests/testthat/test-object_name_linter.R | 47 +++++++++++++++++------- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/R/object_name_linters.R b/R/object_name_linters.R index 084b9d565d..4aae9acb4b 100644 --- a/R/object_name_linters.R +++ b/R/object_name_linters.R @@ -47,11 +47,13 @@ object_name_xpath <- local({ #' @param regexes A (possibly named) character vector specifying a custom naming convention. #' If named, the names will be used in the lint message. Otherwise, "custom" will be used as a name for the style. #' Quotes (`` `"' ``) and specials (`%` and trailing `<-`) are not considered part of the name to be matched. +#' Note that specifying `regexes` overrides the default `styles`. So if you want to combine `regexes` and `styles`, +#' both need to be explicitly specified. #' @evalRd rd_tags("object_name_linter") #' @seealso [linters] for a complete list of linters available in lintr. #' @export object_name_linter <- function(styles = c("snake_case", "symbols"), regexes = character()) { - if (length(styles) > 0L) { + if ((!missing(styles) || missing(regexes)) && length(styles) > 0L) { # Allow `object_name_linter(NULL, "my_regex")` styles <- match.arg(styles, names(style_regexes), several.ok = TRUE) style_list <- style_regexes[styles] @@ -62,9 +64,10 @@ object_name_linter <- function(styles = c("snake_case", "symbols"), regexes = ch if (!is.character(regexes)) { stop("`regexes` must be a character vector.") } - if (is.null(names(regexes))) { - names(regexes) <- "custom" - } + rx_names <- names2(regexes) + rx_names[!nzchar(rx_names)] <- "custom" + names(regexes) <- rx_names + style_list <- c(style_list, as.list(regexes)) } if (length(style_list) == 0L) { diff --git a/man/object_name_linter.Rd b/man/object_name_linter.Rd index 0649fb8856..10fdd12f93 100644 --- a/man/object_name_linter.Rd +++ b/man/object_name_linter.Rd @@ -13,7 +13,9 @@ match at least one of these styles.} \item{regexes}{A (possibly named) character vector specifying a custom naming convention. If named, the names will be used in the lint message. Otherwise, "custom" will be used as a name for the style. -Quotes (\verb{`"'}) and the specials (\verb{\%} and trailing \verb{<-}) are not considered part of the name to be matched.} +Quotes (\verb{`"'}) and specials (\verb{\%} and trailing \verb{<-}) are not considered part of the name to be matched. +Note that specifying \code{regexes} overrides the default \code{styles}. So if you want to combine \code{regexes} and \code{styles}, +both need to be explicitly specified.} } \description{ Check that object names conform to a naming style. diff --git a/tests/testthat/test-linter_tags.R b/tests/testthat/test-linter_tags.R index 2850241ff2..06b16fe85b 100644 --- a/tests/testthat/test-linter_tags.R +++ b/tests/testthat/test-linter_tags.R @@ -59,7 +59,7 @@ test_that("available_linters matches the set of linters available from lintr", { # ensure that the contents of inst/lintr/linters.csv covers all _linter objects in our namespace expect_identical(sort(lintr_db$linter), sort(linters_in_namespace)) # ensure that all _linter objects in our namespace are also exported - exported_linters <- grep("_linter$", getNamespaceExports("lintr"), value = TRUE) + exported_linters <- setdiff(grep("_linter$", getNamespaceExports("lintr"), value = TRUE), "is_linter") expect_identical(sort(linters_in_namespace), sort(exported_linters)) }) diff --git a/tests/testthat/test-object_name_linter.R b/tests/testthat/test-object_name_linter.R index 1c3e1c682c..7fad6b0796 100644 --- a/tests/testthat/test-object_name_linter.R +++ b/tests/testthat/test-object_name_linter.R @@ -152,16 +152,16 @@ test_that("object_name_linter won't fail if an imported namespace is unavailable }) test_that("object_name_linter supports custom regexes", { - # snake_case and symbols also allowed + # disables default styles linter <- object_name_linter( regexes = c("shinyModule" = rex(start, lower, zero_or_more(alnum), "UI" %or% "Server", end)) ) - msg <- rex::rex("Variable and function name style should be snake_case, symbols or shinyModule.") + msg <- rex::rex("Variable and function name style should be shinyModule.") linter2 <- object_name_linter( - styles = NULL, + styles = c("snake_case", "symbols"), regexes = c("shinyModule" = rex(start, lower, zero_or_more(alnum), "UI" %or% "Server", end)) ) - msg2 <- rex::rex("Variable and function name style should be shinyModule.") + msg2 <- rex::rex("Variable and function name style should be snake_case, symbols or shinyModule.") # Can't allow 0 styles expect_error( @@ -184,7 +184,14 @@ test_that("object_name_linter supports custom regexes", { myBadName <- 20L "), - list(line_number = 12L, message = msg), + list( + list(line_number = 1L, message = msg), + list(line_number = 2L, message = msg), + # argument "id" is linted if we only allow shinyModule names + list(line_number = 4L, column_number = 24L, message = msg), + list(line_number = 8L, column_number = 28L, message = msg), + list(line_number = 12L, message = msg) + ), linter ) @@ -203,14 +210,28 @@ test_that("object_name_linter supports custom regexes", { myBadName <- 20L "), - list( - list(line_number = 1L, message = msg2), - list(line_number = 2L, message = msg2), - # argument "id" is linted if we only allow shinyModule names - list(line_number = 4L, column_number = 24L, message = msg2), - list(line_number = 8L, column_number = 28L, message = msg2), - list(line_number = 12L, message = msg2) - ), + list(line_number = 12L, message = msg2), linter2 ) + + # Default regex naming works + expect_lint( + trim_some(" + a <- 42L + b <- 1L + c <- 2L + "), + list(line_number = 3L, message = rex::rex("Variable and function name style should be custom.")), + object_name_linter(regexes = c("^a$", "^b$")) + ) + + expect_lint( + trim_some(" + a <- 42L + b <- 1L + c <- 2L + "), + list(line_number = 3L, message = rex::rex("Variable and function name style should be a or custom.")), + object_name_linter(regexes = c(a = "^a$", "^b$")) + ) }) From 2f22a3c4c3968cbaf1eb055dd51266786933a4ce Mon Sep 17 00:00:00 2001 From: Indrajeet Patil Date: Sat, 8 Oct 2022 20:00:40 +0200 Subject: [PATCH 06/12] Update test-object_name_linter.R --- tests/testthat/test-object_name_linter.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/testthat/test-object_name_linter.R b/tests/testthat/test-object_name_linter.R index 03f23edf86..01821064d2 100644 --- a/tests/testthat/test-object_name_linter.R +++ b/tests/testthat/test-object_name_linter.R @@ -1,6 +1,5 @@ test_that("styles are correctly identified", { - styles <- names(lintr:::style_regexes) - do_style_check <- function(nms) lapply(styles, lintr:::check_style, nms = nms) + do_style_check <- function(nms) lapply(unname(style_regexes), check_style, nms = nms) # symbl UpC lowC snake SNAKE dot allow ALLUP expect_identical(do_style_check("x"), list(FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, TRUE, FALSE)) From 6665985830dbadbe91eb93796fb1f4086b02ce0d Mon Sep 17 00:00:00 2001 From: Alexander Rosenstock Date: Sat, 15 Oct 2022 15:11:52 +0200 Subject: [PATCH 07/12] split object_name_linters into object_length_linter.R and object_name_linter.R for consistency --- R/object_length_linter.R | 76 +++++++++++++++++ ...ct_name_linters.R => object_name_linter.R} | 81 +------------------ 2 files changed, 78 insertions(+), 79 deletions(-) create mode 100644 R/object_length_linter.R rename R/{object_name_linters.R => object_name_linter.R} (73%) diff --git a/R/object_length_linter.R b/R/object_length_linter.R new file mode 100644 index 0000000000..9efd7eb6ca --- /dev/null +++ b/R/object_length_linter.R @@ -0,0 +1,76 @@ +#' Object length linter +#' +#' Check that object names are not too long. +#' The length of an object name is defined as the length in characters, after removing extraneous parts: +#' +#' * generic prefixes for implementations of S3 generics, e.g. `as.data.frame.my_class` has length 8. +#' * leading `.`, e.g. `.my_hidden_function` has length 18. +#' * "%%" for infix operators, e.g. `%my_op%` has length 5. +#' * trailing `<-` for assignment functions, e.g. `my_attr<-` has length 7. +#' +#' Note that this behavior relies in part on having packages in your Imports available; +#' see the detailed note in [object_name_linter()] for more details. +#' +#' @param length maximum variable name length allowed. +#' +#' @examples +#' # will produce lints +#' lint( +#' text = "very_very_long_variable_name <- 1L", +#' linters = object_length_linter(length = 10L) +#' ) +#' +#' # okay +#' lint( +#' text = "very_very_long_variable_name <- 1L", +#' linters = object_length_linter(length = 30L) +#' ) +#' +#' lint( +#' text = "var <- 1L", +#' linters = object_length_linter(length = 10L) +#' ) +#' +#' @evalRd rd_tags("object_length_linter") +#' @seealso [linters] for a complete list of linters available in lintr. +#' @export +object_length_linter <- function(length = 30L) { + lint_message <- paste("Variable and function names should not be longer than", length, "characters.") + + Linter(function(source_expression) { + if (!is_lint_level(source_expression, "file")) { + return(list()) + } + + xml <- source_expression$full_xml_parsed_content + + assignments <- xml2::xml_find_all(xml, object_name_xpath) + + # Retrieve assigned name + nms <- strip_names( + xml2::xml_text(assignments) + ) + + # run namespace_imports at run-time, not "compile" time to allow package structure to change + ns_imports <- namespace_imports(find_package(source_expression$filename)) + generics <- strip_names(c( + declared_s3_generics(xml), + imported_s3_generics(ns_imports)$fun, + .base_s3_generics + )) + generics <- unique(generics[nzchar(generics)]) + + # Remove generic function names from generic implementations + # This only lints S3 implementations if the class names are too long, still lints generics if they are too long. + nms_stripped <- re_substitutes(nms, rex(start, or(generics), "."), "") + + too_long <- nchar(nms_stripped) > length + + xml_nodes_to_lints( + assignments[too_long], + source_expression = source_expression, + lint_message = lint_message, + type = "style" + ) + }) +} diff --git a/R/object_name_linters.R b/R/object_name_linter.R similarity index 73% rename from R/object_name_linters.R rename to R/object_name_linter.R index 436a7403aa..96c27a8e56 100644 --- a/R/object_name_linters.R +++ b/R/object_name_linter.R @@ -99,7 +99,7 @@ object_name_linter <- function(styles = c("snake_case", "symbols"), regexes = ch stop("`regexes` must be a character vector.") } rx_names <- names2(regexes) - rx_names[!nzchar(rx_names)] <- "custom" + rx_names[!nzchar(rx_names)] <- paste0("/", regexes[!nzchar(rx_names)], "/") # auto-name regex "asd" -> /asd/ names(regexes) <- rx_names style_list <- c(style_list, as.list(regexes)) @@ -109,7 +109,7 @@ object_name_linter <- function(styles = c("snake_case", "symbols"), regexes = ch } lint_message <- paste0( - "Variable and function name style should be ", + "Variable and function name style should match ", glue::glue_collapse(unique(names(style_list)), sep = ", ", last = " or "), "." ) @@ -215,80 +215,3 @@ style_regexes <- list( ) regexes_rd <- toString(paste0("\\sQuote{", names(style_regexes), "}")) - -#' Object length linter -#' -#' Check that object names are not too long. -#' The length of an object name is defined as the length in characters, after removing extraneous parts: -#' -#' * generic prefixes for implementations of S3 generics, e.g. `as.data.frame.my_class` has length 8. -#' * leading `.`, e.g. `.my_hidden_function` has length 18. -#' * "%%" for infix operators, e.g. `%my_op%` has length 5. -#' * trailing `<-` for assignment functions, e.g. `my_attr<-` has length 7. -#' -#' Note that this behavior relies in part on having packages in your Imports available; -#' see the detailed note in [object_name_linter()] for more details. -#' -#' @param length maximum variable name length allowed. -#' -#' @examples -#' # will produce lints -#' lint( -#' text = "very_very_long_variable_name <- 1L", -#' linters = object_length_linter(length = 10L) -#' ) -#' -#' # okay -#' lint( -#' text = "very_very_long_variable_name <- 1L", -#' linters = object_length_linter(length = 30L) -#' ) -#' -#' lint( -#' text = "var <- 1L", -#' linters = object_length_linter(length = 10L) -#' ) -#' -#' @evalRd rd_tags("object_length_linter") -#' @seealso [linters] for a complete list of linters available in lintr. -#' @export -object_length_linter <- function(length = 30L) { - lint_message <- paste("Variable and function names should not be longer than", length, "characters.") - - Linter(function(source_expression) { - if (!is_lint_level(source_expression, "file")) { - return(list()) - } - - xml <- source_expression$full_xml_parsed_content - - assignments <- xml2::xml_find_all(xml, object_name_xpath) - - # Retrieve assigned name - nms <- strip_names( - xml2::xml_text(assignments) - ) - - # run namespace_imports at run-time, not "compile" time to allow package structure to change - ns_imports <- namespace_imports(find_package(source_expression$filename)) - generics <- strip_names(c( - declared_s3_generics(xml), - imported_s3_generics(ns_imports)$fun, - .base_s3_generics - )) - generics <- unique(generics[nzchar(generics)]) - - # Remove generic function names from generic implementations - # This only lints S3 implementations if the class names are too long, still lints generics if they are too long. - nms_stripped <- re_substitutes(nms, rex(start, or(generics), "."), "") - - too_long <- nchar(nms_stripped) > length - - xml_nodes_to_lints( - assignments[too_long], - source_expression = source_expression, - lint_message = lint_message, - type = "style" - ) - }) -} From a2d445ea0d6702a1854f2d6a1f21d4b619464f77 Mon Sep 17 00:00:00 2001 From: Alexander Rosenstock Date: Sat, 15 Oct 2022 15:19:54 +0200 Subject: [PATCH 08/12] document(), add examples with regexes --- DESCRIPTION | 3 ++- R/object_name_linter.R | 10 ++++++++++ man/object_length_linter.Rd | 2 +- man/object_name_linter.Rd | 12 +++++++++++- tests/testthat/test-object_name_linter.R | 16 ++++++++-------- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 6260ff61be..6d20a8b8f9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -126,7 +126,8 @@ Collate: 'nested_ifelse_linter.R' 'no_tab_linter.R' 'numeric_leading_zero_linter.R' - 'object_name_linters.R' + 'object_length_linter.R' + 'object_name_linter.R' 'object_usage_linter.R' 'outer_negation_linter.R' 'package_hooks_linter.R' diff --git a/R/object_name_linter.R b/R/object_name_linter.R index 96c27a8e56..6f0ffcb48b 100644 --- a/R/object_name_linter.R +++ b/R/object_name_linter.R @@ -67,6 +67,11 @@ object_name_xpath <- local({ #' linters = object_name_linter(styles = "dotted.case") #' ) #' +#' lint( +#' text = "asd <- 1L", +#' linters = object_name_linter(regexes = c(my_style = "F$", "f$")) +#' ) +#' #' # okay #' lint( #' text = "my_var <- 1L", @@ -83,6 +88,11 @@ object_name_xpath <- local({ #' linters = object_name_linter(styles = c("dotted.case", "lowercase")) #' ) #' +#' lint( +#' text = "asdf <- 1L; asdF <- 1L", +#' linters = object_name_linter(regexes = c(my_style = "F$", "f$")) +#' ) +#' #' @evalRd rd_tags("object_name_linter") #' @seealso [linters] for a complete list of linters available in lintr. #' @export diff --git a/man/object_length_linter.Rd b/man/object_length_linter.Rd index 39a5c70028..64c647e5cf 100644 --- a/man/object_length_linter.Rd +++ b/man/object_length_linter.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/object_name_linters.R +% Please edit documentation in R/object_length_linter.R \name{object_length_linter} \alias{object_length_linter} \title{Object length linter} diff --git a/man/object_name_linter.Rd b/man/object_name_linter.Rd index 81b9bea4ba..46e1d9a586 100644 --- a/man/object_name_linter.Rd +++ b/man/object_name_linter.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/object_name_linters.R +% Please edit documentation in R/object_name_linter.R \name{object_name_linter} \alias{object_name_linter} \title{Object name linter} @@ -56,6 +56,11 @@ lint( linters = object_name_linter(styles = "dotted.case") ) +lint( + text = "asd <- 1L", + linters = object_name_linter(regexes = c(my_style = "F$", "f$")) +) + # okay lint( text = "my_var <- 1L", @@ -72,6 +77,11 @@ lint( linters = object_name_linter(styles = c("dotted.case", "lowercase")) ) +lint( + text = "asdf <- 1L; asdF <- 1L", + linters = object_name_linter(regexes = c(my_style = "F$", "f$")) +) + } \seealso{ \link{linters} for a complete list of linters available in lintr. diff --git a/tests/testthat/test-object_name_linter.R b/tests/testthat/test-object_name_linter.R index 8e1537234a..f57ed88795 100644 --- a/tests/testthat/test-object_name_linter.R +++ b/tests/testthat/test-object_name_linter.R @@ -54,7 +54,7 @@ test_that("linter ignores some objects", { }) test_that("linter returns correct linting", { - lint_msg <- "Variable and function name style should be camelCase." + lint_msg <- "Variable and function name style should match camelCase." linter <- object_name_linter("camelCase") expect_lint("myObject <- 123", NULL, linter) @@ -86,7 +86,7 @@ test_that("linter returns correct linting", { }) test_that("linter accepts vector of styles", { - lint_msg <- "Variable and function name style should be camelCase or dotted.case." + lint_msg <- "Variable and function name style should match camelCase or dotted.case." linter <- object_name_linter(styles = c("camelCase", "dotted.case")) expect_lint( @@ -99,7 +99,7 @@ test_that("linter accepts vector of styles", { test_that("dollar subsetting only lints the first expression", { # Regression test for #582 linter <- object_name_linter() - lint_msg <- "Variable and function name style should be snake_case or symbols." + lint_msg <- "Variable and function name style should match snake_case or symbols." expect_lint("my_var$MY_COL <- 42L", NULL, linter) expect_lint("MY_VAR$MY_COL <- 42L", lint_msg, linter) @@ -109,7 +109,7 @@ test_that("dollar subsetting only lints the first expression", { test_that("assignment targets of compound lhs are correctly identified", { linter <- object_name_linter() - lint_msg <- "Variable and function name style should be snake_case or symbols." + lint_msg <- "Variable and function name style should match snake_case or symbols." # (recursive) [, $, and [[ subsetting expect_lint("good_name[badName] <- badName2", NULL, linter) @@ -161,12 +161,12 @@ test_that("object_name_linter supports custom regexes", { linter <- object_name_linter( regexes = c("shinyModule" = rex(start, lower, zero_or_more(alnum), "UI" %or% "Server", end)) ) - msg <- rex::rex("Variable and function name style should be shinyModule.") + msg <- rex::rex("Variable and function name style should match shinyModule.") linter2 <- object_name_linter( styles = c("snake_case", "symbols"), regexes = c("shinyModule" = rex(start, lower, zero_or_more(alnum), "UI" %or% "Server", end)) ) - msg2 <- rex::rex("Variable and function name style should be snake_case, symbols or shinyModule.") + msg2 <- rex::rex("Variable and function name style should match snake_case, symbols or shinyModule.") # Can't allow 0 styles expect_error( @@ -226,7 +226,7 @@ test_that("object_name_linter supports custom regexes", { b <- 1L c <- 2L "), - list(line_number = 3L, message = rex::rex("Variable and function name style should be custom.")), + list(line_number = 3L, message = rex::rex("Variable and function name style should match /^a$/ or /^b$/.")), object_name_linter(regexes = c("^a$", "^b$")) ) @@ -236,7 +236,7 @@ test_that("object_name_linter supports custom regexes", { b <- 1L c <- 2L "), - list(line_number = 3L, message = rex::rex("Variable and function name style should be a or custom.")), + list(line_number = 3L, message = rex::rex("Variable and function name style should match a or /^b$/.")), object_name_linter(regexes = c(a = "^a$", "^b$")) ) }) From 395b7e4d77861a9d6dac50ada58e2a45d7a6d931 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Sat, 15 Oct 2022 07:00:19 -0700 Subject: [PATCH 09/12] un-escape quote for readability --- tests/testthat/test-object_name_linter.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-object_name_linter.R b/tests/testthat/test-object_name_linter.R index f57ed88795..ccb8e355e6 100644 --- a/tests/testthat/test-object_name_linter.R +++ b/tests/testthat/test-object_name_linter.R @@ -175,9 +175,9 @@ test_that("object_name_linter supports custom regexes", { ) expect_lint( - trim_some(" + trim_some(' snake_case <- 42L - \"%+%\" <- function(...) ..1 + ..2 + "%+%" <- function(...) ..1 + ..2 myModuleUI <- function(id) { # blah @@ -188,7 +188,7 @@ test_that("object_name_linter supports custom regexes", { } myBadName <- 20L - "), + '), list( list(line_number = 1L, message = msg), list(line_number = 2L, message = msg), From bccf5951c914af11060816800f14b941726a88ca Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Sat, 15 Oct 2022 07:04:16 -0700 Subject: [PATCH 10/12] again --- tests/testthat/test-object_name_linter.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-object_name_linter.R b/tests/testthat/test-object_name_linter.R index ccb8e355e6..8bbda600f7 100644 --- a/tests/testthat/test-object_name_linter.R +++ b/tests/testthat/test-object_name_linter.R @@ -201,9 +201,9 @@ test_that("object_name_linter supports custom regexes", { ) expect_lint( - trim_some(" + trim_some(' snake_case <- 42L - \"%+%\" <- function(...) ..1 + ..2 + "%+%" <- function(...) ..1 + ..2 myModuleUI <- function(id) { # blah @@ -214,7 +214,7 @@ test_that("object_name_linter supports custom regexes", { } myBadName <- 20L - "), + '), list(line_number = 12L, message = msg2), linter2 ) From 246b4f1dcabdf97a20d33ee9c430ccf4f435ca55 Mon Sep 17 00:00:00 2001 From: Alexander Rosenstock Date: Sat, 15 Oct 2022 16:20:56 +0200 Subject: [PATCH 11/12] fix documentation --- R/object_name_linter.R | 6 ++++-- man/object_name_linter.Rd | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/R/object_name_linter.R b/R/object_name_linter.R index 6f0ffcb48b..0ab89a6dcf 100644 --- a/R/object_name_linter.R +++ b/R/object_name_linter.R @@ -24,6 +24,8 @@ object_name_xpath <- local({ #' Check that object names conform to a naming style. #' The default naming styles are "snake_case" and "symbols". #' +#' Quotes (`` `"' ``) and specials (`%` and trailing `<-`) are not considered part of the object name. +#' #' Note when used in a package, in order to ignore objects imported #' from other namespaces, this linter will attempt [getNamespaceExports()] #' whenever an `import(PKG)` or `importFrom(PKG, ...)` statement is found @@ -45,8 +47,8 @@ object_name_xpath <- local({ #' \Sexpr[stage=render, results=rd]{lintr:::regexes_rd}. A name should #' match at least one of these styles. #' @param regexes A (possibly named) character vector specifying a custom naming convention. -#' If named, the names will be used in the lint message. Otherwise, "custom" will be used as a name for the style. -#' Quotes (`` `"' ``) and specials (`%` and trailing `<-`) are not considered part of the name to be matched. +#' If named, the names will be used in the lint message. Otherwise, the regexes enclosed by `/` will be used in the +#' lint message. #' Note that specifying `regexes` overrides the default `styles`. So if you want to combine `regexes` and `styles`, #' both need to be explicitly specified. #' diff --git a/man/object_name_linter.Rd b/man/object_name_linter.Rd index 46e1d9a586..ad5e9ebb45 100644 --- a/man/object_name_linter.Rd +++ b/man/object_name_linter.Rd @@ -12,8 +12,8 @@ object_name_linter(styles = c("snake_case", "symbols"), regexes = character()) match at least one of these styles.} \item{regexes}{A (possibly named) character vector specifying a custom naming convention. -If named, the names will be used in the lint message. Otherwise, "custom" will be used as a name for the style. -Quotes (\verb{`"'}) and specials (\verb{\%} and trailing \verb{<-}) are not considered part of the name to be matched. +If named, the names will be used in the lint message. Otherwise, the regexes enclosed by \code{/} will be used in the +lint message. Note that specifying \code{regexes} overrides the default \code{styles}. So if you want to combine \code{regexes} and \code{styles}, both need to be explicitly specified.} } @@ -22,6 +22,8 @@ Check that object names conform to a naming style. The default naming styles are "snake_case" and "symbols". } \details{ +Quotes (\verb{`"'}) and specials (\verb{\%} and trailing \verb{<-}) are not considered part of the object name. + Note when used in a package, in order to ignore objects imported from other namespaces, this linter will attempt \code{\link[=getNamespaceExports]{getNamespaceExports()}} whenever an \code{import(PKG)} or \code{importFrom(PKG, ...)} statement is found From 1abdfdbd08e599a973291cd43d52e8e6d806ad15 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Sat, 15 Oct 2022 07:25:42 -0700 Subject: [PATCH 12/12] re-use nzchar output --- R/object_name_linter.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/object_name_linter.R b/R/object_name_linter.R index 0ab89a6dcf..a7e37de2a0 100644 --- a/R/object_name_linter.R +++ b/R/object_name_linter.R @@ -111,7 +111,8 @@ object_name_linter <- function(styles = c("snake_case", "symbols"), regexes = ch stop("`regexes` must be a character vector.") } rx_names <- names2(regexes) - rx_names[!nzchar(rx_names)] <- paste0("/", regexes[!nzchar(rx_names)], "/") # auto-name regex "asd" -> /asd/ + missing_name <- !nzchar(rx_names) + rx_names[missing_name] <- paste0("/", regexes[missing_name], "/") # auto-name regex "asd" -> /asd/ names(regexes) <- rx_names style_list <- c(style_list, as.list(regexes))