Skip to content

Commit d1dca49

Browse files
Merge b462119 into ff1dc21
2 parents ff1dc21 + b462119 commit d1dca49

11 files changed

+357
-3
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ Collate:
138138
'namespace.R'
139139
'namespace_linter.R'
140140
'nested_ifelse_linter.R'
141+
'nested_pipe_linter.R'
141142
'nonportable_path_linter.R'
142143
'nrow_subset_linter.R'
143144
'numeric_leading_zero_linter.R'

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export(missing_package_linter)
9999
export(modify_defaults)
100100
export(namespace_linter)
101101
export(nested_ifelse_linter)
102+
export(nested_pipe_linter)
102103
export(no_tab_linter)
103104
export(nonportable_path_linter)
104105
export(nrow_subset_linter)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
* `which_grepl_linter()` for discouraging `which(grepl(ptn, x))` in favor of directly using `grep(ptn, x)` (part of #884, @MichaelChirico).
3232
* `list_comparison_linter()` for discouraging comparisons on the output of `lapply()`, e.g. `lapply(x, sum) > 10` (part of #884, @MichaelChirico).
3333
* `print_linter()` for discouraging usage of `print()` on string literals like `print("Reached here")` or `print(paste("Found", nrow(DF), "rows."))` (#1894, @MichaelChirico).
34+
* `nested_pipe_linter()` for discouraging pipes within pipes, e.g. `df1 %>% inner_join(df2 %>% select(a, b))` (part of #884, @MichaelChirico).
3435
* `nrow_subset_linter()` for discouraging usage like `nrow(subset(x, conditions))` in favor of something like `with(x, sum(conditions))` which doesn't require a full subset of `x` (part of #884, @MichaelChirico).
3536
* `pipe_return_linter()` for discouraging usage of `return()` inside a {magrittr} pipeline (part of #884, @MichaelChirico).
3637

R/nested_pipe_linter.R

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#' Block usage of pipes nested inside other calls
2+
#'
3+
#' Nesting pipes harms readability; extract sub-steps to separate variables,
4+
#' append further pipeline steps, or otherwise refactor such usage away.
5+
#'
6+
#' @param allow_inline Logical, default `TRUE`, in which case only "inner"
7+
#' pipelines which span more than one line are linted. If `FALSE`, even
8+
#' "inner" pipelines that fit in one line are linted.
9+
#' @param allow_outer_calls Character vector dictating which "outer"
10+
#' calls to exempt from the requirement to unnest (see examples). Defaults
11+
#' to [try()], [tryCatch()], and [withCallingHandlers()].
12+
#'
13+
#' @examples
14+
#' # will produce lints
15+
#' code <- "df1 %>%\n inner_join(df2 %>%\n select(a, b)\n )"
16+
#' writeLines(code)
17+
#' lint(
18+
#' text = code,
19+
#' linters = nested_pipe_linter()
20+
#' )
21+
#'
22+
#' lint(
23+
#' text = "df1 %>% inner_join(df2 %>% select(a, b))",
24+
#' linters = nested_pipe_linter(allow_inline = FALSE)
25+
#' )
26+
#'
27+
#' lint(
28+
#' text = "tryCatch(x %>% filter(grp == 'a'), error = identity)",
29+
#' linters = nested_pipe_linter(allow_outer_calls = character())
30+
#' )
31+
#'
32+
#' # okay
33+
#' lint(
34+
#' text = "df1 %>% inner_join(df2 %>% select(a, b))",
35+
#' linters = nested_pipe_linter()
36+
#' )
37+
#'
38+
#' code <- "df1 %>%\n inner_join(df2 %>%\n select(a, b)\n )"
39+
#' writeLines(code)
40+
#' lint(
41+
#' text = code,
42+
#' linters = nested_pipe_linter(allow_outer_calls = "inner_join")
43+
#' )
44+
#'
45+
#' lint(
46+
#' text = "tryCatch(x %>% filter(grp == 'a'), error = identity)",
47+
#' linters = nested_pipe_linter()
48+
#' )
49+
#'
50+
#' @evalRd rd_tags("nested_pipe_linter")
51+
#' @seealso [linters] for a complete list of linters available in lintr.
52+
#' @export
53+
nested_pipe_linter <- function(
54+
allow_inline = TRUE,
55+
allow_outer_calls = c("try", "tryCatch", "withCallingHandlers")) {
56+
multiline_and <- if (allow_inline) "@line1 != @line2 and" else ""
57+
xpath <- glue("
58+
(//PIPE | //SPECIAL[{ xp_text_in_table(magrittr_pipes) }])
59+
/parent::expr[{multiline_and} preceding-sibling::expr/SYMBOL_FUNCTION_CALL[
60+
not({ xp_text_in_table(allow_outer_calls) })
61+
and (
62+
text() != 'switch'
63+
or parent::expr
64+
/following-sibling::expr[1]
65+
/*[self::PIPE or self::SPECIAL[{ xp_text_in_table(magrittr_pipes) }]]
66+
)
67+
]]
68+
")
69+
70+
Linter(function(source_expression) {
71+
if (!is_lint_level(source_expression, "expression")) {
72+
return(list())
73+
}
74+
75+
xml <- source_expression$xml_parsed_content
76+
77+
bad_expr <- xml_find_all(xml, xpath)
78+
79+
xml_nodes_to_lints(
80+
bad_expr,
81+
source_expression = source_expression,
82+
lint_message = "Don't nest pipes inside other calls.",
83+
type = "warning"
84+
)
85+
})
86+
}

inst/lintr/linters.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ missing_argument_linter,correctness common_mistakes configurable
5656
missing_package_linter,robustness common_mistakes
5757
namespace_linter,correctness robustness configurable executing
5858
nested_ifelse_linter,efficiency readability
59+
nested_pipe_linter,readability consistency configurable
5960
no_tab_linter,style consistency deprecated
6061
nonportable_path_linter,robustness best_practices configurable
6162
nrow_subset_linter,efficiency consistency best_practices

man/configurable_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/consistency_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: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/nested_pipe_linter.Rd

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

man/readability_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.

0 commit comments

Comments
 (0)