Skip to content

Commit e1859e5

Browse files
Lint identical(x, sort(x)) in sort_linter() (#2921)
* Add tests for is.unsorted() recommendation on identical() * Add new XPath components for identical() * Split XPaths for idential() and == * Add NEWS item * Use expect_no_lint() for false positives * Add false positive tests for is.unsorted * Add one expression matching test * Work around comments --------- Co-authored-by: Michael Chirico <chiricom@google.com>
1 parent 4d64422 commit e1859e5

File tree

3 files changed

+39
-4
lines changed

3 files changed

+39
-4
lines changed

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@
4747
+ `unreachable_code_linter()` #2827
4848
+ `vector_logic_linter()` #2826
4949

50+
## New and improved features
51+
52+
### Linter improvements
53+
54+
* `sort_linter()` recommends usage of `!is.unsorted(x)` over `identical(x, sort(x))` (#2921, @Bisaloo).
55+
5056
## Notes
5157

5258
* {lintr} now requires R 4.1.0

R/sort_linter.R

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,17 @@ sort_linter <- function() {
8282
")
8383

8484
sorted_xpath <- "
85-
self::*[
85+
expr[
8686
(EQ or NE)
8787
and expr/expr = expr
8888
and not(expr/EQ_SUB)
8989
]"
90-
90+
sorted_identical_xpath <- "
91+
expr[
92+
expr/SYMBOL_FUNCTION_CALL[text() = 'identical']
93+
and expr/expr = expr
94+
]
95+
"
9196

9297
arguments_xpath <-
9398
".//SYMBOL_SUB[text() = 'method' or text() = 'decreasing' or text() = 'na.last']"
@@ -132,7 +137,7 @@ sort_linter <- function() {
132137
type = "warning"
133138
)
134139

135-
sort_calls <- xml_parent(xml_parent(source_expression$xml_find_function_calls("sort")))
140+
sort_calls <- xml_parent(xml_parent(xml_parent(source_expression$xml_find_function_calls("sort"))))
136141
sort_calls <- strip_comments_from_subtree(sort_calls)
137142
sorted_expr <- xml_find_all(sort_calls, sorted_xpath)
138143

@@ -143,8 +148,22 @@ sort_linter <- function() {
143148
"Use is.unsorted(x) to test the unsortedness of a vector."
144149
)
145150

151+
sorted_identical_expr <- xml_find_all(sort_calls, sorted_identical_xpath)
152+
is_negated <- !is.na(
153+
xml_find_first(sorted_identical_expr, "preceding-sibling::OP-EXCLAMATION")
154+
)
155+
156+
lint_message <- c(
157+
lint_message,
158+
ifelse(
159+
is_negated,
160+
"Use is.unsorted(x) to test the unsortedness of a vector.",
161+
"Use !is.unsorted(x) to test the sortedness of a vector."
162+
)
163+
)
164+
146165
sorted_lints <- xml_nodes_to_lints(
147-
sorted_expr,
166+
combine_nodesets(sorted_expr, sorted_identical_expr),
148167
source_expression = source_expression,
149168
lint_message = lint_message,
150169
type = "warning"

tests/testthat/test-sort_linter.R

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ test_that("sort_linter skips allowed usages", {
1212
expect_no_lint("x[order(x, y)]", linter)
1313
# pretty sure this never makes sense, but test anyway
1414
expect_no_lint("x[order(y, na.last = x)]", linter)
15+
16+
# is.unsorted false positives
17+
expect_no_lint("identical(sort(x), y)", linter)
18+
expect_no_lint("identical(sort(foo(x)), x)", linter)
1519
})
1620

1721

@@ -134,15 +138,21 @@ test_that("sort_linter blocks simple disallowed usages for is.sorted cases", {
134138
sorted_msg <- rex::rex("Use !is.unsorted(x) to test the sortedness of a vector.")
135139

136140
expect_lint("sort(x) == x", sorted_msg, linter)
141+
expect_lint("identical(x, sort(x))", sorted_msg, linter)
137142

138143
# argument order doesn't matter
139144
expect_lint("x == sort(x)", sorted_msg, linter)
145+
expect_lint("identical(sort(x), x)", sorted_msg, linter)
140146

141147
# inverted version
142148
expect_lint("sort(x) != x", unsorted_msg, linter)
149+
expect_lint("!identical(x, sort(x))", unsorted_msg, linter)
143150

144151
# expression matching
145152
expect_lint("sort(foo(x)) == foo(x)", sorted_msg, linter)
153+
expect_lint("identical(foo(x), sort(foo(x)))", sorted_msg, linter)
154+
expect_lint("identical(sort(foo(x)), foo(x))", sorted_msg, linter)
155+
146156
expect_lint(
147157
trim_some("
148158
sort(foo(x # comment

0 commit comments

Comments
 (0)