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

New which_grepl_linter #2281

Merged
merged 11 commits into from
Nov 18, 2023
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ Collate:
'unused_import_linter.R'
'use_lintr.R'
'vector_logic_linter.R'
'which_grepl_linter.R'
'whitespace_linter.R'
'with.R'
'with_id.R'
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export(unreachable_code_linter)
export(unused_import_linter)
export(use_lintr)
export(vector_logic_linter)
export(which_grepl_linter)
export(whitespace_linter)
export(with_defaults)
export(with_id)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* `stopifnot_all_linter()` discourages tests with `all()` like `stopifnot(all(x > 0))`; `stopifnot()` runs `all()` itself, and uses a better error message (part of #884, @MichaelChirico).
* `comparison_negation_linter()` for discouraging negated comparisons when a direct negation is preferable, e.g. `!(x == y)` could be `x != y` (part of #884, @MichaelChirico).
* `terminal_close_linter()` for discouraging using `close()` to end functions (part of #884, @MichaelChirico). Such usages are not robust to errors, where `close()` will not be run as intended. Put `close()` in an `on.exit()` hook, or use {withr} to manage connections with proper cleanup.
* `which_grepl_linter()` for discouraging `which(grepl(ptn, x))` in favor of directly using `grep(ptn, x)` (part of #884, @MichaelChirico).

### Lint accuracy fixes: removing false positives

Expand Down
17 changes: 17 additions & 0 deletions R/which_grepl_linter.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#' Require usage of grep over which(grepl(.))
#'
#' `which(grepl(pattern, x))` is the same as `grep(pattern, x)`, but harder
#' to read and requires two passes over the vector.
#'
#' @evalRd rd_tags("which_grepl_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
which_grepl_linter <- make_linter_from_xpath(
xpath = "
//SYMBOL_FUNCTION_CALL[text() = 'grepl']
/parent::expr
/parent::expr
/parent::expr[expr/SYMBOL_FUNCTION_CALL[text() = 'which']]
",
lint_message = "grep(pattern, x) is better than which(grepl(pattern, x))."
)
1 change: 1 addition & 0 deletions inst/lintr/linters.csv
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,6 @@ unneeded_concatenation_linter,style readability efficiency configurable deprecat
unreachable_code_linter,best_practices readability
unused_import_linter,best_practices common_mistakes configurable executing
vector_logic_linter,default efficiency best_practices
which_grepl_linter,readability efficiency consistency
whitespace_linter,style consistency default
yoda_test_linter,package_development best_practices readability pkg_testthat
1 change: 1 addition & 0 deletions man/consistency_linters.Rd

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

1 change: 1 addition & 0 deletions man/efficiency_linters.Rd

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

7 changes: 4 additions & 3 deletions man/linters.Rd

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

1 change: 1 addition & 0 deletions man/readability_linters.Rd

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

18 changes: 18 additions & 0 deletions man/which_grepl_linter.Rd

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

27 changes: 27 additions & 0 deletions tests/testthat/test-which_grepl_linter.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
test_that("which_grepl_linter skips allowed usages", {
# this _could_ be combined as p1|p2, but often it's cleaner to read this way
MichaelChirico marked this conversation as resolved.
Show resolved Hide resolved
expect_lint("which(grepl(p1, x) & grepl(p2, x))", NULL, which_grepl_linter())
})

test_that("which_grepl_linter blocks simple disallowed usages", {
linter <- which_grepl_linter()
lint_msg <- rex::rex("grep(pattern, x) is better than which(grepl(pattern, x)).")

expect_lint("which(grepl('^a', x))", lint_msg, linter)
# options also don't matter (grep has more arguments: value, invert)
expect_lint("which(grepl('^a', x, perl = TRUE, fixed = TRUE))", lint_msg, linter)

expect_lint(
trim_some('{
which(x)
grepl(y)
which(grepl("pt1", x))
which(grepl("pt2", y))
}'),
list(
list(lint_msg, line_number = 4L, column_number = 3L),
list(lint_msg, line_number = 5L, column_number = 3L)
),
linter
)
})
Loading