-
Notifications
You must be signed in to change notification settings - Fork 187
New ifelse_censor_linter #1007
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 ifelse_censor_linter #1007
Changes from all commits
726d787
df5a11d
42e94b7
528b8e1
c51c22c
6c8b39f
106d1c4
fc99c74
79a8316
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,57 @@ | ||
#' Block usage of ifelse where pmin or pmax is more appropriate | ||
#' | ||
#' `ifelse(x > M, M, x)` is the same as `pmin(x, M)`, but harder | ||
#' to read and requires several passes over the vector. | ||
#' | ||
#' The same goes for other similar ways to censor a vector, e.g. | ||
#' `ifelse(x <= M, x, M)` is `pmin(x, M)`, | ||
#' `ifelse(x < m, m, x)` is `pmax(x, m)`, and | ||
#' `ifelse(x >= m, x, m)` is `pmax(x, m)`. | ||
#' | ||
#' @evalRd rd_tags("ifelse_censor_linter") | ||
#' @seealso [linters] for a complete list of linters available in lintr. | ||
#' @export | ||
ifelse_censor_linter <- function() { | ||
Linter(function(source_file) { | ||
if (length(source_file$xml_parsed_content) == 0L) { | ||
return(list()) | ||
} | ||
|
||
xml <- source_file$xml_parsed_content | ||
|
||
xpath <- glue::glue("//expr[ | ||
expr[SYMBOL_FUNCTION_CALL[ {xp_text_in_table(ifelse_funs)} ]] | ||
and expr[2][ | ||
(LT or GT or LE or GE) | ||
and expr[1] = following-sibling::expr | ||
and expr[2] = following-sibling::expr | ||
] | ||
]") | ||
bad_expr <- xml2::xml_find_all(xml, xpath) | ||
|
||
return(lapply( | ||
bad_expr, | ||
xml_nodes_to_lint, | ||
source_file = source_file, | ||
lint_message = function(expr) { | ||
matched_call <- xml2::xml_text(xml2::xml_find_first(expr, "expr/SYMBOL_FUNCTION_CALL")) | ||
op <- xml2::xml_text(xml2::xml_find_first(expr, "expr[2]/*[2]")) | ||
match_first <- !is.na(xml2::xml_find_first(expr, "expr[2][expr[1] = following-sibling::expr[1]]")) | ||
if (op %in% c("<", "<=")) { | ||
if (match_first) { | ||
sprintf("pmin(x, y) is preferable to %s(x %s y, x, y).", matched_call, op) | ||
} else { | ||
sprintf("pmax(x, y) is preferable to %s(x %s y, y, x).", matched_call, op) | ||
} | ||
} else { | ||
if (match_first) { | ||
sprintf("pmax(x, y) is preferable to %s(x %s y, x, y).", matched_call, op) | ||
} else { | ||
sprintf("pmin(x, y) is preferable to %s(x %s y, y, x).", matched_call, op) | ||
} | ||
} | ||
}, | ||
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,67 @@ | ||
test_that("ifelse_censor_linter skips allowed usages", { | ||
expect_lint("ifelse(x > 2, x, y)", NULL, ifelse_censor_linter()) | ||
expect_lint("ifelse(x > 2, x, y)", NULL, ifelse_censor_linter()) | ||
}) | ||
|
||
test_that("ifelse_censor_linter blocks simple disallowed usages", { | ||
expect_lint( | ||
"ifelse(x < 0, 0, x)", | ||
rex::rex("pmax(x, y) is preferable to ifelse(x < y, y, x)"), | ||
ifelse_censor_linter() | ||
) | ||
# other equivalents to base::ifelse() | ||
expect_lint( | ||
"if_else(x < 0, 0, x)", | ||
rex::rex("pmax(x, y) is preferable to if_else(x < y, y, x)"), | ||
ifelse_censor_linter() | ||
) | ||
expect_lint( | ||
"fifelse(x < 0, 0, x)", | ||
rex::rex("pmax(x, y) is preferable to fifelse(x < y, y, x)"), | ||
ifelse_censor_linter() | ||
) | ||
|
||
# other equivalents for censoring | ||
expect_lint( | ||
"ifelse(x <= 0, 0, x)", | ||
rex::rex("pmax(x, y) is preferable to ifelse(x <= y, y, x)"), | ||
ifelse_censor_linter() | ||
) | ||
expect_lint( | ||
"ifelse(x > 0, x, 0)", | ||
rex::rex("pmax(x, y) is preferable to ifelse(x > y, x, y)"), | ||
ifelse_censor_linter() | ||
) | ||
expect_lint( | ||
"ifelse(x >= 0, x, 0)", | ||
rex::rex("pmax(x, y) is preferable to ifelse(x >= y, x, y)"), | ||
ifelse_censor_linter() | ||
) | ||
|
||
# pairwise min/max (similar to censoring) | ||
expect_lint( | ||
"ifelse(x < y, x, y)", | ||
rex::rex("pmin(x, y) is preferable to ifelse(x < y, x, y)"), | ||
ifelse_censor_linter() | ||
) | ||
expect_lint( | ||
"ifelse(x >= y, y, x)", | ||
rex::rex("pmin(x, y) is preferable to ifelse(x >= y, y, x)"), | ||
ifelse_censor_linter() | ||
) | ||
|
||
# more complicated expression still matches | ||
lines <- trim_some(" | ||
ifelse(2 + p + 104 + 1 > ncols, | ||
ncols, 2 + p + 104 + 1 | ||
) | ||
") | ||
expect_lint( | ||
lines, | ||
rex::rex("pmin(x, y) is preferable to ifelse(x > y, y, x)"), | ||
ifelse_censor_linter() | ||
) | ||
}) | ||
|
||
# TODO(michaelchirico): how easy would it be to strip parens when considering lint? | ||
# e.g. ifelse(x < (kMaxIndex - 1), x, kMaxIndex - 1) |
Uh oh!
There was an error while loading. Please reload this page.