Skip to content

Commit 68b82ce

Browse files
lint x %in% NA in equals_na_linter (#2112)
1 parent 99fdd85 commit 68b82ce

File tree

4 files changed

+38
-16
lines changed

4 files changed

+38
-16
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* `seq_linter()` recommends `rev()` in the lint message for lints like `nrow(x):1` (#1542, @MichaelChirico).
3535
* New `xp_call_name()` helper to facilitate writing custom linters (#2023, @MichaelChirico). This helper converts a matched XPath to the R function to which it corresponds. This is useful for including the "offending" function in the lint's message.
3636
* `function_argument_linter()` detects usage of `missing()` for the linted argument (#1546, @MichaelChirico). The simplest fix for `function_argument_linter()` lints is typically to set that argument to `NULL` by default, in which case it's usually preferable to update function logic checking `missing()` to check `is.null()` instead.
37+
* `equals_na_linter()` checks for `x %in% NA`, which is a more convoluted form of `is.na(x)` (#2088, @MichaelChirico).
3738
* `commas_linter()` gains an option `allow_trailing` (default `FALSE`) to allow trailing commas while indexing. (#2104, @MEO265)
3839

3940
### New linters

R/equals_na_linter.R

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#' Equality check with NA linter
22
#'
3-
#' Check for `x == NA` and `x != NA`. Such usage is almost surely incorrect --
3+
#' Check for `x == NA`, `x != NA` and `x %in% NA`. Such usage is almost surely incorrect --
44
#' checks for missing values should be done with [is.na()].
55
#'
66
#' @examples
@@ -15,6 +15,11 @@
1515
#' linters = equals_na_linter()
1616
#' )
1717
#'
18+
#' lint(
19+
#' text = "x %in% NA",
20+
#' linters = equals_na_linter()
21+
#' )
22+
#'
1823
#' # okay
1924
#' lint(
2025
#' text = "is.na(x)",
@@ -36,6 +41,9 @@ equals_na_linter <- function() {
3641
//NUM_CONST[ {na_table} ]
3742
/parent::expr
3843
/parent::expr[EQ or NE]
44+
|
45+
//SPECIAL[text() = '%in%' and following-sibling::expr/NUM_CONST[ {na_table} ]]
46+
/parent::expr
3947
")
4048

4149
Linter(function(source_expression) {
@@ -50,7 +58,7 @@ equals_na_linter <- function() {
5058
xml_nodes_to_lints(
5159
bad_expr,
5260
source_expression,
53-
lint_message = "Use is.na for comparisons to NA (not == or !=)",
61+
lint_message = "Use is.na for comparisons to NA (not == or != or %in%)",
5462
type = "warning"
5563
)
5664
})

man/equals_na_linter.Rd

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-equals_na_linter.R

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,42 @@ test_that("equals_na_linter skips allowed usages", {
1717

1818
# nested NAs are okay
1919
expect_lint("x==f(1, ignore = NA)", NULL, linter)
20+
21+
# this should be covered by any_is_na_linter()
22+
expect_lint("NA %in% x", NULL, linter)
2023
})
2124

2225
skip_if_not_installed("tibble")
2326
patrick::with_parameters_test_that(
2427
"equals_na_linter blocks disallowed usages for all combinations of operators and types of NAs",
2528
expect_lint(
2629
paste("x", operation, type_na),
27-
rex::rex("Use is.na for comparisons to NA (not == or !=)"),
30+
rex::rex("Use is.na for comparisons to NA (not == or != or %in%)"),
2831
equals_na_linter()
2932
),
3033
.cases = tibble::tribble(
31-
~.test_name, ~operation, ~type_na,
32-
"equality, logical NA", "==", "NA",
33-
"equality, integer NA", "==", "NA_integer_",
34-
"equality, real NA", "==", "NA_real_",
35-
"equality, complex NA", "==", "NA_complex_",
36-
"equality, character NA", "==", "NA_character_",
37-
"inequality, logical NA", "!=", "NA",
38-
"inequality, integer NA", "!=", "NA_integer_",
39-
"inequality, real NA", "!=", "NA_real_",
40-
"inequality, complex NA", "!=", "NA_complex_",
41-
"inequality, character NA", "!=", "NA_character_"
34+
~.test_name, ~operation, ~type_na,
35+
"equality, logical NA", "==", "NA",
36+
"equality, integer NA", "==", "NA_integer_",
37+
"equality, real NA", "==", "NA_real_",
38+
"equality, complex NA", "==", "NA_complex_",
39+
"equality, character NA", "==", "NA_character_",
40+
"containment, logical NA", "%in%", "NA",
41+
"containment, integer NA", "%in%", "NA_integer_",
42+
"containment, real NA", "%in%", "NA_real_",
43+
"containment, complex NA", "%in%", "NA_complex_",
44+
"containment, character NA", "%in%", "NA_character_",
45+
"inequality, logical NA", "!=", "NA",
46+
"inequality, integer NA", "!=", "NA_integer_",
47+
"inequality, real NA", "!=", "NA_real_",
48+
"inequality, complex NA", "!=", "NA_complex_",
49+
"inequality, character NA", "!=", "NA_character_"
4250
)
4351
)
4452

4553
test_that("equals_na_linter blocks disallowed usages in edge cases", {
4654
linter <- equals_na_linter()
47-
lint_msg <- rex::rex("Use is.na for comparisons to NA (not == or !=)")
55+
lint_msg <- rex::rex("Use is.na for comparisons to NA (not == or != or %in%)")
4856

4957
# missing spaces around operators
5058
expect_lint("x==NA", list(message = lint_msg, line_number = 1L, column_number = 1L), linter)

0 commit comments

Comments
 (0)