Skip to content

Commit

Permalink
Check for namespace::function() calls (#121)
Browse files Browse the repository at this point in the history
* add namespaced_function_calls to default linters
  • Loading branch information
radbasa authored Jul 15, 2024
1 parent b5f6704 commit e998ea5
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 1 deletion.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: box.linters
Title: Linters for 'box' Modules
Version: 0.9.1.9004
Version: 0.9.1.9005
Authors@R:
c(
person("Ricardo Rodrigo", "Basa", role = c("aut", "cre"), email = "[email protected]"),
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export(box_unused_att_pkg_fun_linter)
export(box_unused_attached_mod_linter)
export(box_unused_attached_pkg_linter)
export(box_usage_linter)
export(namespaced_function_calls)
export(r6_usage_linter)
export(rhino_default_linters)
export(style_box_use_dir)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# box.linters (development version)

* Add checks for `package::function()` calls. Allow `box::*()` by default.
* [Bug fix] Allow relative box module paths (#110)
* Less verbose `box_alphabetical_calls_linter()`. Reports only the first out-of-place function.
* Added styling functions for `box::use()` calls.
Expand Down
89 changes: 89 additions & 0 deletions R/namespaced_function_calls.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#' Check that `namespace::function()` calls except for `box::*()` are not made.
#'
#' @param allow Character vector of `namespace` or `namespace::function` to allow in the source
#' code. Take not that the `()` are not included. The `box` namespace will always be allowed
#'
#' @examples
#' # will produce lints
#' code <- "box::use(package)
#' tidyr::pivot_longer()"
#'
#' lintr::lint(text = code, linters = namespaced_function_calls())
#'
#' ## allow `tidyr::pivot_longer()`
#' code <- "box::use(package)
#' tidyr::pivot_longer()
#' tidyr::pivot_wider()"
#'
#' lintr::lint(text = code, linters = namespaced_function_calls(allow = c("tidyr::pivot_longer")))
#'
#' # okay
#' code <- "box::use(package)"
#'
#' lintr::lint(text = code, linters = namespaced_function_calls())
#'
#' ## allow all `tidyr`
#' code <- "box::use(package)
#' tidyr::pivot_longer()
#' tidyr::pivot_wider()"
#'
#' lintr::lint(text = code, linters = namespaced_function_calls(allow = c("tidyr")))
#'
#' ## allow `tidyr::pivot_longer()`
#' code <- "box::use(package)
#' tidyr::pivot_longer()"
#'
#' lintr::lint(text = code, linters = namespaced_function_calls(allow = c("tidyr::pivot_longer")))
#'
#' @export
namespaced_function_calls <- function(allow = NULL) {
always_allow <- c("box")

allow <- c(always_allow, allow)

ts_query <- "
function: (namespace_operator
lhs: (identifier) @pkg_namespace
operator: \"::\"
rhs: (identifier)
) @full_namespace"

lint_message <- "Explicit `package::function()` calls are not advisible when using `box` modules."

lintr::Linter(function(source_expression) {
if (!lintr::is_lint_level(source_expression, "file")) {
return(list())
}
source_text <- paste(source_expression$file_lines, collapse = "\n")

tree_root <- ts_root(source_text)

tree_namespaced <- ts_find_all(tree_root, ts_query)

lapply(tree_namespaced[[1]], function(tree_node) {
full_idx <- match("full_namespace", tree_node$name)
full_text <- treesitter::node_text(tree_node$node[[full_idx]])
pkg_idx <- match("pkg_namespace", tree_node$name)
pkg_text <- treesitter::node_text(tree_node$node[[pkg_idx]])

namespaced_calls <- c(full_text, pkg_text)

if (all(!namespaced_calls %in% allow)) {
start_where <- treesitter::node_start_point(tree_node$node[[full_idx]])
line_number <- treesitter::point_row(start_where) + 1
start_col_number <- treesitter::point_column(start_where) + 1
end_where <- treesitter::node_end_point(tree_node$node[[full_idx]])
end_col_number <- treesitter::point_column(end_where)
lintr::Lint(
source_expression$filename,
line_number = line_number,
column_number = start_col_number,
type = "warning",
message = lint_message,
line = source_expression$file_lines[line_number],
ranges = list(c(start_col_number, end_col_number))
)
}
})
})
}
2 changes: 2 additions & 0 deletions R/zzz.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ rhino_default_linters <- lintr::modify_defaults(
box_unused_att_pkg_linter = box_unused_attached_pkg_linter(),
box_unused_attached_pkg_fun_linter = box_unused_att_pkg_fun_linter(),
box_usage_linter = box_usage_linter(),
namespaced_function_calls = namespaced_function_calls(),
r6_usage_linter = r6_usage_linter(),
unused_declared_object_linter = unused_declared_object_linter(),
object_usage_linter = NULL # Does not work with `box::use()`
Expand All @@ -49,6 +50,7 @@ box_default_linters <- lintr::modify_defaults(
box_unused_att_pkg_linter = box_unused_attached_pkg_linter(),
box_unused_attached_pkg_fun_linter = box_unused_att_pkg_fun_linter(),
box_usage_linter = box_usage_linter(),
namespaced_function_calls = namespaced_function_calls(),
r6_usage_linter = r6_usage_linter(),
unused_declared_object_linter = unused_declared_object_linter(),
object_usage_linter = NULL # Does not work with `box::use()`
Expand Down
48 changes: 48 additions & 0 deletions man/namespaced_function_calls.Rd

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

43 changes: 43 additions & 0 deletions tests/testthat/test-namespaced_function_calls.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
test_that("namespaced_function_calls() skips allowed box::*() calls", {
linter <- namespaced_function_calls()

code <- "box::use(package)
box::export()
box::file()
box::name()"

lintr::expect_lint(code, NULL, linter)
})

test_that("namespaced_function_calls() blocks non-box namespace calls", {
linter <- namespaced_function_calls()
lint_message <- rex::rex(
"Explicit `package::function()` calls are not advisible when using `box` modules."
)

code <- "box::use(package)
tidyr::pivot_longer()
"

lintr::expect_lint(code, list(message = lint_message), linter)
})

test_that("namespaced_function_calls() skips excluded namespace calls", {
linter <- namespaced_function_calls(allow = c("tidyr"))

code <- "box::use(package)
tidyr::pivot_longer()
"

lintr::expect_lint(code, NULL, linter)
})

test_that("namespaced_function_calls() skips excluded namespace::function calls", {
linter <- namespaced_function_calls(allow = c("tidyr::pivot_longer"))

code <- "box::use(package)
tidyr::pivot_longer()
"

lintr::expect_lint(code, NULL, linter)
})

0 comments on commit e998ea5

Please sign in to comment.