Skip to content

Commit e8f755d

Browse files
authored
Merge branch 'main' into if_not_else
2 parents ad96854 + 15a22fe commit e8f755d

File tree

3 files changed

+20
-9
lines changed

3 files changed

+20
-9
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
* `conjunct_test_linter()` also lints usage like `dplyr::filter(x, A & B)` in favor of using `dplyr::filter(x, A, B)` (part of #884, @MichaelChirico).
3131
* `sort_linter()` checks for code like `x == sort(x)` which is better served by using the function `is.unsorted()` (part of #884, @MichaelChirico).
3232
* `paste_linter()` gains detection for file paths that are better constructed with `file.path()`, e.g. `paste0(dir, "/", file)` would be better as `file.path(dir, file)` (part of #884, @MichaelChirico).
33+
* `seq_linter()` recommends `rev()` in the lint message for lints like `nrow(x):1` (#1542, @MichaelChirico).
3334
* 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.
3435

3536
### New linters

R/seq_linter.R

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,23 +95,24 @@ seq_linter <- function() {
9595

9696
badx <- xml_find_all(xml, xpath)
9797

98-
# TODO: better message customization. For example, length(x):1
99-
# would get rev(seq_along(x)) as the preferred replacement.
10098
dot_expr1 <- get_fun(badx, 1L)
10199
dot_expr2 <- get_fun(badx, 2L)
102100
seq_along_idx <- grepl("length(", dot_expr1, fixed = TRUE) | grepl("length(", dot_expr2, fixed = TRUE)
103-
replacement <- ifelse(seq_along_idx, "seq_along", "seq_len")
101+
rev_idx <- startsWith(dot_expr2, "1")
102+
103+
replacement <- rep("seq_along(...)", length(badx))
104+
replacement[!seq_along_idx] <- paste0("seq_len(", ifelse(rev_idx, dot_expr1, dot_expr2)[!seq_along_idx], ")")
105+
replacement[rev_idx] <- paste0("rev(", replacement[rev_idx], ")")
104106

105-
dot_expr3 <- ifelse(seq_along_idx, "...", dot_expr2)
106107
lint_message <- ifelse(
107108
grepl("seq", dot_expr1, fixed = TRUE),
108109
sprintf(
109-
"%s(%s) is likely to be wrong in the empty edge case. Use %s(%s) instead.",
110-
dot_expr1, dot_expr2, replacement, dot_expr3
110+
"%s(%s) is likely to be wrong in the empty edge case. Use %s instead.",
111+
dot_expr1, dot_expr2, replacement
111112
),
112113
sprintf(
113-
"%s:%s is likely to be wrong in the empty edge case. Use %s(%s) instead.",
114-
dot_expr1, dot_expr2, replacement, dot_expr3
114+
"%s:%s is likely to be wrong in the empty edge case. Use %s instead.",
115+
dot_expr1, dot_expr2, replacement
115116
)
116117
)
117118

tests/testthat/test-seq_linter.R

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ test_that("reverse seq is ok", {
116116

117117
expect_lint(
118118
"function(x) { length(x):1 }",
119-
rex::rex("length(...):1", anything, "Use seq_along"),
119+
rex::rex("length(...):1", anything, "Use rev(seq_along(...))"),
120120
seq_linter()
121121
)
122122
})
@@ -158,3 +158,12 @@ test_that("Message vectorization works for multiple lints", {
158158
seq_linter()
159159
)
160160
})
161+
162+
test_that("Message recommends rev() correctly", {
163+
linter <- seq_linter()
164+
165+
expect_lint(".N:1", rex::rex("Use rev(seq_len(.N))"), linter)
166+
expect_lint("n():1", rex::rex("Use rev(seq_len(n()))"), linter)
167+
expect_lint("nrow(x):1", rex::rex("Use rev(seq_len(nrow(...)))"), linter)
168+
expect_lint("length(x):1", rex::rex("Use rev(seq_along(...))"), linter)
169+
})

0 commit comments

Comments
 (0)