Skip to content
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

lint %<>% in assignment_linter #2020

Merged
merged 7 commits into from
Jul 28, 2023
Merged
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
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

* `inner_combine_linter()` no longer throws on length-1 calls to `c()` like `c(exp(2))` or `c(log(3))` (#2017, @MichaelChirico). Such usage is discouraged by `unnecessary_concatenation_linter()`, but `inner_combine_linter()` _per se_ does not apply.

## Changes to defaults

* `assignment_linter()` lints the {magrittr} assignment pipe `%<>%` (#2008, @MichaelChirico). This can be deactivated by setting the new argument `allow_pipe_assign` to `TRUE`.

# lintr 3.1.0

## Deprecations & Breaking Changes
Expand Down
35 changes: 26 additions & 9 deletions R/assignment_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#' If `FALSE`, [`<<-`][base::assignOps] and `->>` are not allowed.
#' @param allow_right_assign Logical, default `FALSE`. If `TRUE`, `->` and `->>` are allowed.
#' @param allow_trailing Logical, default `TRUE`. If `FALSE` then assignments aren't allowed at end of lines.
#' @param allow_pipe_assign Logical, default `FALSE`. If `TRUE`, magrittr's `%<>%` assignment is allowed.
#'
#' @examples
#' # will produce lints
Expand All @@ -21,6 +22,11 @@
#' linters = assignment_linter()
#' )
#'
#' lint(
#' text = "x %<>% as.character()",
#' linters = assignment_linter()
#' )
#'
#' # okay
#' lint(
#' text = "x <- mean(x)",
Expand Down Expand Up @@ -53,19 +59,29 @@
#' linters = assignment_linter(allow_trailing = FALSE)
#' )
#'
#' lint(
#' text = "x %<>% as.character()",
#' linters = assignment_linter(allow_pipe_assign = TRUE)
#' )
#'
#' @evalRd rd_tags("assignment_linter")
#' @seealso
#' - [linters] for a complete list of linters available in lintr.
#' - <https://style.tidyverse.org/syntax.html#assignment-1>
#' - <https://style.tidyverse.org/pipes.html#assignment-2>
#' @export
assignment_linter <- function(allow_cascading_assign = TRUE, allow_right_assign = FALSE, allow_trailing = TRUE) {
assignment_linter <- function(allow_cascading_assign = TRUE,
allow_right_assign = FALSE,
allow_trailing = TRUE,
allow_pipe_assign = FALSE) {
trailing_assign_xpath <- paste(
collapse = " | ",
c(
paste0("//LEFT_ASSIGN", if (allow_cascading_assign) "" else "[text() = '<-']"),
if (allow_right_assign) paste0("//RIGHT_ASSIGN", if (allow_cascading_assign) "" else "[text() = '->']"),
"//EQ_SUB",
"//EQ_FORMALS"
"//EQ_FORMALS",
if (!allow_pipe_assign) "//SPECIAL[text() = '%<>%']"
),
"[@line1 < following-sibling::expr[1]/@line1]"
)
Expand All @@ -79,7 +95,8 @@ assignment_linter <- function(allow_cascading_assign = TRUE, allow_right_assign
# NB: := is not linted because of (1) its common usage in rlang/data.table and
# (2) it's extremely uncommon as a normal assignment operator
if (!allow_cascading_assign) "//LEFT_ASSIGN[text() = '<<-']",
if (!allow_trailing) trailing_assign_xpath
if (!allow_trailing) trailing_assign_xpath,
if (!allow_pipe_assign) "//SPECIAL[text() = '%<>%']"
))

Linter(function(source_expression) {
Expand All @@ -95,16 +112,16 @@ assignment_linter <- function(allow_cascading_assign = TRUE, allow_right_assign
}

operator <- xml2::xml_text(bad_expr)
lint_message_fmt <- ifelse(
operator %in% c("<<-", "->>"),
"%s can have hard-to-predict behavior; prefer assigning to a specific environment instead (with assign() or <-).",
"Use <-, not %s, for assignment."
)
lint_message_fmt <- rep("Use <-, not %s, for assignment.", length(operator))
lint_message_fmt[operator %in% c("<<-", "->>")] <-
"%s can have hard-to-predict behavior; prefer assigning to a specific environment instead (with assign() or <-)."
lint_message_fmt[operator == "%<>%"] <-
"Avoid the assignment pipe %s; prefer using <- and %%>%% separately."
IndrajeetPatil marked this conversation as resolved.
Show resolved Hide resolved

if (!allow_trailing) {
bad_trailing_expr <- xml2::xml_find_all(xml, trailing_assign_xpath)
trailing_assignments <- xml2::xml_attrs(bad_expr) %in% xml2::xml_attrs(bad_trailing_expr)
lint_message_fmt[trailing_assignments] <- "Assignment %s should not be trailing at end of line"
lint_message_fmt[trailing_assignments] <- "Assignment %s should not be trailing at the end of a line."
}

lint_message <- sprintf(lint_message_fmt, operator)
Expand Down
16 changes: 15 additions & 1 deletion man/assignment_linter.Rd

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

23 changes: 22 additions & 1 deletion tests/testthat/test-assignment_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ test_that("arguments handle trailing assignment operators correctly", {

expect_lint(
"foo(bar =\n1)",
rex::rex("= should not be trailing"),
rex::rex("= should not be trailing at the end of a line."),
assignment_linter(allow_trailing = FALSE)
)

Expand Down Expand Up @@ -163,3 +163,24 @@ test_that("allow_trailing interacts correctly with comments in braced expression
linter
)
})

test_that("%<>% throws a lint", {
expect_lint("x %<>% sum()", "Avoid the assignment pipe %<>%", assignment_linter())
expect_lint("x %<>% sum()", NULL, assignment_linter(allow_pipe_assign = TRUE))

# interaction with allow_trailing
expect_lint("x %<>%\n sum()", "Assignment %<>% should not be trailing", assignment_linter(allow_trailing = FALSE))
})

test_that("multiple lints throw correct messages", {
expect_lint(
"{ x <<- 1; y ->> 2; z -> 3; x %<>% as.character() }",
list(
list(message = "<<- can have hard-to-predict behavior"),
list(message = "->> can have hard-to-predict behavior"),
list(message = "Use <-, not ->"),
list(message = "Avoid the assignment pipe %<>%")
),
assignment_linter(allow_cascading_assign = FALSE)
)
})
Loading