Skip to content

Commit c0899af

Browse files
New consecutive_stopifnot_linter (#1005)
1 parent 3aca22f commit c0899af

11 files changed

+122
-3
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Collate:
6161
'comment_linters.R'
6262
'comments.R'
6363
'conjunct_expectation_linter.R'
64+
'consecutive_stopifnot_linter.R'
6465
'cyclocomp_linter.R'
6566
'declared_functions.R'
6667
'deprecated.R'

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export(closed_curly_linter)
2525
export(commas_linter)
2626
export(commented_code_linter)
2727
export(conjunct_expectation_linter)
28+
export(consecutive_stopifnot_linter)
2829
export(cyclocomp_linter)
2930
export(default_linters)
3031
export(default_settings)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ function calls. (#850, #851, @renkun-ken)
107107
* `paste_sep_linter()` Require usage of `paste0()` over `paste(sep = "")`
108108
* `nested_ifelse_linter()` Prevent nested calls to `ifelse()` like `ifelse(A, x, ifelse(B, y, z))`, and similar
109109
* `unreachable_code_linter()` Prevent code after `return()` and `stop()` statements that will never be reached
110+
* `consecutive_stopifnot_linter()` Require consecutive calls to `stopifnot()` to be unified into one
110111
* `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)
111112
* `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)
112113
* `infix_spaces_linter()` now throws a lint on `a~b` and `function(a=1) {}` (#930, @michaelchirico)

R/consecutive_stopifnot_linter.R

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#' Force consecutive calls to stopifnot into just one when possible
2+
#'
3+
#' [stopifnot()] accepts any number of tests, so sequences like
4+
#' `stopifnot(x); stopifnot(y)` are redundant.
5+
#'
6+
#' @evalRd rd_tags("consecutive_stopifnot_linter")
7+
#' @seealso [linters] for a complete list of linters available in lintr.
8+
#' @export
9+
consecutive_stopifnot_linter <- function() {
10+
Linter(function(source_file) {
11+
# need the full file to also catch usages at the top level
12+
if (length(source_file$full_xml_parsed_content) == 0L) {
13+
return(list())
14+
}
15+
16+
xml <- source_file$full_xml_parsed_content
17+
18+
# match on the expr, not the SYMBOL_FUNCTION_CALL, to ensure
19+
# namespace-qualified calls only match if the namespaces do.
20+
xpath <- glue::glue("
21+
//expr[
22+
expr[SYMBOL_FUNCTION_CALL[text() = 'stopifnot']] = following-sibling::expr[1]/expr
23+
]
24+
")
25+
bad_expr <- xml2::xml_find_all(xml, xpath)
26+
27+
return(lapply(
28+
bad_expr,
29+
xml_nodes_to_lint,
30+
source_file = source_file,
31+
lint_message = "Unify consecutive calls to stopifnot().",
32+
type = "warning",
33+
global = TRUE
34+
))
35+
})
36+
}

inst/lintr/linters.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ closed_curly_linter,style readability default configurable
99
commas_linter,style readability default
1010
commented_code_linter,style readability best_practices default
1111
conjunct_expectation_linter,package_development best_practices readability
12+
consecutive_stopifnot_linter,style readability consistency
1213
cyclocomp_linter,style readability best_practices default configurable
1314
duplicate_argument_linter,correctness common_mistakes configurable
1415
equals_na_linter,robustness correctness common_mistakes default

man/consecutive_stopifnot_linter.Rd

Lines changed: 18 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/linters.Rd

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

man/style_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.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
test_that("consecutive_stopifnot_linter skips allowed usages", {
2+
expect_lint("stopifnot(x)", NULL, consecutive_stopifnot_linter())
3+
expect_lint("stopifnot(x, y, z)", NULL, consecutive_stopifnot_linter())
4+
5+
# intervening expression
6+
expect_lint("stopifnot(x); y; stopifnot(z)", NULL, consecutive_stopifnot_linter())
7+
8+
# inline or potentially with gaps don't matter
9+
lines <- trim_some("
10+
stopifnot(x)
11+
y
12+
13+
stopifnot(z)
14+
")
15+
expect_lint(lines, NULL, consecutive_stopifnot_linter())
16+
})
17+
18+
test_that("consecutive_stopifnot_linter blocks simple disallowed usages", {
19+
# one test of inline usage
20+
expect_lint(
21+
"stopifnot(x); stopifnot(y)",
22+
rex::rex("Unify consecutive calls to stopifnot()."),
23+
consecutive_stopifnot_linter()
24+
)
25+
26+
lines_gap <- trim_some("
27+
stopifnot(x)
28+
29+
stopifnot(y, z)
30+
")
31+
expect_lint(
32+
lines_gap,
33+
rex::rex("Unify consecutive calls to stopifnot()."),
34+
consecutive_stopifnot_linter()
35+
)
36+
37+
lines_consecutive <- trim_some("
38+
stopifnot(x)
39+
stopifnot(y)
40+
")
41+
expect_lint(
42+
lines_consecutive,
43+
rex::rex("Unify consecutive calls to stopifnot()."),
44+
consecutive_stopifnot_linter()
45+
)
46+
47+
lines_comment <- trim_some("
48+
stopifnot(x)
49+
# a comment on y
50+
stopifnot(y)
51+
")
52+
expect_lint(
53+
lines_comment,
54+
rex::rex("Unify consecutive calls to stopifnot()."),
55+
consecutive_stopifnot_linter()
56+
)
57+
})

0 commit comments

Comments
 (0)