Skip to content

Commit b462119

Browse files
New allow_outer_calls argument
1 parent 55152be commit b462119

File tree

3 files changed

+79
-16
lines changed

3 files changed

+79
-16
lines changed

R/nested_pipe_linter.R

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
#' Nesting pipes harms readability; extract sub-steps to separate variables,
44
#' append further pipeline steps, or otherwise refactor such usage away.
55
#'
6-
#' When [try()] or [tryCatch()] are the "outer" call, no lint is thrown,
7-
#' since the "unnested" version of such usage may not work as intended
8-
#' due to how evaluation happens in such cases.
9-
#'
106
#' @param allow_inline Logical, default `TRUE`, in which case only "inner"
117
#' pipelines which span more than one line are linted. If `FALSE`, even
128
#' "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()].
1312
#'
1413
#' @examples
1514
#' # will produce lints
@@ -25,12 +24,24 @@
2524
#' linters = nested_pipe_linter(allow_inline = FALSE)
2625
#' )
2726
#'
27+
#' lint(
28+
#' text = "tryCatch(x %>% filter(grp == 'a'), error = identity)",
29+
#' linters = nested_pipe_linter(allow_outer_calls = character())
30+
#' )
31+
#'
2832
#' # okay
2933
#' lint(
3034
#' text = "df1 %>% inner_join(df2 %>% select(a, b))",
3135
#' linters = nested_pipe_linter()
3236
#' )
3337
#'
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+
#'
3445
#' lint(
3546
#' text = "tryCatch(x %>% filter(grp == 'a'), error = identity)",
3647
#' linters = nested_pipe_linter()
@@ -39,12 +50,14 @@
3950
#' @evalRd rd_tags("nested_pipe_linter")
4051
#' @seealso [linters] for a complete list of linters available in lintr.
4152
#' @export
42-
nested_pipe_linter <- function(allow_inline = TRUE) {
53+
nested_pipe_linter <- function(
54+
allow_inline = TRUE,
55+
allow_outer_calls = c("try", "tryCatch", "withCallingHandlers")) {
4356
multiline_and <- if (allow_inline) "@line1 != @line2 and" else ""
4457
xpath <- glue("
4558
(//PIPE | //SPECIAL[{ xp_text_in_table(magrittr_pipes) }])
4659
/parent::expr[{multiline_and} preceding-sibling::expr/SYMBOL_FUNCTION_CALL[
47-
not(text() = 'try' or text() = 'tryCatch')
60+
not({ xp_text_in_table(allow_outer_calls) })
4861
and (
4962
text() != 'switch'
5063
or parent::expr

man/nested_pipe_linter.Rd

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

tests/testthat/test-nested_pipe_linter.R

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,24 @@ test_that("nested_pipe_linter skips allowed usages", {
3737
NULL,
3838
linter
3939
)
40-
41-
# try/tryCatch must be evaluated inside the call
42-
expect_lint("try(x %>% foo())", NULL, linter)
43-
expect_lint("tryCatch(x %>% foo(), error = identity)", NULL, linter)
4440
})
4541

42+
patrick::with_parameters_test_that(
43+
"allow_outer_calls defaults are ignored by default",
44+
expect_lint(
45+
trim_some(sprintf(outer_call, fmt = "
46+
%s(
47+
x %%>%%
48+
foo()
49+
)
50+
")),
51+
NULL,
52+
nested_pipe_linter()
53+
),
54+
.test_name = c("try", "tryCatch", "withCallingHandlers"),
55+
outer_call = c("try", "tryCatch", "withCallingHandlers")
56+
)
57+
4658
test_that("nested_pipe_linter blocks simple disallowed usages", {
4759
linter <- nested_pipe_linter()
4860
linter_inline <- nested_pipe_linter(allow_inline = FALSE)
@@ -90,6 +102,30 @@ test_that("nested_pipe_linter blocks simple disallowed usages", {
90102
)
91103
})
92104

105+
test_that("allow_outer_calls= argument works", {
106+
expect_lint(
107+
trim_some("
108+
try(
109+
x %>%
110+
foo()
111+
)
112+
"),
113+
rex::rex("Don't nest pipes inside other calls."),
114+
nested_pipe_linter(allow_outer_calls = character())
115+
)
116+
117+
expect_lint(
118+
trim_some("
119+
print(
120+
x %>%
121+
foo()
122+
)
123+
"),
124+
NULL,
125+
nested_pipe_linter(allow_outer_calls = "print")
126+
)
127+
})
128+
93129
test_that("Native pipes are handled as well", {
94130
skip_if_not_r_version("4.1.0")
95131

0 commit comments

Comments
 (0)