Skip to content

Commit 020c4b6

Browse files
Catch consecutive calls to assert_that in renamed consecutive_asserion_linter (#1940)
* Catch consecutive calls to assert_that in renamed consecutive_assertion_linter * new examples * remove dated comment * [style] better parallelism for similar xpath * fix lints * use local variables in tests
1 parent ddc1b71 commit 020c4b6

18 files changed

+259
-147
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ Collate:
7575
'comments.R'
7676
'condition_message_linter.R'
7777
'conjunct_test_linter.R'
78-
'consecutive_stopifnot_linter.R'
78+
'consecutive_assertion_linter.R'
7979
'cyclocomp_linter.R'
8080
'declared_functions.R'
8181
'deprecated.R'

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export(commas_linter)
3838
export(commented_code_linter)
3939
export(condition_message_linter)
4040
export(conjunct_test_linter)
41+
export(consecutive_assertion_linter)
4142
export(consecutive_stopifnot_linter)
4243
export(cyclocomp_linter)
4344
export(default_linters)

NEWS.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
* `single_quotes_linter()` is deprecated in favor of the more generalizable `quotes_linter()` (#1729, @MichaelChirico).
66
* `unneeded_concatentation_linter()` is deprecated in favor of `unnecessary_concatenation_linter()` for naming consistency (#1707, @IndrajeetPatil).
7+
* `consecutive_stopifnot_linter()` is deprecated in favor of the more general (see below) `consecutive_assertion_linter()` (#1604, @MichaelChirico).
78

89
## Bug fixes
910

@@ -95,8 +96,7 @@
9596

9697
* `infix_spaces_linter()` supports the native R pipe `|>` (#1793, @AshesITR)
9798

98-
* `unneeded_concatenation_linter()` no longer lints on `c(...)` (i.e., passing `...` in a function call)
99-
when `allow_single_expression = FALSE` (#1696, @MichaelChirico)
99+
* `unnecessary_concatenation_linter()` (f.k.a. `unneeded_concatenation_linter()`) no longer lints on `c(...)` (i.e., passing `...` in a function call) when `allow_single_expression = FALSE` (#1696, @MichaelChirico)
100100

101101
* `object_name_linter()` gains parameter `regexes` to allow custom naming conventions (#822, #1421, @AshesITR)
102102

@@ -148,6 +148,8 @@
148148

149149
* `unnecessary_concatenation_linter()` is simply `unneeded_concatenation_linter()`, renamed.
150150

151+
* `consecutive_assertion_linter()` (f.k.a. `consecutive_stopifnot_linter()`) now lints for consecutive calls to `assertthat::assert_that()` (as long as the `msg=` argument is not used; #1604, @MichaelChirico).
152+
151153
## Notes
152154

153155
* {lintr} now depends on R version 3.5.0, in line with the tidyverse policy for R version compatibility.

R/consecutive_assertion_linter.R

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#' Force consecutive calls to assertions into just one when possible
2+
#'
3+
#' [stopifnot()] accepts any number of tests, so sequences like
4+
#' `stopifnot(x); stopifnot(y)` are redundant. Ditto for tests using
5+
#' `assertthat::assert_that()` without specifying `msg=`.
6+
#'
7+
#' @examples
8+
#' # will produce lints
9+
#' lint(
10+
#' text = "stopifnot(x); stopifnot(y)",
11+
#' linters = consecutive_assertion_linter()
12+
#' )
13+
#'
14+
#' lint(
15+
#' text = "assert_that(x); assert_that(y)",
16+
#' linters = consecutive_assertion_linter()
17+
#' )
18+
#'
19+
#' # okay
20+
#' lint(
21+
#' text = "stopifnot(x, y)",
22+
#' linters = consecutive_assertion_linter()
23+
#' )
24+
#'
25+
#' lint(
26+
#' text = 'assert_that(x, msg = "Bad x!"); assert_that(y)',
27+
#' linters = consecutive_assertion_linter()
28+
#' )
29+
#'
30+
#' @evalRd rd_tags("consecutive_assertion_linter")
31+
#' @seealso [linters] for a complete list of linters available in lintr.
32+
#' @export
33+
consecutive_assertion_linter <- function() {
34+
xpath <- "
35+
//SYMBOL_FUNCTION_CALL[text() = 'stopifnot']
36+
/parent::expr
37+
/parent::expr[
38+
expr[1]/SYMBOL_FUNCTION_CALL = following-sibling::expr[1]/expr[1]/SYMBOL_FUNCTION_CALL
39+
]
40+
|
41+
//SYMBOL_FUNCTION_CALL[text() = 'assert_that']
42+
/parent::expr
43+
/parent::expr[
44+
not(SYMBOL_SUB[text() = 'msg'])
45+
and not(following-sibling::expr[1]/SYMBOL_SUB[text() = 'msg'])
46+
and expr[1]/SYMBOL_FUNCTION_CALL = following-sibling::expr[1]/expr[1]/SYMBOL_FUNCTION_CALL
47+
]
48+
"
49+
50+
Linter(function(source_expression) {
51+
# need the full file to also catch usages at the top level
52+
if (!is_lint_level(source_expression, "file")) {
53+
return(list())
54+
}
55+
56+
xml <- source_expression$full_xml_parsed_content
57+
58+
bad_expr <- xml2::xml_find_all(xml, xpath)
59+
60+
matched_function <- xp_call_name(bad_expr)
61+
xml_nodes_to_lints(
62+
bad_expr,
63+
source_expression,
64+
lint_message = sprintf("Unify consecutive calls to %s().", matched_function),
65+
type = "warning"
66+
)
67+
})
68+
}

R/consecutive_stopifnot_linter.R

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

R/lintr-deprecated.R

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,16 @@ single_quotes_linter <- function() {
226226
)
227227
quotes_linter()
228228
}
229+
230+
#' Consecutive stopifnot linter
231+
#' @rdname lintr-deprecated
232+
#' @export
233+
consecutive_stopifnot_linter <- function() {
234+
lintr_deprecated(
235+
old = "consecutive_stopifnot_linter",
236+
new = "consecutive_assertion_linter",
237+
version = "3.1.0",
238+
type = "Linter"
239+
)
240+
consecutive_assertion_linter()
241+
}

R/utils.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ get_r_code <- function(xml) {
281281
#'
282282
#' [xml2::xml_text()] is deceptively close to obviating this helper, but it collapses
283283
#' text across lines. R is _mostly_ whitespace-agnostic, so this only matters in some edge cases,
284-
#' in particular when there are comments within an expression (<expr> node). See #1919.
284+
#' in particular when there are comments within an expression (`<expr>` node). See #1919.
285285
#'
286286
#' @noRd
287287
xml2lang <- function(x) {

inst/lintr/linters.csv

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ commas_linter,style readability default
1212
commented_code_linter,style readability best_practices default
1313
condition_message_linter,best_practices consistency
1414
conjunct_test_linter,package_development best_practices readability configurable
15-
consecutive_stopifnot_linter,style readability consistency
15+
consecutive_assertion_linter,style readability consistency
16+
consecutive_stopifnot_linter,style readability consistency deprecated
1617
cyclocomp_linter,style readability best_practices default configurable
1718
duplicate_argument_linter,correctness common_mistakes configurable
1819
empty_assignment_linter,readability best_practices

man/consecutive_assertion_linter.Rd

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

man/consecutive_stopifnot_linter.Rd

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

0 commit comments

Comments
 (0)