-
Notifications
You must be signed in to change notification settings - Fork 188
length_test_linter() for length(x == 0) #2124
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
Changes from 4 commits
d3f1417
ac69c2e
a69819c
ffa3e97
4196e85
dace666
ec488de
ba6ef99
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
#' Check for a common mistake where length is applied in the wrong place | ||
#' | ||
#' Usage like `length(x == 0)` is a mistake. If you intended to check `x` is empty, | ||
#' use `length(x) == 0`. Other mistakes are possible, but running `length()` on the | ||
#' outcome of a logical comparison is never the best choice. | ||
#' | ||
#' @examples | ||
#' # will produce lints | ||
#' lint( | ||
#' text = "length(x == 0)", | ||
#' linters = length_test_linter() | ||
#' ) | ||
#' | ||
#' # okay | ||
#' lint( | ||
#' text = "length(x) > 0", | ||
#' linters = length_test_linter() | ||
#' ) | ||
#' @evalRd rd_tags("class_equals_linter") | ||
#' @seealso [linters] for a complete list of linters available in lintr. | ||
#' @export | ||
length_test_linter <- function() { | ||
xpath <- glue::glue(" | ||
//SYMBOL_FUNCTION_CALL[text() = 'length'] | ||
/parent::expr | ||
/following-sibling::expr[{ xp_or(infix_metadata$xml_tag[infix_metadata$comparator]) }] | ||
/parent::expr | ||
") | ||
|
||
Linter(function(source_expression) { | ||
if (!is_lint_level(source_expression, "expression")) { | ||
return(list()) | ||
} | ||
|
||
xml <- source_expression$xml_parsed_content | ||
|
||
bad_expr <- xml_find_all(xml, xpath) | ||
expr_parts <- vapply(lapply(bad_expr, xml_find_all, "expr[2]/*"), xml_text, character(3L)) | ||
lint_message <- sprintf( | ||
"Checking the length of a logical vector is likely a mistake. Did you mean `length(%s) %s %s`?", | ||
expr_parts[1L, ], expr_parts[2L, ], expr_parts[3L, ] | ||
) | ||
|
||
xml_nodes_to_lints( | ||
bad_expr, | ||
source_expression = source_expression, | ||
lint_message = lint_message, | ||
type = "warning" | ||
) | ||
}) | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
test_that("skips allowed usages", { | ||
linter <- length_test_linter() | ||
|
||
expect_lint("length(x) > 0", NULL, linter) | ||
expect_lint("length(DF[key == val, cols])", NULL, linter) | ||
}) | ||
|
||
test_that("blocks simple disallowed usages", { | ||
linter <- length_test_linter() | ||
lint_msg_stub <- rex::rex("Checking the length of a logical vector is likely a mistake. Did you mean ") | ||
|
||
expect_lint("length(x == 0)", rex::rex(lint_msg_stub, "`length(x) == 0`?"), linter) | ||
expect_lint("length(x == y)", rex::rex(lint_msg_stub, "`length(x) == y`?"), linter) | ||
expect_lint("length(x + y == 2)", rex::rex(lint_msg_stub, "`length(x+y) == 2`?"), linter) | ||
}) | ||
|
||
local({ | ||
ops <- c(lt = "<", lte = "<=", gt = ">", gte = ">=", eq = "==", neq = "!=") | ||
linter <- length_test_linter() | ||
lint_msg_stub <- rex::rex("Checking the length of a logical vector is likely a mistake. Did you mean ") | ||
|
||
patrick::with_parameters_test_that( | ||
"all logical operators detected", | ||
expect_lint( | ||
paste("length(x", op, "y)"), | ||
rex::rex("`length(x) ", op, " y`?"), | ||
linter | ||
), | ||
op = ops, | ||
.test_name = names(ops) | ||
) | ||
}) |
Uh oh!
There was an error while loading. Please reload this page.