Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add library call linter #2027

Merged
merged 13 commits into from
Aug 3, 2023
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ Collate:
'is_lint_level.R'
'is_numeric_linter.R'
'lengths_linter.R'
'library_call_linter.R'
'line_length_linter.R'
'lint.R'
'linter_tag_docs.R'
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export(inner_combine_linter)
export(is_lint_level)
export(is_numeric_linter)
export(lengths_linter)
export(library_call_linter)
export(line_length_linter)
export(lint)
export(lint_dir)
Expand Down
60 changes: 60 additions & 0 deletions R/library_call_linter.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#' Library call linter
#'
#' Force library calls to all be at the top of the script.
#'
#' @examples
#' # will produce lints
#' lint(
#' text = c("library(dplyr)", "print('test')", "library(tidyr)"),
AshesITR marked this conversation as resolved.
Show resolved Hide resolved
#' linters = library_call_linter()
#' )
#'
#' lint(
#' text = c("library(dplyr)", "print('test')", "library(tidyr)", "library(purrr)"),
#' linters = library_call_linter()
#' )
#'
#' # okay
#' lint(
#' text = c("library(dplyr)", "print('test')"),
#' linters = library_call_linter()
#' )
#'
#' lint(
#' text = c("# comment", "library(dplyr)"),
#' linters = library_call_linter()
#' )
#'
#' @evalRd rd_tags("library_call_linter")
#' @seealso [linters] for a complete list of linters available in lintr.
#' @export
library_call_linter <- function() {

xpath <- "
(//SYMBOL_FUNCTION_CALL[text() = 'library'])[last()]
AshesITR marked this conversation as resolved.
Show resolved Hide resolved
//preceding::expr
AshesITR marked this conversation as resolved.
Show resolved Hide resolved
//SYMBOL_FUNCTION_CALL[text() != 'library'][last()]
//following::expr[SYMBOL_FUNCTION_CALL[text() = 'library']]
"

Linter(function(source_expression) {
if (!is_lint_level(source_expression, "file")) {
return(list())
}

xml <- source_expression$full_xml_parsed_content

bad_expr <- xml2::xml_find_all(xml, xpath)

if (length(bad_expr) == 0L) {
return(list())
}

xml_nodes_to_lints(
bad_expr,
source_expression = source_expression,
lint_message = "Move all library calls to the top of the script.",
type = "warning"
)
})
}
1 change: 1 addition & 0 deletions inst/lintr/linters.csv
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ infix_spaces_linter,style readability default configurable
inner_combine_linter,efficiency consistency readability
is_numeric_linter,readability best_practices consistency
lengths_linter,efficiency readability best_practices
library_call_linter,style best_practices
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this counts as readability to me too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is updated.

line_length_linter,style readability default configurable
literal_coercion_linter,best_practices consistency efficiency
matrix_apply_linter,readability efficiency
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.

41 changes: 41 additions & 0 deletions man/library_call_linter.Rd

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

5 changes: 3 additions & 2 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/style_linters.Rd

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

51 changes: 51 additions & 0 deletions tests/testthat/test-library_call_linter.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
test_that("library_call_linter skips allowed usages", {

expect_lint(c("library(dplyr)", "print('test')"),
AshesITR marked this conversation as resolved.
Show resolved Hide resolved
NULL,
library_call_linter()
)

expect_lint("print('test')",
NULL,
library_call_linter()
)

expect_lint(c("# comment", "library(dplyr)"),
NULL,
library_call_linter()
)

expect_lint(c("print('test')", "# library(dplyr)"),
NULL,
library_call_linter()
)
})

test_that("library_call_linter warns on disallowed usages", {
AshesITR marked this conversation as resolved.
Show resolved Hide resolved
lint_message <- rex::rex("Move all library calls to the top of the script.")

expect_lint(c("library(dplyr)", "print('test')", "library(tidyr)"),
lint_message,
library_call_linter()
)

expect_lint(c("library(dplyr)", "print('test')", "library(tidyr)", "library(purrr)"),
list(lint_message, lint_message),
library_call_linter()
)

expect_lint(c("library(dplyr)", "print('test')", "print('test')", "library(tidyr)"),
rex("Move all library calls to the top of the script."),
library_call_linter()
)

expect_lint(c("library(dplyr)", "print('test')", "library(tidyr)", "print('test')"),
lint_message,
library_call_linter()
)

expect_lint(c("library(dplyr)", "print('test')", "library(tidyr)", "print('test')", "library(tidyr)"),
list(lint_message, lint_message),
library_call_linter()
)
})
Loading