Skip to content

Commit 9d7fc9b

Browse files
New expect_true_false_linter (#947)
* New expect_true_false_linter * fix lint in test suite * customize message to observed usage
1 parent 463cd86 commit 9d7fc9b

13 files changed

+101
-7
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Collate:
6767
'expect_not_linter.R'
6868
'expect_null_linter.R'
6969
'expect_s3_class_linter.R'
70+
'expect_true_false_linter.R'
7071
'expect_type_linter.R'
7172
'extract.R'
7273
'extraction_operator_linter.R'

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export(expect_not_linter)
3535
export(expect_null_linter)
3636
export(expect_s3_class_linter)
3737
export(expect_s4_class_linter)
38+
export(expect_true_false_linter)
3839
export(expect_type_linter)
3940
export(extraction_operator_linter)
4041
export(function_left_parentheses_linter)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ function calls. (#850, #851, @renkun-ken)
9292
+ `expect_s3_class_linter()` Require usage of `expect_s3_class(x, k)` over `expect_equal(class(x), k)` and similar
9393
+ `expect_s4_class_linter()` Require usage of `expect_s4_class(x, k)` over `expect_true(methods::is(x, k))`
9494
+ `expect_not_linter()` Require usage of `expect_false(x)` over `expect_true(!x)`, and _vice versa_.
95+
+ `expect_true_false_linter()` Require usage of `expect_true(x)` over `expect_equal(x, TRUE)` and similar
9596
* `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)
9697

9798
# lintr 2.0.1

R/expect_true_false_linter.R

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#' Require usage of expect_true(x) over expect_equal(x, TRUE)
2+
#'
3+
#' [testthat::expect_true()] and [testthat::expect_false()] exist specifically
4+
#' for testing the `TRUE`/`FALSE` value of an object.
5+
#' [testthat::expect_equal()] and [testthat::expect_identical()] can also be
6+
#' used for such tests, but it is better to use the tailored function instead.
7+
#'
8+
#' @evalRd rd_tags("expect_true_false_linter")
9+
#' @seealso [linters] for a complete list of linters available in lintr.
10+
#' @export
11+
expect_true_false_linter <- function() {
12+
Linter(function(source_file) {
13+
if (length(source_file$parsed_content) == 0L) {
14+
return(list())
15+
}
16+
17+
xml <- source_file$xml_parsed_content
18+
19+
xpath <- "//expr[expr[
20+
SYMBOL_FUNCTION_CALL[text() = 'expect_equal' or text() = 'expect_identical']
21+
and following-sibling::expr[position() <= 2 and NUM_CONST[text() = 'TRUE' or text() = 'FALSE']]
22+
]]"
23+
24+
bad_expr <- xml2::xml_find_all(xml, xpath)
25+
return(lapply(bad_expr, gen_expect_true_false_lint, source_file))
26+
})
27+
}
28+
29+
gen_expect_true_false_lint <- function(expr, source_file) {
30+
# NB: use expr/$node, not expr[$node], to exclude other things (especially ns:: parts of the call)
31+
call_name <- xml2::xml_text(xml2::xml_find_first(expr, "expr/SYMBOL_FUNCTION_CALL[starts-with(text(), 'expect_')]"))
32+
truth_value <- xml2::xml_text(xml2::xml_find_first(expr, "expr/NUM_CONST[text() = 'TRUE' or text() = 'FALSE']"))
33+
if (truth_value == "TRUE") {
34+
lint_msg <- sprintf("expect_true(x) is better than %s(x, TRUE)", call_name)
35+
} else {
36+
lint_msg <- sprintf("expect_false(x) is better than %s(x, FALSE)", call_name)
37+
}
38+
xml_nodes_to_lint(expr, source_file, lint_msg, type = "warning")
39+
}

inst/lintr/linters.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ expect_not_linter,package_development best_practices readability
1313
expect_null_linter,package_development best_practices
1414
expect_s3_class_linter,package_development best_practices
1515
expect_s4_class_linter,package_development best_practices
16+
expect_true_false_linter,package_development best_practices readability
1617
expect_type_linter,package_development best_practices
1718
extraction_operator_linter,style best_practices
1819
function_left_parentheses_linter,style readability default

man/best_practices_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/expect_not_linter.Rd

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/expect_true_false_linter.Rd

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

man/package_development_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/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.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
test_that("expect_true_false_linter skips allowed usages", {
2+
# expect_true is a scalar test; testing logical vectors with expect_equal is OK
3+
expect_lint("expect_equal(x, c(TRUE, FALSE))", NULL, expect_true_false_linter())
4+
})
5+
6+
test_that("expect_true_false_linter blocks simple disallowed usages", {
7+
expect_lint(
8+
"expect_equal(foo(x), TRUE)",
9+
rex::rex("expect_true(x) is better than expect_equal(x, TRUE)"),
10+
expect_true_false_linter()
11+
)
12+
13+
# expect_identical is treated the same as expect_equal
14+
expect_lint(
15+
"testthat::expect_identical(x, FALSE)",
16+
rex::rex("expect_false(x) is better than expect_identical(x, FALSE)"),
17+
expect_true_false_linter()
18+
)
19+
20+
# also caught when TRUE/FALSE is the first argument
21+
expect_lint(
22+
"expect_equal(TRUE, foo(x))",
23+
rex::rex("expect_true(x) is better than expect_equal(x, TRUE)"),
24+
expect_true_false_linter()
25+
)
26+
})

tests/testthat/test-settings.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@ test_that("logical_env utility works as intended", {
9292
on.exit(if (is.na(old)) Sys.unsetenv(test_env) else sym_set_env(test_env, old))
9393

9494
sym_set_env(test_env, "true")
95-
expect_equal(lintr:::logical_env(test_env), TRUE)
95+
expect_true(lintr:::logical_env(test_env))
9696

9797
sym_set_env(test_env, "F")
98-
expect_equal(lintr:::logical_env(test_env), FALSE)
98+
expect_false(lintr:::logical_env(test_env))
9999

100100
sym_set_env(test_env, "")
101101
expect_null(lintr:::logical_env(test_env))

0 commit comments

Comments
 (0)