diff --git a/NEWS.md b/NEWS.md index 1a97d003f..5d76e7b23 100644 --- a/NEWS.md +++ b/NEWS.md @@ -48,6 +48,7 @@ * `object_usage_linter()` lints missing packages that may cause false positives (#2872, @AshesITR) * New argument `include_s4_slots` for the `xml_find_function_calls()` entry in the `get_source_expressions()` to govern whether calls of the form `s4Obj@fun()` are included in the result (#2820, @MichaelChirico). * `sprintf_linter()` lints `sprintf()` and `gettextf()` calls when a constant string is passed to `fmt` (#2894, @Bisaloo). +* `implicit_assignment_linter()` gains argument `allow_print` to disable lints for the use of `(` for auto-printing (#2919, @TimTaylor). ### New linters diff --git a/R/implicit_assignment_linter.R b/R/implicit_assignment_linter.R index c45732868..381fc29ab 100644 --- a/R/implicit_assignment_linter.R +++ b/R/implicit_assignment_linter.R @@ -9,6 +9,8 @@ #' @param allow_scoped Logical, default `FALSE`. If `TRUE`, "scoped assignments", #' where the object is assigned in the statement beginning a branch and used only #' within that branch, are skipped. +#' @param allow_print Logical, default `FALSE`. If `TRUE`, using `(` for auto-printing +#' at the top-level is not linted. #' #' @examples #' # will produce lints @@ -22,6 +24,12 @@ #' linters = implicit_assignment_linter() #' ) #' +#' lint( +#' text = "(x <- 1)", +#' linters = implicit_assignment_linter() +#' ) +#' +#' #' # okay #' lines <- "x <- 1L\nif (x) TRUE" #' writeLines(lines) @@ -53,6 +61,11 @@ #' linters = implicit_assignment_linter(allow_scoped = TRUE) #' ) #' +#' lint( +#' text = "(x <- 1)", +#' linters = implicit_assignment_linter(allow_print = TRUE) +#' ) +#' #' @evalRd rd_tags("implicit_assignment_linter") #' @seealso #' - [linters] for a complete list of linters available in lintr. @@ -61,7 +74,8 @@ #' @export implicit_assignment_linter <- function(except = c("bquote", "expression", "expr", "quo", "quos", "quote"), allow_lazy = FALSE, - allow_scoped = FALSE) { + allow_scoped = FALSE, + allow_print = FALSE) { stopifnot(is.null(except) || is.character(except)) if (length(except) > 0L) { @@ -116,6 +130,9 @@ implicit_assignment_linter <- function(except = c("bquote", "expression", "expr" bad_expr <- xml_find_all(xml, xpath) print_only <- !is.na(xml_find_first(bad_expr, "parent::expr[parent::exprlist and *[1][self::OP-LEFT-PAREN]]")) + if (allow_print) { + bad_expr <- bad_expr[!print_only] + } xml_nodes_to_lints( bad_expr, diff --git a/man/implicit_assignment_linter.Rd b/man/implicit_assignment_linter.Rd index 8eee7ea7d..d77d419a6 100644 --- a/man/implicit_assignment_linter.Rd +++ b/man/implicit_assignment_linter.Rd @@ -7,7 +7,8 @@ implicit_assignment_linter( except = c("bquote", "expression", "expr", "quo", "quos", "quote"), allow_lazy = FALSE, - allow_scoped = FALSE + allow_scoped = FALSE, + allow_print = FALSE ) } \arguments{ @@ -19,6 +20,9 @@ trigger conditionally (e.g. in the RHS of \code{&&} or \code{||} expressions) ar \item{allow_scoped}{Logical, default \code{FALSE}. If \code{TRUE}, "scoped assignments", where the object is assigned in the statement beginning a branch and used only within that branch, are skipped.} + +\item{allow_print}{Logical, default \code{FALSE}. If \code{TRUE}, using \code{(} for auto-printing +at the top-level is not linted.} } \description{ Assigning inside function calls makes the code difficult to read, and should @@ -36,6 +40,12 @@ lint( linters = implicit_assignment_linter() ) +lint( + text = "(x <- 1)", + linters = implicit_assignment_linter() +) + + # okay lines <- "x <- 1L\nif (x) TRUE" writeLines(lines) @@ -67,6 +77,11 @@ lint( linters = implicit_assignment_linter(allow_scoped = TRUE) ) +lint( + text = "(x <- 1)", + linters = implicit_assignment_linter(allow_print = TRUE) +) + } \seealso{ \itemize{ diff --git a/tests/testthat/test-implicit_assignment_linter.R b/tests/testthat/test-implicit_assignment_linter.R index 5fe12c360..7e0b56c83 100644 --- a/tests/testthat/test-implicit_assignment_linter.R +++ b/tests/testthat/test-implicit_assignment_linter.R @@ -476,3 +476,20 @@ test_that("call-less '(' mentions avoiding implicit printing", { linter ) }) + +test_that("allow_print allows `(` for auto printing", { + lint_message <- rex::rex("Avoid implicit assignments in function calls.") + linter <- implicit_assignment_linter(allow_print = TRUE) + expect_no_lint("(a <- foo())", linter) + + # Doesn't effect other cases + lint_message <- rex::rex("Avoid implicit assignments in function calls.") + expect_lint("if (x <- 1L) TRUE", lint_message, linter) + expect_lint("while (x <- 0L) FALSE", lint_message, linter) + expect_lint("for (x in 1:10 -> y) print(x)", lint_message, linter) + expect_lint("mean(x <- 1:4)", lint_message, linter) + + # default remains as is + print_msg <- rex::rex("Call print() explicitly instead of relying on implicit printing behavior via '('.") + expect_lint("(a <- foo())", print_msg, implicit_assignment_linter()) +})