Skip to content

Commit ca1b16d

Browse files
catch NA %in% x (#2436)
Co-authored-by: AshesITR <alexander.rosenstock@web.de>
1 parent f865f94 commit ca1b16d

File tree

3 files changed

+35
-7
lines changed

3 files changed

+35
-7
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
* `unreachable_code_linter()` has an argument `allow_comment_regex` for customizing which "terminal" comments to exclude (#2327, @MichaelChirico). `# nolint end` comments are always excluded, as are {covr} exclusions (e.g. `# nocov end`) by default.
4242
* `format()` and `print()` methods for `lint` and `lints` classes get a new option `width` to control the printing width of lint messages (#1884, @MichaelChirico). The default is controlled by a new option `lintr.format_width`; if unset, no wrapping occurs (matching earlier behavior).
4343
* New function node caching for big efficiency gains to most linters (e.g. overall `lint_package()` improvement of 14-27% and core linting improvement up to 30%; #2357, @AshesITR). Most linters are written around function usage, and XPath performance searching for many functions is poor. The new `xml_find_function_calls()` entry in the `get_source_expressions()` output caches all function call nodes instead. See the vignette on creating linters for more details on how to use it.
44+
* `any_is_na_linter()` is extended to catch the unusual usage `NA %in% x` (#2113, @MichaelChirico).
4445

4546
### New linters
4647

R/any_is_na_linter.R

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
#' @seealso [linters] for a complete list of linters available in lintr.
3737
#' @export
3838
any_is_na_linter <- function() {
39-
xpath <- "
39+
any_xpath <- "
4040
parent::expr
4141
/following-sibling::expr[1][expr[1][SYMBOL_FUNCTION_CALL[text() = 'is.na']]]
4242
/parent::expr[
@@ -45,15 +45,28 @@ any_is_na_linter <- function() {
4545
]
4646
"
4747

48+
in_xpath <- "//SPECIAL[text() = '%in%']/preceding-sibling::expr[NUM_CONST[starts-with(text(), 'NA')]]"
49+
4850
Linter(linter_level = "expression", function(source_expression) {
51+
xml <- source_expression$xml_parsed_content
4952
xml_calls <- source_expression$xml_find_function_calls("any")
50-
bad_expr <- xml_find_all(xml_calls, xpath)
5153

52-
xml_nodes_to_lints(
53-
bad_expr,
54+
any_expr <- xml_find_all(xml_calls, any_xpath)
55+
any_lints <- xml_nodes_to_lints(
56+
any_expr,
5457
source_expression = source_expression,
5558
lint_message = "anyNA(x) is better than any(is.na(x)).",
5659
type = "warning"
5760
)
61+
62+
in_expr <- xml_find_all(xml, in_xpath)
63+
in_lints <- xml_nodes_to_lints(
64+
in_expr,
65+
source_expression = source_expression,
66+
lint_message = "anyNA(x) is better than NA %in% x.",
67+
type = "warning"
68+
)
69+
70+
c(any_lints, in_lints)
5871
})
5972
}

tests/testthat/test-any_is_na_linter.R

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,31 @@ test_that("any_is_na_linter blocks simple disallowed usages", {
2626
expect_lint("foo(any(is.na(x)))", lint_message, linter)
2727
})
2828

29+
test_that("NA %in% x is also found", {
30+
linter <- any_is_na_linter()
31+
lint_message <- rex::rex("anyNA(x) is better than NA %in% x.")
32+
33+
expect_lint("NA %in% x", lint_message, linter)
34+
expect_lint("NA_real_ %in% x", lint_message, linter)
35+
expect_lint("NA_not_a_sentinel_ %in% x", NULL, linter)
36+
})
37+
2938
test_that("lints vectorize", {
30-
lint_message <- rex::rex("anyNA(x) is better than any(is.na(x)).")
39+
any_message <- rex::rex("any(is.na(x))")
40+
in_message <- rex::rex("NA %in% x")
3141

3242
expect_lint(
3343
trim_some("{
3444
any(is.na(foo(x)))
3545
any(is.na(y), na.rm = TRUE)
46+
NA %in% a
47+
NA_complex_ %in% b
3648
}"),
3749
list(
38-
list(lint_message, line_number = 2L),
39-
list(lint_message, line_number = 3L)
50+
list(any_message, line_number = 2L),
51+
list(any_message, line_number = 3L),
52+
list(in_message, line_number = 4L),
53+
list(in_message, line_number = 5L)
4054
),
4155
any_is_na_linter()
4256
)

0 commit comments

Comments
 (0)