Skip to content

Commit 83ef5b8

Browse files
New unnecessary_placeholder_linter (#1656)
* New unnecessary_placeholder_linter * add examples, links * remove TODO (posted as issue comment) Co-authored-by: Indrajeet Patil <[email protected]>
1 parent 49f9bee commit 83ef5b8

File tree

10 files changed

+147
-2
lines changed

10 files changed

+147
-2
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ Collate:
152152
'undesirable_function_linter.R'
153153
'undesirable_operator_linter.R'
154154
'unnecessary_lambda_linter.R'
155+
'unnecessary_placeholder_linter.R'
155156
'unneeded_concatenation_linter.R'
156157
'unreachable_code_linter.R'
157158
'unused_import_linter.R'

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export(trailing_whitespace_linter)
118118
export(undesirable_function_linter)
119119
export(undesirable_operator_linter)
120120
export(unnecessary_lambda_linter)
121+
export(unnecessary_placeholder_linter)
121122
export(unneeded_concatenation_linter)
122123
export(unreachable_code_linter)
123124
export(unused_import_linter)

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@
6161

6262
* `empty_assignment_linter()` for identifying empty assignments like `x = {}` that are more clearly written as `x = NULL` (@MichaelChirico)
6363

64+
* `unnecessary_placeholder_linter()` for identifying where usage of the {magrittr} placeholder `.` could be omitted (@MichaelChirico)
65+
6466
## Notes
6567

6668
* `lint()` continues to support Rmarkdown documents. For users of custom .Rmd engines, e.g.

R/unnecessary_placeholder_linter.R

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#' Block usage of pipeline placeholders if unnecessary
2+
#'
3+
#' The argument placeholder `.` in magrittr pipelines is unnecessary if
4+
#' passed as the first positional argument; using it can cause confusion
5+
#' and impacts readability.
6+
#'
7+
#' @examples
8+
#' # will produce lints
9+
#' lint(
10+
#' text = "x %>% sum(., na.rm = TRUE)",
11+
#' linters = unnecessary_placeholder_linter()
12+
#' )
13+
#'
14+
#' # okay
15+
#' lint(
16+
#' text = "x %>% sum(na.rm = TRUE)",
17+
#' linters = unnecessary_placeholder_linter()
18+
#' )
19+
#'
20+
#' lint(
21+
#' text = "x %>% lm(data = ., y ~ z)",
22+
#' linters = unnecessary_placeholder_linter()
23+
#' )
24+
#'
25+
#' lint(
26+
#' text = "x %>% outer(., .)",
27+
#' linters = unnecessary_placeholder_linter()
28+
#' )
29+
#'
30+
#' @evalRd rd_tags("unnecessary_placeholder_linter")
31+
#' @seealso [linters] for a complete list of linters available in lintr.
32+
#' @export
33+
unnecessary_placeholder_linter <- function() {
34+
# TODO(michaelchirico): handle R4.2.0 native placeholder _ as well
35+
xpath <- "
36+
//SPECIAL[text() = '%>%']
37+
/following-sibling::expr[
38+
expr/SYMBOL_FUNCTION_CALL
39+
and not(expr[
40+
position() > 2
41+
and descendant-or-self::expr/SYMBOL[text() = '.']
42+
])
43+
]
44+
/expr[2][
45+
SYMBOL[text() = '.']
46+
and not(preceding-sibling::*[1][self::EQ_SUB])
47+
]
48+
"
49+
50+
Linter(function(source_expression) {
51+
if (!is_lint_level(source_expression, "expression")) {
52+
return(list())
53+
}
54+
55+
xml <- source_expression$xml_parsed_content
56+
57+
bad_expr <- xml2::xml_find_all(xml, xpath)
58+
59+
xml_nodes_to_lints(
60+
bad_expr,
61+
source_expression = source_expression,
62+
lint_message = paste(
63+
"Don't use the placeholder (`.`) when it's not needed,",
64+
"i.e., when it's only used as the first positional argument in a pipeline step."
65+
),
66+
type = "warning"
67+
)
68+
})
69+
}

inst/lintr/linters.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ trailing_whitespace_linter,style default
7979
undesirable_function_linter,style efficiency configurable robustness best_practices
8080
undesirable_operator_linter,style efficiency configurable robustness best_practices
8181
unnecessary_lambda_linter,best_practices efficiency readability
82+
unnecessary_placeholder_linter,readability best_practices
8283
unneeded_concatenation_linter,style readability efficiency configurable
8384
unreachable_code_linter,best_practices readability
8485
unused_import_linter,best_practices common_mistakes configurable executing

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

man/unnecessary_placeholder_linter.Rd

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
test_that("unnecessary_placeholder_linter skips allowed usages", {
2+
# . used in position other than first --> ok
3+
expect_lint("x %>% foo(y, .)", NULL, unnecessary_placeholder_linter())
4+
# ditto for nested usage
5+
expect_lint("x %>% foo(y, bar(.))", NULL, unnecessary_placeholder_linter())
6+
# . used twice --> ok
7+
expect_lint("x %>% foo(., .)", NULL, unnecessary_placeholder_linter())
8+
# . used as a keyword argument --> ok
9+
expect_lint("x %>% foo(arg = .)", NULL, unnecessary_placeholder_linter())
10+
})
11+
12+
test_that("unnecessary_placeholder_linter blocks simple disallowed usages", {
13+
expect_lint(
14+
"x %>% sum(.)",
15+
rex::rex("Don't use the placeholder (`.`) when it's not needed"),
16+
unnecessary_placeholder_linter()
17+
)
18+
19+
# can come anywhere in the pipeline
20+
expect_lint(
21+
"x %>% y(.) %>% sum()",
22+
rex::rex("Don't use the placeholder (`.`) when it's not needed"),
23+
unnecessary_placeholder_linter()
24+
)
25+
})

0 commit comments

Comments
 (0)