Skip to content

Commit cf9758a

Browse files
Add potential sequence() lints to seq_linter() (#2618)
* Draft sequence_linter linter * Fold sequence_linter() into seq_linter() * Add tests * Add NEWS bullet * Switch to xml_find_function_calls * Change element order in xpath * Support lapply(x, seq) * Simplify xpath * Do not lint seq() calls with extra arguments * Edit false positive example * Add more info about new lints in NEWS * update * Update NEWS.md * add Hugo to DESCRIPTION (and clean it up) * change DESCRIPTION --> change Rd --------- Co-authored-by: Michael Chirico <[email protected]> Co-authored-by: Michael Chirico <[email protected]>
1 parent 500a38c commit cf9758a

File tree

6 files changed

+84
-8
lines changed

6 files changed

+84
-8
lines changed

DESCRIPTION

+8-5
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ Version: 3.2.0.9000
44
Authors@R: c(
55
person("Jim", "Hester", , role = "aut"),
66
person("Florent", "Angly", role = "aut",
7-
comment = "fangly"),
7+
comment = c(GitHub = "fangly")),
88
person("Russ", "Hyde", role = "aut"),
9-
person("Michael", "Chirico", email = "[email protected]", role = c("aut", "cre")),
9+
person("Michael", "Chirico", email = "[email protected]", role = c("aut", "cre"),
10+
comment = c(ORCID = "0000-0003-0787-087X")),
1011
person("Kun", "Ren", role = "aut"),
1112
person("Alexander", "Rosenstock", role = "aut",
12-
comment = "AshesITR"),
13-
person("Indrajeet", "Patil", , "[email protected]", role = "aut",
14-
comment = c(ORCID = "0000-0003-1995-6531", Twitter = "@patilindrajeets"))
13+
comment = c(GitHub = "AshesITR")),
14+
person("Indrajeet", "Patil", email = "[email protected]", role = "aut",
15+
comment = c(ORCID = "0000-0003-1995-6531", Twitter = "@patilindrajeets")),
16+
person("Hugo", "Gruson", role = "aut",
17+
comment = c(ORCID = "0000-0002-4094-1476"))
1518
)
1619
Description: Checks adherence to a given style, syntax errors and possible
1720
semantic issues. Supports on the fly checking of R code edited with

NEWS.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
## New and improved features
2121

2222
* `brace_linter()`' has a new argument `function_bodies` (default `"multi_line"`) which controls when to require function bodies to be wrapped in curly braces, with the options `"always"`, `"multi_line"` (only require curly braces when a function body spans multiple lines), `"not_inline"` (only require curly braces when a function body starts on a new line) and `"never"` (#1807, #2240, @salim-b).
23-
* `seq_linter()` recommends using `seq_along(x)` instead of `seq_len(length(x))` (#2577, @MichaelChirico).
23+
* `seq_linter()`:
24+
+ recommends using `seq_along(x)` instead of `seq_len(length(x))` (#2577, @MichaelChirico).
25+
+ recommends using `sequence()` instead of `unlist(lapply(ints, seq))` (#2618, @Bisaloo)
2426
* `undesirable_operator_linter()` lints operators in prefix form, e.g. `` `%%`(x, 2)`` (#1910, @MichaelChirico). Disable this by setting `call_is_undesirable=FALSE`.
2527
* `indentation_linter()` handles `for` un-braced for loops correctly (#2564, @MichaelChirico).
2628
* Setting `exclusions` supports globs like `knitr*` to exclude files/directories with a pattern (#1554, @MichaelChirico).

R/seq_linter.R

+31-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
#' linters = seq_linter()
3333
#' )
3434
#'
35+
#' lint(
36+
#' text = "unlist(lapply(x, seq_len))",
37+
#' linters = seq_linter()
38+
#' )
39+
#'
3540
#' # okay
3641
#' lint(
3742
#' text = "seq_along(x)",
@@ -53,6 +58,11 @@
5358
#' linters = seq_linter()
5459
#' )
5560
#'
61+
#' lint(
62+
#' text = "sequence(x)",
63+
#' linters = seq_linter()
64+
#' )
65+
#'
5666
#' @evalRd rd_tags("seq_linter")
5767
#' @seealso [linters] for a complete list of linters available in lintr.
5868
#' @export
@@ -80,6 +90,17 @@ seq_linter <- function() {
8090
parent::expr[expr/expr/SYMBOL_FUNCTION_CALL[text() = 'length']]
8191
"
8292

93+
map_funcs <- c("sapply", "lapply", "map")
94+
seq_funcs <- xp_text_in_table(c("seq_len", "seq"))
95+
# count(expr) = 3 because we only want seq() calls without extra arguments
96+
sequence_xpath <- glue("
97+
parent::expr[
98+
count(expr) = 3
99+
and expr/SYMBOL[ {seq_funcs} ]
100+
and preceding-sibling::expr/SYMBOL_FUNCTION_CALL[text() = 'unlist']
101+
]
102+
")
103+
83104
## The actual order of the nodes is document order
84105
## In practice we need to handle length(x):1
85106
get_fun <- function(expr, n) {
@@ -138,6 +159,15 @@ seq_linter <- function() {
138159
type = "warning"
139160
)
140161

141-
c(seq_lints, seq_len_lints)
162+
xml_map_calls <- source_expression$xml_find_function_calls(map_funcs)
163+
potential_sequence_calls <- xml_find_all(xml_map_calls, sequence_xpath)
164+
sequence_lints <- xml_nodes_to_lints(
165+
potential_sequence_calls,
166+
source_expression,
167+
"Use sequence() to generate a concatenated sequence of seq_len().",
168+
type = "warning"
169+
)
170+
171+
c(seq_lints, seq_len_lints, sequence_lints)
142172
})
143173
}

man/lintr-package.Rd

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/seq_linter.Rd

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-seq_linter.R

+30
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,24 @@ test_that("reverse seq is ok", {
123123
)
124124
})
125125

126+
test_that("finds potential sequence() replacements", {
127+
linter <- seq_linter()
128+
lint_msg <- rex::rex("Use sequence()")
129+
130+
expect_lint("unlist(lapply(x, seq_len))", lint_msg, linter)
131+
132+
expect_lint("unlist(lapply(x, seq))", lint_msg, linter)
133+
134+
# Even for prefixed purrr:: calls
135+
expect_lint("unlist(purrr::map(x, seq_len))", lint_msg, linter)
136+
})
137+
138+
test_that("sequence() is not recommended for complex seq() calls", {
139+
linter <- seq_linter()
140+
141+
expect_no_lint("unlist(lapply(x, seq, from = 2))", linter)
142+
})
143+
126144
test_that("Message vectorization works for multiple lints", {
127145
linter <- seq_linter()
128146

@@ -173,6 +191,18 @@ test_that("Message vectorization works for multiple lints", {
173191
),
174192
linter
175193
)
194+
195+
expect_lint(
196+
trim_some("{
197+
1:NROW(x)
198+
unlist(lapply(y, seq_len))
199+
}"),
200+
list(
201+
list(rex::rex("seq_len(NROW(...))", anything, "1:NROW(...)"), line_number = 2L),
202+
list(rex::rex("sequence()"), line_number = 3L)
203+
),
204+
linter
205+
)
176206
})
177207

178208
test_that("Message recommends rev() correctly", {

0 commit comments

Comments
 (0)