Skip to content

Commit

Permalink
New expect_length_linter (#950)
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelChirico authored Mar 16, 2022
1 parent 9d7fc9b commit 878dacf
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 3 deletions.
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Collate:
'duplicate_argument_linter.R'
'equals_na_linter.R'
'exclude.R'
'expect_length_linter.R'
'expect_lint.R'
'expect_not_linter.R'
'expect_null_linter.R'
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export(default_undesirable_functions)
export(default_undesirable_operators)
export(duplicate_argument_linter)
export(equals_na_linter)
export(expect_length_linter)
export(expect_lint)
export(expect_lint_free)
export(expect_not_linter)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ function calls. (#850, #851, @renkun-ken)
+ `expect_s4_class_linter()` Require usage of `expect_s4_class(x, k)` over `expect_true(methods::is(x, k))`
+ `expect_not_linter()` Require usage of `expect_false(x)` over `expect_true(!x)`, and _vice versa_.
+ `expect_true_false_linter()` Require usage of `expect_true(x)` over `expect_equal(x, TRUE)` and similar
* `expect_length_linter()` Require usage of `expect_length(x, n)` over `expect_equal(length(x), n)` and similar
* `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)

# lintr 2.0.1
Expand Down
36 changes: 36 additions & 0 deletions R/expect_length_linter.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#' Require usage of expect_length(x, n) over expect_equal(length(x), n)
#'
#' [testthat::expect_length()] exists specifically for testing the [length()] of
#' an object. [testthat::expect_equal()] can also be used for such tests,
#' but it is better to use the tailored function instead.
#'
#' @evalRd rd_tags("expect_length_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
expect_length_linter <- function() {
Linter(function(source_file) {
if (length(source_file$parsed_content) == 0L) {
return(list())
}

xml <- source_file$xml_parsed_content

# TODO(michaelchirico): also catch expect_true(length(x) == 1)
xpath <- sprintf("//expr[
SYMBOL_FUNCTION_CALL[text() = 'expect_equal' or text() = 'expect_identical']
and following-sibling::expr[
expr[SYMBOL_FUNCTION_CALL[text() = 'length']]
and (position() = 1 or preceding-sibling::expr[NUM_CONST])
]
]")

bad_expr <- xml2::xml_find_all(xml, xpath)
return(lapply(bad_expr, gen_expect_length_lint, source_file))
})
}

gen_expect_length_lint <- function(expr, source_file) {
matched_function <- xml2::xml_text(xml2::xml_find_first(expr, "SYMBOL_FUNCTION_CALL"))
lint_msg <- sprintf("expect_length(x, n) is better than %s(length(x), n)", matched_function)
xml_nodes_to_lint(expr, source_file, lint_msg, type = "warning")
}
1 change: 1 addition & 0 deletions inst/lintr/linters.csv
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ commented_code_linter,style readability best_practices default
cyclocomp_linter,style readability best_practices default configurable
duplicate_argument_linter,correctness common_mistakes configurable
equals_na_linter,robustness correctness common_mistakes default
expect_length_linter,package_development best_practices readability
expect_not_linter,package_development best_practices readability
expect_null_linter,package_development best_practices
expect_s3_class_linter,package_development best_practices
Expand Down
1 change: 1 addition & 0 deletions man/best_practices_linters.Rd

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

19 changes: 19 additions & 0 deletions man/expect_length_linter.Rd

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

7 changes: 4 additions & 3 deletions man/linters.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/package_development_linters.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/readability_linters.Rd

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

44 changes: 44 additions & 0 deletions tests/testthat/test-expect_length_linter.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
test_that("expect_length_linter skips allowed usages", {
expect_lint("expect_equal(nrow(x), 4L)", NULL, expect_length_linter())
# NB: also applies to tinytest, but it's sufficient to test testthat
expect_lint("testthat::expect_equal(nrow(x), 4L)", NULL, expect_length_linter())

# only check the first argument. yoda tests in the second argument will be
# missed, but there are legitimate uses of length() in argument 2
expect_lint("expect_equal(nrow(x), length(y))", NULL, expect_length_linter())
})

test_that("expect_length_linter blocks simple disallowed usages", {
expect_lint(
"expect_equal(length(x), 2L)",
rex::rex("expect_length(x, n) is better than expect_equal(length(x), n)"),
expect_length_linter()
)

expect_lint(
"testthat::expect_equal(length(DF), length(old))",
rex::rex("expect_length(x, n) is better than expect_equal(length(x), n)"),
expect_length_linter()
)

# yoda test cases
expect_lint(
"expect_equal(2, length(x))",
rex::rex("expect_length(x, n) is better than expect_equal(length(x), n)"),
expect_length_linter()
)

expect_lint(
"expect_equal(2L, length(x))",
rex::rex("expect_length(x, n) is better than expect_equal(length(x), n)"),
expect_length_linter()
)
})

test_that("expect_length_linter blocks expect_identical usage as well", {
expect_lint(
"expect_identical(length(x), 2L)",
rex::rex("expect_length(x, n) is better than expect_identical(length(x), n)"),
expect_length_linter()
)
})

0 comments on commit 878dacf

Please sign in to comment.