Skip to content

Commit 3011d78

Browse files
Fix package_hooks_linter to validate .onUnload() arguments
Signed-off-by: Emmanuel Ferdman <[email protected]>
1 parent ec332f1 commit 3011d78

File tree

2 files changed

+62
-7
lines changed

2 files changed

+62
-7
lines changed

R/package_hooks_linter.R

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#' Package hooks linter
22
#'
3-
#' Check various common "gotchas" in [.onLoad()], [.onAttach()], [.Last.lib()], and [.onDetach()]
3+
#' Check various common "gotchas" in [.onLoad()], [.onAttach()], [.Last.lib()], [.onDetach()], and [.onUnload()]
44
#' namespace hooks that will cause `R CMD check` issues. See Writing R Extensions for details.
55
#'
66
#' 1. `.onLoad()` shouldn't call [cat()], [message()], [print()], [writeLines()], [packageStartupMessage()],
@@ -9,7 +9,7 @@
99
#' `require()`, `library()`, or `installed.packages()`.
1010
#' 3. `.Last.lib()` and `.onDetach()` shouldn't call [library.dynam.unload()].
1111
#' 4. `.onLoad()` and `.onAttach()` should take two arguments, with names matching `^lib` and `^pkg`;
12-
#' `.Last.lib()` and `.onDetach()` should take one argument with name matching `^lib`.
12+
#' `.Last.lib()`, `.onDetach()`, and `.onUnload()` should take one argument with name matching `^lib`.
1313
#'
1414
#' @examples
1515
#' # will produce lints
@@ -28,6 +28,11 @@
2828
#' linters = package_hooks_linter()
2929
#' )
3030
#'
31+
#' lint(
32+
#' text = ".onUnload <- function() { }",
33+
#' linters = package_hooks_linter()
34+
#' )
35+
#'
3136
#' # okay
3237
#' lint(
3338
#' text = ".onLoad <- function(lib, pkg) { }",
@@ -44,6 +49,11 @@
4449
#' linters = package_hooks_linter()
4550
#' )
4651
#'
52+
#' lint(
53+
#' text = ".onUnload <- function(libpath) { }",
54+
#' linters = package_hooks_linter()
55+
#' )
56+
#'
4757
#' @evalRd rd_tags("package_hooks_linter")
4858
#' @seealso [linters] for a complete list of linters available in lintr.
4959
#' @export
@@ -77,7 +87,7 @@ package_hooks_linter <- function() {
7787

7888
# lints here will hit the function <expr>,
7989
# this path returns to the corresponding namespace hook's name
80-
ns_calls <- xp_text_in_table(c(".onLoad", ".onAttach", ".onDetach", ".Last.lib"))
90+
ns_calls <- xp_text_in_table(c(".onLoad", ".onAttach", ".onDetach", ".onUnload", ".Last.lib"))
8191

8292
# usually any given package will have one or maybe two files defining namespace hooks.
8393
# given the number of checks, then, it's prudent to check first if any such hook is defined,
@@ -118,7 +128,7 @@ package_hooks_linter <- function() {
118128
unload_arg_name_xpath <- "
119129
(//FUNCTION | //OP-LAMBDA)
120130
/parent::expr[
121-
preceding-sibling::expr/SYMBOL[text() = '.onDetach' or text() = '.Last.lib']
131+
preceding-sibling::expr/SYMBOL[text() = '.onDetach' or text() = '.onUnload' or text() = '.Last.lib']
122132
and (
123133
count(SYMBOL_FORMALS) != 1
124134
or SYMBOL_FORMALS[not(starts-with(text(), 'lib'))]
@@ -180,7 +190,7 @@ package_hooks_linter <- function() {
180190
bad_unload_call_lints <-
181191
xml_nodes_to_lints(bad_unload_call_expr, source_expression, bad_unload_call_message, type = "warning")
182192

183-
# (5) .Last.lib() and .onDetach() should take one arguments with name matching ^lib
193+
# (5) .Last.lib(), .onDetach(), and .onUnload() should take one argument with name matching ^lib
184194
unload_arg_name_expr <- xml_find_all(xml, unload_arg_name_xpath)
185195

186196
unload_arg_name_message <- sprintf(

tests/testthat/test-package_hooks_linter.R

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,17 @@ test_that("package_hooks_linter blocks attaching namespaces", {
171171
)
172172
})
173173

174-
test_that("package_hooks_linter skips valid .onDetach() and .Last.lib()", {
174+
test_that("package_hooks_linter skips valid .onDetach(), .Last.lib(), and .onUnload()", {
175175
linter <- package_hooks_linter()
176176

177177
expect_lint(".onDetach <- function(lib) { }", NULL, linter)
178178
expect_lint(".onDetach <- function(libname) { }", NULL, linter)
179179

180180
expect_lint(".Last.lib <- function(lib) { }", NULL, linter)
181181
expect_lint(".Last.lib <- function(libname) { }", NULL, linter)
182+
183+
expect_lint(".onUnload <- function(lib) { }", NULL, linter)
184+
expect_lint(".onUnload <- function(libpath) { }", NULL, linter)
182185
})
183186

184187
test_that("package_hooks_linter catches usage of library.dynam.unload()", {
@@ -202,7 +205,7 @@ test_that("package_hooks_linter catches usage of library.dynam.unload()", {
202205
)
203206
})
204207

205-
test_that("package_hooks_linter detects bad argument names in .onDetach()/.Last.lib()", {
208+
test_that("package_hooks_linter detects bad argument names in .onDetach()/.Last.lib()/.onUnload()", {
206209
linter <- package_hooks_linter()
207210
lint_msg_part <- " should take one argument starting with 'lib'"
208211

@@ -216,6 +219,11 @@ test_that("package_hooks_linter detects bad argument names in .onDetach()/.Last.
216219
rex::rex(".Last.lib()", lint_msg_part),
217220
linter
218221
)
222+
expect_lint(
223+
".onUnload <- function(pkg) { }",
224+
rex::rex(".onUnload()", lint_msg_part),
225+
linter
226+
)
219227

220228
# exactly one argument required.
221229
# NB: QC.R allows ... arguments to be passed, but disallow this flexibility in the linter.
@@ -234,6 +242,21 @@ test_that("package_hooks_linter detects bad argument names in .onDetach()/.Last.
234242
rex::rex(".onDetach()", lint_msg_part),
235243
linter
236244
)
245+
expect_lint(
246+
".onUnload <- function() { }",
247+
rex::rex(".onUnload()", lint_msg_part),
248+
linter
249+
)
250+
expect_lint(
251+
".onUnload <- function(lib, pkg) { }",
252+
rex::rex(".onUnload()", lint_msg_part),
253+
linter
254+
)
255+
expect_lint(
256+
".onUnload <- function(...) { }",
257+
rex::rex(".onUnload()", lint_msg_part),
258+
linter
259+
)
237260
})
238261

239262
test_that("function shorthand is handled", {
@@ -265,6 +288,16 @@ test_that("function shorthand is handled", {
265288
rex::rex(".onDetach() should take one argument starting with 'lib'."),
266289
linter
267290
)
291+
expect_lint(
292+
".onUnload <- \\() { }",
293+
rex::rex(".onUnload() should take one argument starting with 'lib'."),
294+
linter
295+
)
296+
expect_lint(
297+
".onUnload <- \\(libpath) { }",
298+
NULL,
299+
linter
300+
)
268301
})
269302

270303
test_that("lints vectorize", {
@@ -279,4 +312,16 @@ test_that("lints vectorize", {
279312
),
280313
package_hooks_linter()
281314
)
315+
316+
expect_lint(
317+
trim_some("{
318+
.onDetach <- function(xxx) { }
319+
.onUnload <- function(yyy) { }
320+
}"),
321+
list(
322+
list(".onDetach", line_number = 2L),
323+
list(".onUnload", line_number = 3L)
324+
),
325+
package_hooks_linter()
326+
)
282327
})

0 commit comments

Comments
 (0)