Skip to content

feat: argument to disable linting of ( for auto-printing #2919

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think allow_implicit_print may be better, WDYT? My only hesitation is doubling "implicit" in the linter & argument names...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allow_bracket_print?

I'm already wary of implicit as a description as it already doesn't reflect how I think of my usage of this lint ... basically "stop people accidentally using <- in function calls". I'd prefer not to use the term more if possible.


### New linters

Expand Down
19 changes: 18 additions & 1 deletion R/implicit_assignment_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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) {
Expand Down Expand Up @@ -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]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, I think it will be ideal to work this in to the up-front XPath logic, rather than including those hits in the search only to drop them.

Have a try and if you get stuck we can mark it as a follow-up issue.

Copy link
Author

@TimTaylor TimTaylor Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that initially but then I realised you need to do the print_only for the error message anyway so this seemed to be the most KISS approach. WDY?

}

xml_nodes_to_lints(
bad_expr,
Expand Down
17 changes: 16 additions & 1 deletion man/implicit_assignment_linter.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions tests/testthat/test-implicit_assignment_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -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())
})
Loading