Skip to content

Commit e80cc8c

Browse files
delete if_else_match_braces_linter and merge it into brace_linter (#1095)
* delete if_else_match_braces_linter and merge it into brace_linter * deprecate open_curly_linter and merge it into brace_linter (#1096) * deprecate open_curly_linter - remove open_curly_linter from defaults - refactor to XPath based approach - no longer lint trailing whitespace (there's a separate linter for that) * merge paren_brace_linter into brace_linter (#1097) * deprecate paren_brace_linter - remove paren_brace_linter from defaults - extend to else{ and repeat{ * `code` Co-authored-by: Michael Chirico <michaelchirico4@gmail.com> * add explicit test for different behaviour compared to closed_curly_linter Co-authored-by: Michael Chirico <michaelchirico4@gmail.com> Co-authored-by: Michael Chirico <michaelchirico4@gmail.com>
1 parent 7799cc0 commit e80cc8c

22 files changed

+301
-189
lines changed

DESCRIPTION

-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ Collate:
8585
'function_left_parentheses.R'
8686
'get_source_expressions.R'
8787
'ids_with_token.R'
88-
'if_else_match_braces_linter.R'
8988
'ifelse_censor_linter.R'
9089
'implicit_integer_linter.R'
9190
'infix_spaces_linter.R'

NAMESPACE

-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ export(extraction_operator_linter)
5252
export(function_left_parentheses_linter)
5353
export(get_source_expressions)
5454
export(ids_with_token)
55-
export(if_else_match_braces_linter)
5655
export(ifelse_censor_linter)
5756
export(implicit_integer_linter)
5857
export(infix_spaces_linter)

NEWS.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
* Rename `semicolon_terminator_linter` to `semicolon_linter` for better consistency. `semicolon_terminator_linter` survives but is marked for deprecation. The new linter also has a new signature, taking arguments `allow_compound` and `allow_trailing` to replace the old single argument `semicolon=`, again for signature consistency with other linters.
1212
* Combined several curly brace related linters into a new `brace_linter` (#1041, @AshesITR):
1313
+ `closed_curly_linter()`
14+
+ `open_curly_linter()`, no longer linting unnecessary trailing whitespace
15+
+ `paren_brace_linter()`, also linting `if`/`else` and `repeat` with missing whitespace
1416
+ Require `else` to come on the same line as the preceding `}`, if present (#884, @michaelchirico)
1517
+ Require functions spanning multiple lines to use curly braces (@michaelchirico)
18+
+ Require balanced usage of `{}` in `if`/`else` conditions (@michaelchirico)
1619
* The `...` arguments for `lint()`, `lint_dir()`, and `lint_package()` have promoted to an earlier position to better match the [Tidyverse design principal](https://design.tidyverse.org/args-data-details.html) of data->descriptor->details. This change enables passing objects to `...` without needing to specify non-required arguments, e.g. `lint_dir("/path/to/dir", linter())` now works without the need to specify `relative_path`. This affects some code that uses positional arguments. (#935, @michaelchirico)
1720
+ For `lint()`, `...` is now the 3rd argument, where earlier this was `cache=`
1821
+ For `lint_dir()` and `lint_package()`, `...` is now the 2nd argument, where earlier this was `relative_path=`
@@ -118,7 +121,6 @@ function calls. (#850, #851, @renkun-ken)
118121
* `yoda_test_linter()` Require usage of `expect_identical(x, 1L)` over `expect_equal(1L, x)` and similar
119122
* `expect_identical_linter()` Require usage of `expect_identical()` by default, and `expect_equal()` only by exception
120123
* `expect_comparison_linter()` Require usage of `expect_gt(x, y)` over `expect_true(x > y)` and similar
121-
* `if_else_match_braces_linter()` Require balanced usage of `{}` in `if`/`else` conditions
122124
* `vector_logic_linter()` Require use of scalar logical operators (`&&` and `||`) inside `if()` conditions and similar
123125
* `any_is_na_linter()` Require usage of `anyNA(x)` over `any(is.na(x))`
124126
* `class_equals_linter()` Prevent comparing `class(x)` with `==`, `!=`, or `%in%`, where `inherits()` is typically preferred
@@ -132,7 +134,10 @@ function calls. (#850, #851, @renkun-ken)
132134
* `nested_ifelse_linter()` Prevent nested calls to `ifelse()` like `ifelse(A, x, ifelse(B, y, z))`, and similar
133135
* `condition_message_linter` Prevent condition messages from being constructed like `stop(paste(...))` (where just `stop(...)` is preferable)
134136
* `redundant_ifelse_linter()` Prevent usage like `ifelse(A & B, TRUE, FALSE)` or `ifelse(C, 0, 1)` (the latter is `as.numeric(!C)`)
135-
* `brace_linter()` Require `else` to come on the same line as the preceding `}`, if present
137+
* Extensions to `brace_linter()`
138+
+ Require `else` to come on the same line as the preceding `}`, if present
139+
+ Require balanced usage of `{}` in `if`/`else` conditions
140+
+ Require functions spanning multiple lines to use curly braces
136141
* `unreachable_code_linter()` Prevent code after `return()` and `stop()` statements that will never be reached (extended for #1051 thanks to early user testing, thanks @bersbersbers!)
137142
* `regex_subset_linter()` Require usage of `grep(ptn, x, value = TRUE)` over `x[grep(ptn, x)]` and similar
138143
* `consecutive_stopifnot_linter()` Require consecutive calls to `stopifnot()` to be unified into one

R/brace_linter.R

+78-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@
22
#'
33
#' Perform various style checks related to placement and spacing of curly braces:
44
#'
5-
#' - Curly braces are on their own line unless they are followed by an `else`.
5+
#' - Opening curly braces are never on their own line and are always followed by a newline.
6+
#' - Opening curly braces have a space before them.
7+
#' - Closing curly braces are on their own line unless they are followed by an `else`.
68
#' - Closing curly braces in `if` conditions are on the same line as the corresponding `else`.
9+
#' - Either both or neither branch in `if`/`else` use curly braces, i.e., either both branches use `{...}` or neither
10+
#' does.
711
#' - Functions spanning multiple lines use curly braces.
812
#'
913
#' @param allow_single_line if `TRUE`, allow an open and closed curly pair on the same line.
1014
#'
1115
#' @evalRd rd_tags("brace_linter")
1216
#' @seealso [linters] for a complete list of linters available in lintr. \cr
13-
#' <https://style.tidyverse.org/syntax.html#indenting>
17+
#' <https://style.tidyverse.org/syntax.html#indenting> \cr
18+
#' <https://style.tidyverse.org/syntax.html#if-statements>
1419
#' @export
1520
brace_linter <- function(allow_single_line = FALSE) {
1621
Linter(function(source_expression) {
@@ -20,6 +25,51 @@ brace_linter <- function(allow_single_line = FALSE) {
2025

2126
lints <- list()
2227

28+
xp_cond_open <- xp_and(c(
29+
# matching } is on same line
30+
if (isTRUE(allow_single_line)) {
31+
"(@line1 != following-sibling::OP-LEFT-BRACE/@line1)"
32+
},
33+
# double curly
34+
"not(
35+
(@line1 = parent::expr/preceding-sibling::OP-LEFT-BRACE/@line1) or
36+
(@line1 = following-sibling::expr/OP-LEFT-BRACE/@line1)
37+
)"
38+
))
39+
40+
# TODO (AshesITR): if c_style_braces is TRUE, invert the preceding-sibling condition
41+
xp_open_curly <- glue::glue("//OP-LEFT-BRACE[
42+
{ xp_cond_open } and (
43+
not(@line1 = parent::expr/preceding-sibling::*/@line2) or
44+
@line1 = following-sibling::*[1][not(self::COMMENT)]/@line1
45+
)
46+
]")
47+
48+
lints <- c(lints, lapply(
49+
xml2::xml_find_all(source_expression$xml_parsed_content, xp_open_curly),
50+
xml_nodes_to_lint,
51+
source_file = source_expression,
52+
lint_message = paste(
53+
"Opening curly braces should never go on their own line and",
54+
"should always be followed by a new line."
55+
)
56+
))
57+
58+
xp_open_preceding <- "parent::expr/preceding-sibling::*[1][self::OP-RIGHT-PAREN or self::ELSE or self::REPEAT]"
59+
60+
xp_paren_brace <- glue::glue("//OP-LEFT-BRACE[
61+
@line1 = { xp_open_preceding }/@line1
62+
and
63+
@col1 = { xp_open_preceding }/@col2 + 1
64+
]")
65+
66+
lints <- c(lints, lapply(
67+
xml2::xml_find_all(source_expression$xml_parsed_content, xp_paren_brace),
68+
xml_nodes_to_lint,
69+
source_file = source_expression,
70+
lint_message = "There should be a space before an opening curly brace."
71+
))
72+
2373
xp_cond_closed <- xp_and(c(
2474
# matching { is on same line
2575
if (isTRUE(allow_single_line)) {
@@ -74,6 +124,32 @@ brace_linter <- function(allow_single_line = FALSE) {
74124
lint_message = "Any function spanning multiple lines should use curly braces."
75125
))
76126

127+
# if (x) { ... } else if (y) { ... } else { ... } is OK; fully exact pairing
128+
# of if/else would require this to be
129+
# if (x) { ... } else { if (y) { ... } else { ... } } since there's no
130+
# elif operator/token in R, which is pretty unseemly
131+
xp_if_else_match_brace <- "
132+
//IF[
133+
following-sibling::expr[2][OP-LEFT-BRACE]
134+
and following-sibling::ELSE
135+
/following-sibling::expr[1][not(OP-LEFT-BRACE or IF/following-sibling::expr[2][OP-LEFT-BRACE])]
136+
]
137+
138+
|
139+
140+
//ELSE[
141+
following-sibling::expr[1][OP-LEFT-BRACE]
142+
and preceding-sibling::IF/following-sibling::expr[2][not(OP-LEFT-BRACE)]
143+
]
144+
"
145+
146+
lints <- c(lints, lapply(
147+
xml2::xml_find_all(source_expression$xml_parsed_content, xp_if_else_match_brace),
148+
xml_nodes_to_lint,
149+
source_file = source_expression,
150+
lint_message = "Either both or neither branch in `if`/`else` should use curly braces."
151+
))
152+
77153
lints
78154
})
79155
}

R/if_else_match_braces_linter.R

-48
This file was deleted.

R/open_curly_linter.R

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#' <https://style.tidyverse.org/syntax.html#indenting>
1010
#' @export
1111
open_curly_linter <- function(allow_single_line = FALSE) {
12+
lintr_deprecated("open_curly_linter", new = "brace_linter", version = "2.0.1.9001", type = "Linter")
1213
Linter(function(source_file) {
1314
lapply(
1415
ids_with_token(source_file, "'{'"),

R/paren_brace_linter.R

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#' @seealso [linters] for a complete list of linters available in lintr.
77
#' @export
88
paren_brace_linter <- function() {
9+
lintr_deprecated("paren_brace_linter", new = "brace_linter", version = "2.0.1.9001", type = "Linter")
910
Linter(function(source_file) {
1011
if (is.null(source_file$xml_parsed_content)) {
1112
return(NULL)

R/zzz.R

-3
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,13 @@ default_linters <- with_defaults(
1919
cyclocomp_linter(),
2020
equals_na_linter(),
2121
function_left_parentheses_linter(),
22-
if_else_match_braces_linter(),
2322
infix_spaces_linter(),
2423
line_length_linter(),
2524
no_tab_linter(),
2625
object_length_linter(),
2726
object_name_linter(),
2827
object_usage_linter(),
29-
open_curly_linter(),
3028
paren_body_linter(),
31-
paren_brace_linter(),
3229
pipe_continuation_linter(),
3330
semicolon_linter(),
3431
seq_linter(),

inst/lintr/linters.csv

+2-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ expect_true_false_linter,package_development best_practices readability
2727
expect_type_linter,package_development best_practices
2828
extraction_operator_linter,style best_practices
2929
function_left_parentheses_linter,style readability default
30-
if_else_match_braces_linter,default style readability
3130
ifelse_censor_linter,best_practices efficiency
3231
implicit_integer_linter,style consistency best_practices
3332
infix_spaces_linter,style readability default
@@ -44,11 +43,11 @@ numeric_leading_zero_linter,style consistency readability
4443
object_length_linter,style readability default configurable
4544
object_name_linter,style consistency default configurable
4645
object_usage_linter,style readability correctness default
47-
open_curly_linter,style readability default configurable
46+
open_curly_linter,style readability configurable
4847
outer_negation_linter,readability efficiency best_practices
4948
package_hooks_linter,style correctness package_development
5049
paren_body_linter,style readability default
51-
paren_brace_linter,style readability default
50+
paren_brace_linter,style readability
5251
paste_linter,best_practices consistency
5352
pipe_call_linter,style readability
5453
pipe_continuation_linter,style readability default

man/brace_linter.Rd

+7-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/default_linters.Rd

+1-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/if_else_match_braces_linter.Rd

-20
This file was deleted.

man/linters.Rd

+5-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/open_curly_linter.Rd

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/paren_brace_linter.Rd

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/readability_linters.Rd

-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/style_linters.Rd

-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)