5
5
# ' - Enforce such calls to all be at the top of the script.
6
6
# ' - Block usage of argument `character.only`, in particular
7
7
# ' for loading packages in a loop.
8
+ # ' - Block consecutive calls to `suppressMessages(library(.))`
9
+ # ' in favor of using [suppressMessages()] only once to suppress
10
+ # ' messages from all `library()` calls. Ditto [suppressPackageStartupMessages()].
8
11
# '
9
12
# ' @param allow_preamble Logical, default `TRUE`. If `FALSE`,
10
13
# ' no code is allowed to precede the first `library()` call,
36
39
# ' linters = library_call_linter()
37
40
# ' )
38
41
# '
42
+ # ' code <- "suppressMessages(library(dplyr))\nsuppressMessages(library(tidyr))"
43
+ # ' writeLines(code)
44
+ # ' lint(
45
+ # ' text = code,
46
+ # ' linters = library_call_linter()
47
+ # ' )
48
+ # '
39
49
# ' # okay
40
50
# ' code <- "library(dplyr)\nprint('test')"
41
51
# ' writeLines(code)
62
72
# ' linters = library_call_linter()
63
73
# ' )
64
74
# '
75
+ # ' code <- "suppressMessages({\n library(dplyr)\n library(tidyr)\n})"
76
+ # ' writeLines(code)
77
+ # ' lint(
78
+ # ' text = code,
79
+ # ' linters = library_call_linter()
80
+ # ' )
81
+ # '
65
82
# ' @evalRd rd_tags("library_call_linter")
66
83
# ' @seealso [linters] for a complete list of linters available in lintr.
67
84
# ' @export
68
85
library_call_linter <- function (allow_preamble = TRUE ) {
69
- attach_call <- " text() = 'library' or text() = 'require'"
70
- unsuppressed_call <- glue(" not( {attach_call} or starts-with(text(), 'suppress'))" )
86
+ attach_calls <- c(" library" , " require" )
87
+ attach_call_cond <- xp_text_in_table(attach_calls )
88
+ suppress_call_cond <- xp_text_in_table(c(" suppressMessages" , " suppressPackageStartupMessages" ))
89
+
90
+ unsuppressed_call_cond <- glue(" not( {xp_or(attach_call_cond, suppress_call_cond)} )" )
71
91
if (allow_preamble ) {
72
- unsuppressed_call <- xp_and(
73
- unsuppressed_call ,
74
- glue(" @line1 > //SYMBOL_FUNCTION_CALL[{ attach_call }][1]/@line1" )
92
+ unsuppressed_call_cond <- xp_and(
93
+ unsuppressed_call_cond ,
94
+ glue(" @line1 > //SYMBOL_FUNCTION_CALL[{ attach_call_cond }][1]/@line1" )
75
95
)
76
96
}
77
97
upfront_call_xpath <- glue("
78
- //SYMBOL_FUNCTION_CALL[{ attach_call }][last()]
98
+ //SYMBOL_FUNCTION_CALL[{ attach_call_cond }][last()]
79
99
/preceding::expr
80
- /SYMBOL_FUNCTION_CALL[{ unsuppressed_call }][last()]
81
- /following::expr[SYMBOL_FUNCTION_CALL[{ attach_call }]]
100
+ /SYMBOL_FUNCTION_CALL[{ unsuppressed_call_cond }][last()]
101
+ /following::expr[SYMBOL_FUNCTION_CALL[{ attach_call_cond }]]
82
102
/parent::expr
83
103
" )
84
104
85
105
# STR_CONST: block library|require("..."), i.e., supplying a string literal
86
106
# ancestor::expr[FUNCTION]: Skip usages inside functions a la {knitr}
87
- char_only_direct_xpath <- "
88
- //SYMBOL_FUNCTION_CALL[text() = 'library' or text() = 'require' ]
107
+ char_only_direct_xpath <- glue( "
108
+ //SYMBOL_FUNCTION_CALL[{attach_call_cond} ]
89
109
/parent::expr
90
110
/parent::expr[
91
111
expr[2][STR_CONST]
@@ -94,13 +114,13 @@ library_call_linter <- function(allow_preamble = TRUE) {
94
114
and not(ancestor::expr[FUNCTION])
95
115
)
96
116
]
97
- "
117
+ " )
98
118
99
119
bad_indirect_funs <- c(" do.call" , " lapply" , " sapply" , " map" , " walk" )
100
- call_symbol_cond <- "
101
- SYMBOL[text() = 'library' or text() = 'require' ]
102
- or STR_CONST[text() = '\" library \" ' or text() = ' \" require \" ' ]
103
- "
120
+ call_symbol_cond <- glue( "
121
+ SYMBOL[{attach_call_cond} ]
122
+ or STR_CONST[{ xp_text_in_table(dQuote(attach_calls, '\" ')) } ]
123
+ " )
104
124
char_only_indirect_xpath <- glue("
105
125
//SYMBOL_FUNCTION_CALL[{ xp_text_in_table(bad_indirect_funs) }]
106
126
/parent::expr
@@ -111,6 +131,23 @@ library_call_linter <- function(allow_preamble = TRUE) {
111
131
" )
112
132
call_symbol_path <- glue(" ./expr[{call_symbol_cond}]" )
113
133
134
+ attach_expr_cond <- glue(" expr[expr[SYMBOL_FUNCTION_CALL[{attach_call_cond}]]]" )
135
+
136
+ # Use `calls` in the first condition, not in the second, to prevent, e.g.,
137
+ # the first call matching calls[1] but the second matching calls[2].
138
+ # That is, ensure that calls[i] only matches a following call to calls[i].
139
+ # match on the expr, not the SYMBOL_FUNCTION_CALL, to ensure
140
+ # namespace-qualified calls only match if the namespaces do.
141
+ consecutive_suppress_xpath <- glue("
142
+ //SYMBOL_FUNCTION_CALL[{ suppress_call_cond }]
143
+ /parent::expr
144
+ /parent::expr[
145
+ expr[SYMBOL_FUNCTION_CALL[{ suppress_call_cond }]] =
146
+ following-sibling::expr[1][{attach_expr_cond}]/expr
147
+ and {attach_expr_cond}
148
+ ]
149
+ " )
150
+
114
151
Linter(function (source_expression ) {
115
152
if (! is_lint_level(source_expression , " file" )) {
116
153
return (list ())
@@ -120,12 +157,12 @@ library_call_linter <- function(allow_preamble = TRUE) {
120
157
121
158
upfront_call_expr <- xml_find_all(xml , upfront_call_xpath )
122
159
123
- call_name <- xp_call_name(upfront_call_expr )
160
+ upfront_call_name <- xp_call_name(upfront_call_expr )
124
161
125
162
upfront_call_lints <- xml_nodes_to_lints(
126
163
upfront_call_expr ,
127
164
source_expression = source_expression ,
128
- lint_message = sprintf(" Move all %s calls to the top of the script." , call_name ),
165
+ lint_message = sprintf(" Move all %s calls to the top of the script." , upfront_call_name ),
129
166
type = " warning"
130
167
)
131
168
@@ -161,6 +198,20 @@ library_call_linter <- function(allow_preamble = TRUE) {
161
198
type = " warning"
162
199
)
163
200
164
- c(upfront_call_lints , char_only_direct_lints , char_only_indirect_lints )
201
+ consecutive_suppress_expr <- xml_find_all(xml , consecutive_suppress_xpath )
202
+ consecutive_suppress_call_text <- xp_call_name(consecutive_suppress_expr )
203
+ consecutive_suppress_message <- glue(
204
+ " Unify consecutive calls to {consecutive_suppress_call_text}(). " ,
205
+ " You can do so by writing all of the calls in one braced expression " ,
206
+ " like {consecutive_suppress_call_text}({{...}})."
207
+ )
208
+ consecutive_suppress_lints <- xml_nodes_to_lints(
209
+ consecutive_suppress_expr ,
210
+ source_expression = source_expression ,
211
+ lint_message = consecutive_suppress_message ,
212
+ type = " warning"
213
+ )
214
+
215
+ c(upfront_call_lints , char_only_direct_lints , char_only_indirect_lints , consecutive_suppress_lints )
165
216
})
166
217
}
0 commit comments