Skip to content

Commit 2580ab2

Browse files
extend infix_spaces_linter to be more flexible & correct (#931)
* extend infix_spaces_linter to be more flexible & correct * git mangled * clarifying comments * test of grouped exclusion by %% * missed ~ in docs * use angle brackets for external URL * remove linter from DB & document() * sAF=FALSE for R<4 * sQuote version issue * more tests * switch from with() usage to appease object_usage_linter * mini-edit to create a commit * roxygenize * roxygenize * fix new lintr lints caught by the improvement Co-authored-by: AshesITR <alexander.rosenstock@web.de>
1 parent 3a78194 commit 2580ab2

18 files changed

+160
-158
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ script.R
99

1010
*.Rcheck
1111
lintr_*.tar.gz
12+
testthat-problems.rds

DESCRIPTION

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ Collate:
5050
'actions.R'
5151
'addins.R'
5252
'assignment_linter.R'
53-
'assignment_spaces_linter.R'
5453
'backport_linter.R'
5554
'cache.R'
5655
'closed_curly_linter.R'

NAMESPACE

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export(absolute_path_linter)
1414
export(all_undesirable_functions)
1515
export(all_undesirable_operators)
1616
export(assignment_linter)
17-
export(assignment_spaces_linter)
1817
export(available_linters)
1918
export(backport_linter)
2019
export(checkstyle_output)

NEWS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
format specifiers (#472, @russHyde)
1111
* New style SNAKE_CASE for `object_name_linter()` (#494, @AshesITR)
1212
* RStudio source markers are cleared when there are no lints (#520, @AshesITR)
13-
* New `assignment_spaces()` lintr. (#538, @f-ritter)
1413
* `seq_linter()`'s lint message is clearer about the reason for linting. (#522, @michaelchirico)
1514
* New `missing_package_linter()` (#536, #547, @renkun-ken)
1615
* New `namespace_linter()` (#548, #551, @renkun-ken)
@@ -95,6 +94,8 @@ function calls. (#850, #851, @renkun-ken)
9594
+ `expect_true_false_linter()` Require usage of `expect_true(x)` over `expect_equal(x, TRUE)` and similar
9695
* `expect_length_linter()` Require usage of `expect_length(x, n)` over `expect_equal(length(x), n)` and similar
9796
* `assignment_linter()` now lints right assignment (`->` and `->>`) and gains two arguments. `allow_cascading_assign` (`TRUE` by default) toggles whether to lint `<<-` and `->>`; `allow_right_assign` toggles whether to lint `->` and `->>` (#915, @michaelchirico)
97+
* `infix_spaces_linter()` gains argument `exclude_operators` to disable lints on selected infix operators. By default, all "low-precedence" operators throw lints; see `?infix_spaces_linter` for an enumeration of these. (#914 @michaelchirico)
98+
* `infix_spaces_linter()` now throws a lint on `a~b` and `function(a=1) {}` (#930, @michaelchirico)
9899

99100
# lintr 2.0.1
100101

R/assignment_spaces_linter.R

Lines changed: 0 additions & 38 deletions
This file was deleted.

R/infix_spaces_linter.R

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,81 @@
1-
# names: xml tags; values: getParseData tokens
2-
# these can be used as unary operators; treat separately
3-
unary_infix_tokens <- c(
4-
"OP-PLUS" = "'+'", # + : unary plus
5-
"OP-MINUS" = "'-'", # - : unary minus
1+
# some metadata about infix operators on the R parse tree.
2+
# xml_tag gives the XML tag as returned by xmlparsedata::xml_parse_data().
3+
# r_string gives the operator as you would write it in R code.
4+
# NB: this metadata is used elsewhere in lintr, e.g. spaces_left_parentheses_linter.
5+
# because of that, even though some rows of this table are currently unused, but
6+
# we keep them around because it's useful to keep this info in one place.
7+
infix_metadata <- data.frame(stringsAsFactors = FALSE, matrix(byrow = TRUE, ncol = 2L, c(
8+
"OP-PLUS", "+",
9+
"OP-MINUS", "-",
10+
"OP-TILDE", "~",
11+
"GT", ">",
12+
"GE", ">=",
13+
"LT", "<",
14+
"LE", "<=",
15+
"EQ", "==",
16+
"NE", "!=",
17+
"AND", "&",
18+
"OR", "|",
19+
"AND2", "&&",
20+
"OR2", "||",
21+
"LEFT_ASSIGN", "<-", # also includes := and <<-
22+
"RIGHT_ASSIGN", "->", # also includes ->>
23+
"EQ_ASSIGN", "=",
24+
"EQ_SUB", "=", # in calls: foo(x = 1)
25+
"EQ_FORMALS", "=", # in definitions: function(x = 1)
26+
"SPECIAL", "%%",
27+
"OP-SLASH", "/",
28+
"OP-STAR", "*",
29+
"OP-COMMA", ",",
30+
"OP-CARET", "^", # also includes **
31+
"OP-AT", "@",
32+
"OP-EXCLAMATION", "!",
33+
"OP-COLON", ":",
34+
"NS_GET", "::",
35+
"NS_GET_INT", ":::",
36+
"OP-LEFT-BRACE", "{",
37+
"OP-LEFT-BRACKET", "[",
38+
"LBB", "[[",
39+
"OP-LEFT-PAREN", "(",
40+
"OP-QUESTION", "?",
641
NULL
42+
)))
43+
names(infix_metadata) <- c("xml_tag", "string_value")
44+
# utils::getParseData()'s designation for the tokens wouldn't be valid as XML tags
45+
infix_metadata$parse_tag <- ifelse(
46+
startsWith(infix_metadata$xml_tag, "OP-"),
47+
# NB: sQuote(x, "'") doesn't work on older R versions
48+
paste0("'", infix_metadata$string_value, "'"),
49+
infix_metadata$xml_tag
750
)
8-
binary_infix_tokens <- c(
9-
10-
"GT" = "GT", # > : greater than
11-
"GE" = "GE", # <= : greater than or equal to
12-
"LT" = "LT", # < : less than
13-
"LE" = "LE", # <= : less than or equal to
14-
"EQ" = "EQ", # == : vector equality
15-
"NE" = "NE", # != : not equal
16-
"AND" = "AND", # & : vector boolean and
17-
"OR" = "OR", # | : vector boolean or
18-
"AND2" = "AND2", # && : scalar boolean and
19-
"OR2" = "OR2", # || : scalar boolean or
20-
"LEFT_ASSIGN" = "LEFT_ASSIGN", # <- or := : left assignment
21-
"RIGHT_ASSIGN" = "RIGHT_ASSIGN", # -> : right assignment
22-
"EQ_ASSIGN" = "EQ_ASSIGN", # = : equal assignment
23-
"EQ_SUB" = "EQ_SUB", # = : keyword assignment
24-
"SPECIAL" = "SPECIAL", # %[^%]*% : infix operators
25-
"OP-SLASH" = "'/'", # / : unary division
26-
"OP-STAR" = "'*'", # * : unary multiplication
27-
28-
NULL
51+
# treated separately because spacing rules are different for unary operators
52+
infix_metadata$unary <- infix_metadata$xml_tag %in% c("OP-PLUS", "OP-MINUS", "OP-TILDE")
53+
# high-precedence operators are ignored by this linter; see
54+
# https://style.tidyverse.org/syntax.html#infix-operators
55+
infix_metadata$low_precedence <- infix_metadata$string_value %in% c(
56+
"+", "-", "~", ">", ">=", "<", "<=", "==", "!=", "&", "&&", "|", "||", "<-", "->", "=", "%%", "/", "*"
2957
)
30-
infix_tokens <- c(unary_infix_tokens, binary_infix_tokens)
3158

3259
#' Infix spaces linter
3360
#'
34-
#' Check that infix operators are surrounded by spaces.
61+
#' Check that infix operators are surrounded by spaces. Enforces the corresponding Tidyverse style guide rule;
62+
#' see <https://style.tidyverse.org/syntax.html#infix-operators>.
3563
#'
64+
#' @param exclude_operators Character vector of operators to exlude from consideration for linting.
65+
#' Default is to include the following "low-precedence" operators:
66+
#' `+`, `-`, `~`, `>`, `>=`, `<`, `<=`, `==`, `!=`, `&`, `&&`, `|`, `||`, `<-`, `:=`, `<<-`, `->`, `->>`,
67+
#' `=`, `/`, `*`, and any infix operator (exclude infixes by passing `"%%"`). Note that `<-`, `:=`, and `<<-`
68+
#' are included/excluded as a group (indicated by passing `"<-"`), as are `->` and `->>` (_viz_, `"->"`),
69+
#' and that `=` for assignment and for setting arguments in calls are treated the same.
3670
#' @evalRd rd_tags("infix_spaces_linter")
3771
#' @seealso [linters] for a complete list of linters available in lintr.
3872
#' @export
39-
infix_spaces_linter <- function() {
73+
infix_spaces_linter <- function(exclude_operators = NULL) {
4074
Linter(function(source_file) {
75+
infix_tokens <- infix_metadata[
76+
infix_metadata$low_precedence & !infix_metadata$string_value %in% exclude_operators,
77+
"parse_tag"
78+
]
4179
lapply(
4280
ids_with_token(source_file, infix_tokens, fun = `%in%`),
4381
function(id) {

R/methods.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ names.lints <- function(x, ...) {
129129
}
130130

131131
#' @export
132-
split.lints <- function(x, f=NULL, ...) {
132+
split.lints <- function(x, f = NULL, ...) {
133133
if (is.null(f)) f <- names(x)
134134
splt <- split.default(x, f)
135135
for (i in names(splt)) class(splt[[i]]) <- "lints"

R/path_linters.R

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ is_path <- function(path) {
5959
re_matches(path, path_regex)
6060
}
6161

62-
is_valid_path <- function(path, lax=FALSE) {
62+
is_valid_path <- function(path, lax = FALSE) {
6363
# Given a character vector of paths, return FALSE for directory or file having valid characters.
6464
# On Windows, invalid chars are all control chars and: * ? " < > | :
6565
# On Unix, all characters are valid, except when lax=TRUE (use same invalid chars as Windows).
@@ -91,7 +91,7 @@ is_long_path <- function(path) {
9191
)
9292
}
9393

94-
is_valid_long_path <- function(path, lax=FALSE) {
94+
is_valid_long_path <- function(path, lax = FALSE) {
9595
# Convenience function to avoid linting short paths and those unlikely to be valid paths
9696
ret <- is_valid_path(path, lax)
9797
if (lax) {
@@ -101,7 +101,7 @@ is_valid_long_path <- function(path, lax=FALSE) {
101101
}
102102

103103

104-
split_path <- function(path, sep="/|\\\\") {
104+
split_path <- function(path, sep = "/|\\\\") {
105105
if (!is.character(path)) {
106106
stop("argument 'path' should be a character vector")
107107
}

R/spaces_left_parentheses_linter.R

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ spaces_left_parentheses_linter <- function() {
2929
for_cond <- "@start - 1 = parent::forcond/preceding-sibling::FOR/@end"
3030

3131
# see infix_spaces_linter.R; preceding-sibling::* is needed for unary operators where '-(a)' is ok
32-
unary_nodes <- c(names(unary_infix_tokens), "OP-TILDE")
32+
unary_nodes <- infix_metadata[infix_metadata$unary, "xml_tag"]
3333
unary_selves <- paste0("self::", unary_nodes, "[preceding-sibling::*]", collapse = " or ")
34-
binary_nodes <- c(names(binary_infix_tokens), "EQ_FORMALS", "OP-COMMA", "OP-LEFT-BRACE", "ELSE", "IN")
34+
binary_nodes <- c(
35+
infix_metadata[infix_metadata$low_precedence & !infix_metadata$unary, "xml_tag"],
36+
"OP-COMMA", "OP-LEFT-BRACE", "ELSE", "IN"
37+
)
3538
binary_selves <- paste0("self::", binary_nodes, collapse = " or ")
3639
# preceding-symbol::* catches (1) function definitions and (2) function calls
3740
# ancestor::expr needed for nested RHS expressions, e.g. 'y1<-(abs(yn)>90)*1'

R/utils.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ viapply <- function(x, ...) vapply(x, ..., FUN.VALUE = integer(1))
163163
# imitate sQuote(x, q) [requires R>=3.6]
164164
quote_wrap <- function(x, q) paste0(q, x, q)
165165

166-
unquote <- function(str, q="`") {
166+
unquote <- function(str, q = "`") {
167167
# Remove surrounding quotes (select either single, double or backtick) from given character vector
168168
# and unescape special characters.
169169
str <- re_substitutes(str, rex(start, q, capture(anything), q, end), "\\1")
@@ -185,7 +185,7 @@ escape_chars <- c(
185185
#"\\`" --> "`" # ASCII grave accent (backtick)
186186
)
187187

188-
unescape <- function(str, q="`") {
188+
unescape <- function(str, q = "`") {
189189
names(q) <- paste0("\\", q)
190190
my_escape_chars <- c(escape_chars, q)
191191
res <- gregexpr(text = str, pattern = rex(or(names(my_escape_chars))))

inst/lintr/linters.csv

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
linter,tags
22
absolute_path_linter,robustness best_practices configurable
33
assignment_linter,style consistency default
4-
assignment_spaces_linter,style readability
54
backport_linter,robustness configurable package_development
65
closed_curly_linter,style readability default configurable
76
commas_linter,style readability default

man/assignment_spaces_linter.Rd

Lines changed: 0 additions & 17 deletions
This file was deleted.

man/infix_spaces_linter.Rd

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

man/linters.Rd

Lines changed: 2 additions & 3 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: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/style_linters.Rd

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

tests/testthat/test-assignment_spaces_linter.R

Lines changed: 0 additions & 54 deletions
This file was deleted.

0 commit comments

Comments
 (0)