Skip to content

Auto-detect imported objects from packages in object_usage_linter() #1136

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

Merged
merged 2 commits into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ function calls. (#850, #851, @renkun-ken)
* `trailing_whitespace_linter()` ignores trailing whitespace in strings by default.
This can be disabled using `allow_in_strings = FALSE` (#1045, @AshesITR)
* Moved the default lintr cache directory from `~/.R/lintr_cache` to `R_user_dir("lintr", "cache")`. Note that this major version update invalidated the old cache anyway, so it can be safely deleted. (#1062, @AshesITR)
* `object_usage_linter()` now detects functions exported by packages that are explicitly attached using `library()` or `require()` calls (#1127, @AshesITR)

# lintr 2.0.1

Expand Down
37 changes: 34 additions & 3 deletions R/object_usage_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ object_usage_linter <- function(interpret_glue = TRUE) {
# If there is no xml data just return
if (is.null(source_expression$full_xml_parsed_content)) return(list())

source_expression$parsed_content <- source_expression$full_parsed_content

pkg_name <- pkg_name(find_package(dirname(source_expression$filename)))
if (!is.null(pkg_name)) {
parent_env <- try_silently(getNamespace(pkg_name))
Expand All @@ -27,7 +25,10 @@ object_usage_linter <- function(interpret_glue = TRUE) {

declared_globals <- try_silently(utils::globalVariables(package = pkg_name %||% globalenv()))

symbols <- get_assignment_symbols(source_expression$full_xml_parsed_content)
symbols <- c(
get_assignment_symbols(source_expression$full_xml_parsed_content),
get_imported_symbols(source_expression$full_xml_parsed_content)
)

# Just assign them an empty function
for (symbol in symbols) {
Expand Down Expand Up @@ -275,3 +276,33 @@ parse_check_usage <- function(expression, known_used_symbols = character()) {

res
}

get_imported_symbols <- function(xml) {
import_exprs <- xml2::xml_find_all(
xml,
"//expr[
expr[SYMBOL_FUNCTION_CALL[text() = 'library' or text() = 'require']]
and
(
not(SYMBOL_SUB[
text() = 'character.only' and
following-sibling::expr[1][NUM_CONST[text() = 'TRUE'] or SYMBOL[text() = 'T']]
]) or
expr[2][STR_CONST]
)
]/expr[STR_CONST|SYMBOL][1]"
)
if (length(import_exprs) == 0L) {
return(character())
}
imported_pkgs <- get_r_string(import_exprs)

unlist(lapply(imported_pkgs, function(pkg) {
tryCatch(
getNamespaceExports(pkg),
error = function(e) {
character()
}
)
}))
}
27 changes: 27 additions & 0 deletions tests/testthat/test-object_usage_linter.R
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,30 @@ test_that("definitions below top level are ignored (for now)", {
object_usage_linter()
)
})

# reported as #1127
test_that("package imports are detected if present in file", {
expect_lint(
trim_some("
dog <- function() {
a <- iris %>% summarise(m = 42)
a
}
"),
rex::rex("no visible global function definition for ", anything, "summarise"),
object_usage_linter()
)

expect_lint(
trim_some("
library(dplyr)

dog <- function() {
a <- iris %>% summarise(m = 42)
a
}
"),
NULL,
object_usage_linter()
)
})