Skip to content

Commit 9c26edf

Browse files
authored
Merge pull request #801 from jimhester/pipe-call
New pipe_call_linter enforcing calls (not symbols) in magrittr pipes
2 parents ece965c + 4ebb8b8 commit 9c26edf

File tree

4 files changed

+85
-0
lines changed

4 files changed

+85
-0
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Collate:
7878
'open_curly_linter.R'
7979
'paren_brace_linter.R'
8080
'path_linters.R'
81+
'pipe_call_linter.R'
8182
'pipe_continuation_linter.R'
8283
'semicolon_terminator_linter.R'
8384
'seq_linter.R'

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
* `lint_package()` warns and returns `NULL` if no package is found (instead of giving a peculiar error message) (#776, @michaelchirico)
6464
* `lint_package()` is also stricter about what it considers to be a package -- folders named `DESCRIPTION` are ignored (#702, @michaelchirico)
6565
* `lint()` now has a new optional argument `text` for supplying a string or lines directly, e.g. if the file is already in memory or linting is being done ad hoc. (#503, @renkun-ken)
66+
* New `pipe_call_linter()` enforces that all steps of `magrittr` pipelines use explicit calls instead of symbols, e.g. `x %>% mean()` instead of `x %>% mean` (@michaelchirico)
6667

6768
# lintr 2.0.1
6869

R/pipe_call_linter.R

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#' @describeIn linters that forces explicit calls in magrittr pipes
2+
#' @export
3+
pipe_call_linter <- function() {
4+
Linter(function(source_file) {
5+
if (length(source_file$parsed_content) == 0L) {
6+
return(list())
7+
}
8+
9+
xml <- source_file$xml_parsed_content
10+
11+
# NB: the text() here shows up as %&gt;% but that's not matched, %>% is
12+
# NB: use *[1][self::SYMBOL] to ensure the first element is SYMBOL, otherwise
13+
# we include expressions like x %>% .$col
14+
xpath <- "//expr[preceding-sibling::SPECIAL[text() = '%>%'] and *[1][self::SYMBOL]]"
15+
bad_expr <- xml2::xml_find_all(xml, xpath)
16+
17+
return(lapply(
18+
bad_expr,
19+
xml_nodes_to_lint,
20+
source_file = source_file,
21+
message = "Use explicit calls in magrittr pipes, i.e., `a %>% foo` should be `a %>% foo()`.",
22+
type = "warning"
23+
))
24+
})
25+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
test_that("pipe_call_linter skips allowed usages", {
2+
expect_lint("a %>% foo()", NULL, pipe_call_linter)
3+
expect_lint("a %>% foo(x)", NULL, pipe_call_linter)
4+
expect_lint("b %>% { foo(., ., .) }", NULL, pipe_call_linter)
5+
expect_lint("a %>% foo() %>% bar()", NULL, pipe_call_linter)
6+
7+
# ensure it works across lines too
8+
lines <- trim_some("
9+
a %>%
10+
foo() %>%
11+
bar()
12+
")
13+
expect_lint(lines, NULL, pipe_call_linter)
14+
15+
# symbol extraction is OK (don't force extract2(), e.g.)
16+
expect_lint("a %>% .$y %>% mean()", NULL, pipe_call_linter)
17+
18+
# more complicated expressions don't pick up on nested symbols
19+
lines <- trim_some("
20+
x %>% {
21+
tmp <- .
22+
bla <- foo %>% unrelated_stuff(tmp)
23+
my_combination_fun(tmp, bla)
24+
}
25+
")
26+
expect_lint(lines, NULL, pipe_call_linter)
27+
})
28+
29+
test_that("pipe_call_linter blocks simple disallowed usages", {
30+
expect_lint(
31+
"x %>% foo",
32+
"Use explicit calls in magrittr pipes",
33+
pipe_call_linter
34+
)
35+
36+
expect_lint(
37+
"x %>% foo() %>% bar",
38+
"Use explicit calls in magrittr pipes",
39+
pipe_call_linter
40+
)
41+
42+
expect_lint(
43+
"x %>% foo %>% bar()",
44+
"Use explicit calls in magrittr pipes",
45+
pipe_call_linter
46+
)
47+
48+
lines <- trim_some("
49+
a %>%
50+
foo %>%
51+
bar()
52+
")
53+
expect_lint(
54+
lines,
55+
"Use explicit calls in magrittr pipes",
56+
pipe_call_linter
57+
)
58+
})

0 commit comments

Comments
 (0)