Skip to content

Commit 9620a8c

Browse files
Merge branch 'main' into inner_comparison
2 parents 86005d5 + ef82966 commit 9620a8c

20 files changed

+331
-16
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ Collate:
8181
'condition_message_linter.R'
8282
'conjunct_test_linter.R'
8383
'consecutive_assertion_linter.R'
84+
'consecutive_mutate_linter.R'
8485
'cyclocomp_linter.R'
8586
'declared_functions.R'
8687
'deprecated.R'

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export(comparison_negation_linter)
4040
export(condition_message_linter)
4141
export(conjunct_test_linter)
4242
export(consecutive_assertion_linter)
43+
export(consecutive_mutate_linter)
4344
export(consecutive_stopifnot_linter)
4445
export(cyclocomp_linter)
4546
export(default_linters)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* `which_grepl_linter()` for discouraging `which(grepl(ptn, x))` in favor of directly using `grep(ptn, x)` (part of #884, @MichaelChirico).
3535
* `list_comparison_linter()` for discouraging comparisons on the output of `lapply()`, e.g. `lapply(x, sum) > 10` (part of #884, @MichaelChirico).
3636
* `print_linter()` for discouraging usage of `print()` on string literals like `print("Reached here")` or `print(paste("Found", nrow(DF), "rows."))` (#1894, @MichaelChirico).
37+
* `consecutive_mutate_linter()` for encouraging consecutive calls to `dplyr::mutate()` to be combined (part of #884, @MichaelChirico).
3738
* `if_switch_linter()` for encouraging `switch()` over repeated `if`/`else` tests (part of #884, @MichaelChirico).
3839
* `nested_pipe_linter()` for discouraging pipes within pipes, e.g. `df1 %>% inner_join(df2 %>% select(a, b))` (part of #884, @MichaelChirico).
3940
* `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).

R/any_duplicated_linter.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,6 @@ any_duplicated_linter <- function() {
113113
type = "warning"
114114
)
115115

116-
return(c(any_duplicated_lints, length_unique_lints))
116+
c(any_duplicated_lints, length_unique_lints)
117117
})
118118
}

R/comments.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jenkins_build_info <- function() {
4242
}
4343

4444
in_travis <- function() {
45-
return(nzchar(Sys.getenv("TRAVIS_REPO_SLUG")))
45+
nzchar(Sys.getenv("TRAVIS_REPO_SLUG"))
4646
}
4747

4848
travis_build_info <- function() {
@@ -59,7 +59,7 @@ travis_build_info <- function() {
5959
}
6060

6161
in_wercker <- function() {
62-
return(nzchar(Sys.getenv("WERCKER_GIT_BRANCH")))
62+
nzchar(Sys.getenv("WERCKER_GIT_BRANCH"))
6363
}
6464

6565
ci_build_info <- function() {

R/consecutive_mutate_linter.R

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#' Require consecutive calls to mutate() to be combined when possible
2+
#'
3+
#' `dplyr::mutate()` accepts any number of columns, so sequences like
4+
#' `DF %>% dplyr::mutate(..1) %>% dplyr::mutate(..2)` are redundant --
5+
#' they can always be expressed with a single call to `dplyr::mutate()`.
6+
#'
7+
#' An exception is for some SQL back-ends, where the translation logic may not be
8+
#' as sophisticated as that in the default `dplyr`, for example in
9+
#' `DF %>% mutate(a = a + 1) %>% mutate(b = a - 2)`.
10+
#'
11+
#' @param invalid_backends Character vector of packages providing dplyr backends
12+
#' which may not be compatible with combining `mutate()` calls in all cases.
13+
#' Defaults to `"dbplyr"` since not all SQL backends can handle re-using
14+
#' a variable defined in the same `mutate()` expression.
15+
#'
16+
#' @examples
17+
#' # will produce lints
18+
#' lint(
19+
#' text = "x %>% mutate(a = 1) %>% mutate(b = 2)",
20+
#' linters = consecutive_mutate_linter()
21+
#' )
22+
#'
23+
#' # okay
24+
#' lint(
25+
#' text = "x %>% mutate(a = 1, b = 2)",
26+
#' linters = consecutive_mutate_linter()
27+
#' )
28+
#'
29+
#' code <- "library(dbplyr)\nx %>% mutate(a = 1) %>% mutate(a = a + 1)"
30+
#' writeLines(code)
31+
#' lint(
32+
#' text = code,
33+
#' linters = consecutive_mutate_linter()
34+
#' )
35+
#'
36+
#' @evalRd rd_tags("consecutive_mutate_linter")
37+
#' @seealso [linters] for a complete list of linters available in lintr.
38+
#' @export
39+
consecutive_mutate_linter <- function(invalid_backends = "dbplyr") {
40+
attach_pkg_xpath <- glue("
41+
//SYMBOL_FUNCTION_CALL[text() = 'library' or text() = 'require']
42+
/parent::expr
43+
/following-sibling::expr
44+
/*[self::SYMBOL or self::STR_CONST]
45+
")
46+
47+
namespace_xpath <- glue("
48+
//SYMBOL_PACKAGE[{ xp_text_in_table(invalid_backends) }]
49+
|
50+
//COMMENT[
51+
contains(text(), '@import')
52+
and (
53+
{xp_or(sprintf(\"contains(text(), '%s')\", invalid_backends))}
54+
)
55+
]
56+
")
57+
58+
# match on the expr, not the SYMBOL_FUNCTION_CALL, to ensure
59+
# namespace-qualified calls only match if the namespaces do.
60+
# expr[2] needed in expr[1][expr[2]] to skip matches on pipelines
61+
# starting like mutate(DF, ...) %>% foo() %>% mutate().
62+
# similarly, expr[1][expr[call='mutate']] covers pipelines
63+
# starting like mutate(DF, ...) %>% mutate(...)
64+
mutate_cond <- xp_and(
65+
"expr/SYMBOL_FUNCTION_CALL[text() = 'mutate']",
66+
"not(SYMBOL_SUB[text() = '.keep' or text() = '.by'])"
67+
)
68+
xpath <- glue("
69+
(//PIPE | //SPECIAL[{ xp_text_in_table(magrittr_pipes) }])
70+
/preceding-sibling::expr[expr[2][{ mutate_cond }] or ({ mutate_cond })]
71+
/following-sibling::expr[{ mutate_cond }]
72+
")
73+
74+
Linter(function(source_expression) {
75+
# need the full file to also catch usages at the top level
76+
if (!is_lint_level(source_expression, "file")) {
77+
return(list())
78+
}
79+
80+
xml <- source_expression$full_xml_parsed_content
81+
82+
attach_str <- get_r_string(xml_find_all(xml, attach_pkg_xpath))
83+
if (any(invalid_backends %in% attach_str)) {
84+
return(list())
85+
}
86+
87+
namespace_expr <- xml_find_first(xml, namespace_xpath)
88+
if (!is.na(namespace_expr)) {
89+
return(list())
90+
}
91+
92+
bad_expr <- xml_find_all(xml, xpath)
93+
94+
xml_nodes_to_lints(
95+
bad_expr,
96+
source_expression = source_expression,
97+
lint_message = "Unify consecutive calls to mutate().",
98+
type = "warning"
99+
)
100+
})
101+
}

R/inner_combine_linter.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ arg_match_condition <- function(arg) {
112112
this_symbol <- sprintf("SYMBOL_SUB[text() = '%s']", arg)
113113
following_symbol <- sprintf("following-sibling::expr/%s", this_symbol)
114114
next_expr <- "following-sibling::expr[1]"
115-
return(xp_or(
115+
xp_or(
116116
sprintf("not(%s) and not(%s)", this_symbol, following_symbol),
117117
xp_and(
118118
this_symbol,
@@ -122,7 +122,7 @@ arg_match_condition <- function(arg) {
122122
this_symbol, following_symbol, next_expr
123123
)
124124
)
125-
))
125+
)
126126
}
127127

128128
build_arg_condition <- function(calls, arguments) {

R/package_hooks_linter.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,13 @@ package_hooks_linter <- function() {
183183
unload_arg_name_lints <-
184184
xml_nodes_to_lints(unload_arg_name_expr, source_expression, unload_arg_name_message, type = "warning")
185185

186-
return(c(
186+
c(
187187
onload_bad_msg_call_lints,
188188
onattach_bad_msg_call_lints,
189189
load_arg_name_lints,
190190
library_require_lints,
191191
bad_unload_call_lints,
192192
unload_arg_name_lints
193-
))
193+
)
194194
})
195195
}

R/redundant_ifelse_linter.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,6 @@ redundant_ifelse_linter <- function(allow10 = FALSE) {
108108
lints <- c(lints, xml_nodes_to_lints(num_expr, source_expression, lint_message, type = "warning"))
109109
}
110110

111-
return(lints)
111+
lints
112112
})
113113
}

R/regex_subset_linter.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,6 @@ regex_subset_linter <- function() {
9494
type = "warning"
9595
)
9696

97-
return(c(grep_lints, stringr_lints))
97+
c(grep_lints, stringr_lints)
9898
})
9999
}

R/scalar_in_linter.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ scalar_in_linter <- function() {
3737
/parent::expr
3838
"
3939

40-
return(Linter(function(source_expression) {
40+
Linter(function(source_expression) {
4141
if (!is_lint_level(source_expression, "expression")) {
4242
return(list())
4343
}
@@ -55,5 +55,5 @@ scalar_in_linter <- function() {
5555
lint_message = lint_msg,
5656
type = "warning"
5757
)
58-
}))
58+
})
5959
}

R/xp_utils.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ xp_text_in_table <- function(table) {
1111
single_quoted <- grepl("'", table, fixed = TRUE)
1212
table[single_quoted] <- sQuote(table[single_quoted], '"')
1313
table[!single_quoted] <- sQuote(table[!single_quoted], "'")
14-
return(paste0("text() = ", table, collapse = " or "))
14+
paste0("text() = ", table, collapse = " or ")
1515
}
1616

1717
paren_wrap <- function(..., sep) {

inst/lintr/linters.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ comparison_negation_linter,readability consistency
1414
condition_message_linter,best_practices consistency
1515
conjunct_test_linter,package_development best_practices readability configurable pkg_testthat
1616
consecutive_assertion_linter,style readability consistency
17+
consecutive_mutate_linter,consistency readability configurable efficiency
1718
consecutive_stopifnot_linter,style readability consistency deprecated
1819
cyclocomp_linter,style readability best_practices default configurable
1920
duplicate_argument_linter,correctness common_mistakes configurable

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

Lines changed: 51 additions & 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/efficiency_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: 5 additions & 4 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)