Nothing
#' Infix spaces linter
#'
#' Check that infix operators are surrounded by spaces. Enforces the corresponding Tidyverse style guide rule;
#' see <https://style.tidyverse.org/syntax.html#infix-operators>.
#'
#' @param exclude_operators Character vector of operators to exclude from consideration for linting.
#' Default is to include the following "low-precedence" operators:
#' `+`, `-`, `~`, `>`, `>=`, `<`, `<=`, `==`, `!=`, `&`, `&&`, `|`, `||`, `<-`, `:=`, `<<-`, `->`, `->>`,
#' `=`, `/`, `*`, and any infix operator (exclude infixes by passing `"%%"`). Note that `"="` here includes
#' three different operators, from the parser's point of view. To lint only some of these, pass the
#' corresponding parse tags (i.e., some of `"EQ_ASSIGN"`, `"EQ_SUB"`, and `"EQ_FORMALS"`; see
#' [utils::getParseData()]).
#' @param allow_multiple_spaces Logical, default `TRUE`. If `FALSE`, usage like `x = 2` will also be linted;
#' excluded by default because such usage can sometimes be used for better code alignment, as is allowed
#' by the style guide.
#'
#' @examples
#' # will produce lints
#' lint(
#' text = "x<-1L",
#' linters = infix_spaces_linter()
#' )
#'
#' lint(
#' text = "1:4 %>%sum()",
#' linters = infix_spaces_linter()
#' )
#'
#' # okay
#' lint(
#' text = "x <- 1L",
#' linters = infix_spaces_linter()
#' )
#'
#' lint(
#' text = "1:4 %>% sum()",
#' linters = infix_spaces_linter()
#' )
#'
#' code_lines <- "
#' ab <- 1L
#' abcdef <- 2L
#' "
#' writeLines(code_lines)
#' lint(
#' text = code_lines,
#' linters = infix_spaces_linter(allow_multiple_spaces = TRUE)
#' )
#'
#' lint(
#' text = "a||b",
#' linters = infix_spaces_linter(exclude_operators = "||")
#' )
#'
#' lint(
#' text = "sum(1:10, na.rm=TRUE)",
#' linters = infix_spaces_linter(exclude_operators = "EQ_SUB")
#' )
#'
#' @evalRd rd_tags("infix_spaces_linter")
#' @seealso
#' - [linters] for a complete list of linters available in lintr.
#' - <https://style.tidyverse.org/syntax.html#infix-operators>
#' @export
infix_spaces_linter <- function(exclude_operators = NULL, allow_multiple_spaces = TRUE) {
if (allow_multiple_spaces) {
op <- "<"
lint_message <- "Put spaces around all infix operators."
} else {
op <- "!="
lint_message <- "Put exactly one space on each side of infix operators."
}
infix_tokens <- infix_metadata$xml_tag_exact[
infix_metadata$low_precedence &
!infix_metadata$string_value %in% exclude_operators &
# parse_tag, not xml_tag, since the former is easier for the user to discover with getParseData()
!infix_metadata$parse_tag %in% exclude_operators
]
# NB: preceding-sibling::* and not preceding-sibling::expr because
# of the foo(a=1) case, where the tree is <SYMBOL_SUB><EQ_SUB><expr>
# NB: parent::*[count(expr | SYMBOL_SUB)) > 1] for the unary case, e.g. x[-1]
# SYMBOL_SUB for case with missing argument like alist(a =)
# NB: the last not() disables lints inside box::use() declarations
global_xpath <- paste0("//", infix_tokens, collapse = "|")
xpath <- glue("({global_xpath})[
parent::*[count(expr | SYMBOL_SUB) > 1]
and (
(
@line1 = preceding-sibling::*[1]/@line2
and @start {op} preceding-sibling::*[1]/@end + 2
) or (
@line1 = following-sibling::*[1]/@line1
and following-sibling::*[1]/@start {op} @end + 2
)
)
and not(
self::OP-SLASH[
ancestor::expr/preceding-sibling::OP-LEFT-PAREN/preceding-sibling::expr[
./SYMBOL_PACKAGE[text() = 'box'] and
./SYMBOL_FUNCTION_CALL[text() = 'use']
]
]
)
]")
Linter(function(source_expression) {
if (!is_lint_level(source_expression, "expression")) {
return(list())
}
xml <- source_expression$xml_parsed_content
bad_expr <- xml_find_all(xml, xpath)
xml_nodes_to_lints(
bad_expr,
source_expression = source_expression,
lint_message = lint_message,
type = "style"
)
})
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.