Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Commas trailing #2104

Merged
merged 11 commits into from
Sep 5, 2023
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
* `keyword_quote_linter()` for finding unnecessary or discouraged quoting of symbols in assignment, function arguments, or extraction (part of #884, @MichaelChirico). Quoting is unnecessary when the target is a valid R name, e.g. `c("a" = 1)` can be `c(a = 1)`. The same goes to assignment (`"a" <- 1`) and extraction (`x$"a"`). Where quoting is necessary, the linter encourages doing so with backticks (e.g. `` x$`a b` `` instead of `x$"a b"`).
* `length_levels_linter()` for using the specific function `nlevels()` instead of checking `length(levels(x))` (part of #884, @MichaelChirico).
* `if_not_else_linter()` for encouraging `if` statements to be structured as `if (A) x else y` instead of `if (!A) y else x` (part of #884, @MichaelChirico).
* `commas_linter()` add parameter `allow_trailing_comma` to allow trailing commas while indexing.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest to use the wording "gains an option", see the bullet regarding fixed_regex_linter().

And please reference the issue number and give yourself credit for it.


## Changes to defaults

Expand Down
21 changes: 19 additions & 2 deletions R/commas_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
#'
#' Check that all commas are followed by spaces, but do not have spaces before them.
#'
#' @param allow_trailing_comma If `TRUE`, the linter allows a comma to be followed
#' directly by a closing bracket without a space.
#'
#' @examples
#' # will produce lints
#' lint(
Expand All @@ -19,6 +22,11 @@
#' linters = commas_linter()
#' )
#'
#' lint(
#' text = "x[1,]",
#' linters = commas_linter()
#' )
#'
#' # okay
#' lint(
#' text = "switch(op, x = foo, y = bar)",
Expand All @@ -40,12 +48,17 @@
#' linters = commas_linter()
#' )
#'
#' lint(
#' text = "x[1,]",
#' linters = commas_linter(allow_trailing_comma = TRUE)
#' )
#'
#' @evalRd rd_tags("commas_linter")
#' @seealso
#' - [linters] for a complete list of linters available in lintr.
#' - <https://style.tidyverse.org/syntax.html#commas>
#' @export
commas_linter <- function() {
commas_linter <- function(allow_trailing_comma = FALSE) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allow_trailing seems more concise to me since comma is already part of the linter name.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's also more consistent with missing_argument_linter()

# conditions are in carefully-chosen order for performance --
# an expression like c(a,b,c,....) with many elements can have
# a huge number of preceding-siblings and the performance of
Expand All @@ -58,7 +71,11 @@ commas_linter <- function() {
@line1 = preceding-sibling::*[1]/@line1 and
not(preceding-sibling::*[1][self::OP-COMMA or self::EQ_SUB])
]"
xpath_after <- "//OP-COMMA[@line1 = following-sibling::*[1]/@line1 and @col1 = following-sibling::*[1]/@col1 - 1]"
xpath_after <- paste0(
"//OP-COMMA[@line1 = following-sibling::*[1]/@line1 and @col1 = following-sibling::*[1]/@col1 - 1",
if (allow_trailing_comma) " and not(following-sibling::*[1]/self::OP-RIGHT-BRACKET)",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also allow RBB (a[[1,]]) and OP-RIGHT-PAREN (a(1,)) if the argument is set?

"]"
)

Linter(function(source_expression) {
if (!is_lint_level(source_expression, "expression")) {
Expand Down
2 changes: 1 addition & 1 deletion inst/lintr/linters.csv
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ boolean_arithmetic_linter,efficiency best_practices readability
brace_linter,style readability default configurable
class_equals_linter,best_practices robustness consistency
closed_curly_linter,style readability deprecated configurable
commas_linter,style readability default
commas_linter,style readability default configurable
commented_code_linter,style readability best_practices default
condition_message_linter,best_practices consistency
conjunct_test_linter,package_development best_practices readability configurable pkg_testthat
Expand Down
18 changes: 16 additions & 2 deletions man/commas_linter.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/configurable_linters.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions man/linters.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 54 additions & 1 deletion tests/testthat/test-commas_linter.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
test_that("returns the correct linting", {
test_that("returns the correct linting (with default parameters)", {
linter <- commas_linter()
msg_after <- rex::rex("Commas should always have a space after.")
msg_before <- rex::rex("Commas should never have a space before.")
Expand All @@ -13,6 +13,59 @@ test_that("returns the correct linting", {
expect_lint("fun(1\n,1)", msg_after, linter)
expect_lint("fun(1,1)", msg_after, linter)
expect_lint("\nfun(1,1)", msg_after, linter)
expect_lint("a(1,)", msg_after, linter)
expect_lint("a[1,]", msg_after, linter)
expect_lint(
"fun(1 ,1)",
list(
msg_before,
msg_after
),
linter
)

expect_lint("\"fun(1 ,1)\"", NULL, linter)
expect_lint("a[1, , 2]", NULL, linter)
expect_lint("a[1, , 2, , 3]", NULL, linter)

expect_lint("switch(op, x = foo, y = bar)", NULL, linter)
expect_lint("switch(op, x = , y = bar)", NULL, linter)
expect_lint("switch(op, \"x\" = , y = bar)", NULL, linter)
expect_lint("switch(op, x = ,\ny = bar)", NULL, linter)

expect_lint("switch(op, x = foo , y = bar)", msg_before, linter)
expect_lint("switch(op, x = foo , y = bar)", msg_before, linter)
expect_lint("switch(op , x = foo, y = bar)", msg_before, linter)
expect_lint("switch(op, x = foo, y = bar(a = 4 , b = 5))", msg_before, linter)
expect_lint("fun(op, x = foo , y = switch(bar, a = 4, b = 5))", msg_before, linter)

expect_lint(
"fun(op ,bar)",
list(
list(message = msg_before, column_number = 7L, ranges = list(c(7L, 10L))),
list(message = msg_after, column_number = 12L, ranges = list(c(12L, 12L)))
),
linter
)
})

test_that("returns the correct linting (with 'allow_trailing_comma' set)", {
linter <- commas_linter(allow_trailing_comma = TRUE)
msg_after <- rex::rex("Commas should always have a space after.")
msg_before <- rex::rex("Commas should never have a space before.")

expect_lint("blah", NULL, linter)
expect_lint("fun(1, 1)", NULL, linter)
expect_lint("fun(1,\n 1)", NULL, linter)
expect_lint("fun(1,\n1)", NULL, linter)
expect_lint("fun(1\n,\n1)", NULL, linter)
expect_lint("fun(1\n ,\n1)", NULL, linter)
expect_lint("a[1,]", NULL, linter)

expect_lint("fun(1\n,1)", msg_after, linter)
expect_lint("fun(1,1)", msg_after, linter)
expect_lint("\nfun(1,1)", msg_after, linter)
expect_lint("a(1,)", msg_after, linter)
expect_lint(
"fun(1 ,1)",
list(
Expand Down