Skip to content

Commit 0aefa5f

Browse files
New pipe_return_linter (#2299)
* New pipe_return_linter * remove vestigial * use trim_some() in test * trailing ',' --------- Co-authored-by: Indrajeet Patil <[email protected]>
1 parent 9920110 commit 0aefa5f

10 files changed

+132
-2
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ Collate:
152152
'pipe_call_linter.R'
153153
'pipe_consistency_linter.R'
154154
'pipe_continuation_linter.R'
155+
'pipe_return_linter.R'
155156
'print_linter.R'
156157
'quotes_linter.R'
157158
'redundant_equals_linter.R'

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export(paste_linter)
115115
export(pipe_call_linter)
116116
export(pipe_consistency_linter)
117117
export(pipe_continuation_linter)
118+
export(pipe_return_linter)
118119
export(print_linter)
119120
export(quotes_linter)
120121
export(redundant_equals_linter)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
* `which_grepl_linter()` for discouraging `which(grepl(ptn, x))` in favor of directly using `grep(ptn, x)` (part of #884, @MichaelChirico).
3131
* `list_comparison_linter()` for discouraging comparisons on the output of `lapply()`, e.g. `lapply(x, sum) > 10` (part of #884, @MichaelChirico).
3232
* `print_linter()` for discouraging usage of `print()` on string literals like `print("Reached here")` or `print(paste("Found", nrow(DF), "rows."))` (#1894, @MichaelChirico).
33+
* `pipe_return_linter()` for discouraging usage of `return()` inside a {magrittr} pipeline (part of #884, @MichaelChirico).
3334

3435
### Lint accuracy fixes: removing false positives
3536

R/pipe_return_linter.R

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#' Block usage of return() in magrittr pipelines
2+
#'
3+
#' [return()] inside a magrittr pipeline does not actually execute `return()`
4+
#' like you'd expect: `\(x) { x %>% return(); FALSE }` will return `FALSE`!
5+
#' It will technically work "as expected" if this is the final statement
6+
#' in the function body, but such usage is misleading. Instead, assign
7+
#' the pipe outcome to a variable and return that.
8+
#'
9+
#' @examples
10+
#' # will produce lints
11+
#' lint(
12+
#' text = "function(x) x %>% return()",
13+
#' linters = pipe_return_linter()
14+
#' )
15+
#'
16+
#' # okay
17+
#' code <- "function(x) {\n y <- sum(x)\n return(y)\n}"
18+
#' writeLines(code)
19+
#' lint(
20+
#' text = code,
21+
#' linters = pipe_return_linter()
22+
#' )
23+
#'
24+
#' @evalRd rd_tags("pipe_return_linter")
25+
#' @seealso [linters] for a complete list of linters available in lintr.
26+
#' @export
27+
pipe_return_linter <- make_linter_from_xpath(
28+
# NB: Native pipe disallows this at the parser level, so there's no need
29+
# to lint in valid R code.
30+
xpath = "
31+
//SPECIAL[text() = '%>%']
32+
/following-sibling::expr[expr/SYMBOL_FUNCTION_CALL[text() = 'return']]
33+
",
34+
lint_message = paste(
35+
"Using return() as the final step of a magrittr pipeline",
36+
"is an anti-pattern. Instead, assign the output of the pipeline to",
37+
"a well-named object and return that."
38+
)
39+
)

inst/lintr/linters.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ paste_linter,best_practices consistency configurable
7272
pipe_call_linter,style readability
7373
pipe_consistency_linter,style readability configurable
7474
pipe_continuation_linter,style readability default
75+
pipe_return_linter,best_practices common_mistakes
7576
print_linter,best_practices consistency
7677
quotes_linter,style consistency readability default configurable
7778
redundant_equals_linter,best_practices readability efficiency common_mistakes

man/best_practices_linters.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/common_mistakes_linters.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/linters.Rd

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/pipe_return_linter.Rd

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
test_that("pipe_return_linter skips allowed usages", {
2+
linter <- pipe_return_linter()
3+
4+
normal_pipe_lines <- trim_some("
5+
x %>%
6+
filter(str > 5) %>%
7+
summarize(str = sum(str))
8+
")
9+
expect_lint(normal_pipe_lines, NULL, linter)
10+
11+
normal_function_lines <- trim_some("
12+
pipeline <- function(x) {
13+
out <- x %>%
14+
filter(str > 5) %>%
15+
summarize(str = sum(str))
16+
return(out)
17+
}
18+
")
19+
expect_lint(normal_function_lines, NULL, linter)
20+
21+
nested_return_lines <- trim_some("
22+
pipeline <- function(x) {
23+
x_squared <- x %>%
24+
sapply(function(xi) {
25+
return(xi ** 2)
26+
})
27+
return(x_squared)
28+
}
29+
")
30+
expect_lint(nested_return_lines, NULL, linter)
31+
})
32+
33+
test_that("pipe_return_linter blocks simple disallowed usages", {
34+
lines <- trim_some("
35+
pipeline <- function(x) {
36+
out <- x %>%
37+
filter(str > 5) %>%
38+
summarize(str = sum(str)) %>%
39+
return()
40+
}
41+
")
42+
expect_lint(
43+
lines,
44+
rex::rex("Using return() as the final step of a magrittr pipeline"),
45+
pipe_return_linter()
46+
)
47+
})

0 commit comments

Comments
 (0)