-
Notifications
You must be signed in to change notification settings - Fork 187
/
lintr.Rmd
464 lines (339 loc) · 15.2 KB
/
lintr.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
---
title: "Using lintr"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Using lintr}
%\VignetteEngine{knitr::rmarkdown}
\usepackage[utf8]{inputenc}
---
```{r setup, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```
This vignette describes how to set up and configure `lintr` for use with projects or packages.
## Running `lintr` on a project
Checking an R project for lints can be done with three different functions:
- Lint a single file using `lint()`:
``` r
lint(filename = "R/bad.R")
```
- Lint a directory using `lint_dir()`:
``` r
lint_dir(path = "R")
```
This will apply `lint()` to all R source files matching the `pattern` argument.
By default, this means all `.R` files as well as `knitr` formats (e.g. `.Rmd`, `.Rnw`).
`lint_dir` is vectorized over `path`, so multiple directories can be linted at the same time.
- Lint all relevant directories of an R package using `lint_package()`:
``` r
lint_package(path = ".")
```
This will apply `lint_dir()` to all subdirectories usually containing R code in packages:
- `R` containing the package implementation.
- `tests` containing test code.
- `inst` containing sample code or vignettes that will be installed along with the package.
- `vignettes` containing package vignettes.
- `data-raw` containing code to produce `data` files.
For more information about the assumed package structure, see [R Packages](https://r-pkgs.org/structure.html).
Note that some linters (e.g. `object_usage_linter()`) require the package to be installed to function properly. `pkgload::load_all()` will also suffice. See `?executing_linters` for more details.
## Configuring linters
### The `.lintr` file
The canonical way to configure R projects and packages for linting is to create a `.lintr` file in the project root.
This is a file in debian control format (`?read.dcf`), each value of which is evaluated as R code by `lintr` when reading the settings.
A minimal `.lintr` file can be generated by running `use_lintr()` in the project directory.
Lintr supports per-project configuration of the following fields.
- `linters` - see `?linters_with_defaults` for example of specifying only a few non-default linters and `?linters_with_tags` for more fine-grained control.
- `exclusions` - a list of filenames to exclude from linting. You can use a named item to exclude only certain lines from a file.
- `exclude` - a regex pattern for lines to exclude from linting. Default is "\# nolint"
- `exclude_start` - a regex pattern to start exclusion range. Default is "\# nolint start"
- `exclude_end` - a regex pattern to end exclusion range. Default is "\# nolint end"
- `encoding` - the encoding used for source files. Default inferred from .Rproj or DESCRIPTION files, fallback to UTF-8
### .lintr File Example
Below is an example .lintr file that uses 120 character line lengths, disables `commented_code_linter`, excludes a couple of files.
``` yaml
linters: linters_with_defaults(
line_length_linter(120),
commented_code_linter = NULL
)
exclusions: list(
"inst/doc/creating_linters.R" = 1,
"inst/example/bad.R",
"tests/testthat/exclusions-test"
)
```
### Other configuration options
More generally, `lintr` searches for a settings file according to following prioritized list.
The first one found, if any, will be used:
1. If `options("lintr.linter_file")` is an absolute path, this file will be used. The default for this option is `".lintr"` or the value of the environment variable `R_LINTR_LINTER_FILE`, if set.
2. A project-local linter file; that is, either
1. a linter file (that is, a file named like `lintr.linter_file`) in the currently-searched directory, i.e. the directory of the file passed to `lint()`; or
2. a linter file in the `.github/linters` child directory of the currently-searched directory.
3. A project-local linter file in the closest parent directory of the currently-searched directory, starting from the deepest path, moving upwards one level at a time. When run from `lint_package()`, this directory can differ for each linted file.
4. A linter file in the user's `HOME` directory.
5. A linter file called `config` in the user's configuration path (given by `tools::R_user_dir("lintr", which = "config")`).
If no linter file is found, only default settings take effect (see [defaults](#defaults)).
### Using `options()`
Values in `options()`, if they are not `NULL`, take precedence over those in the linter file (e.g. `.lintr`).
Note that the key `option_name` in the linter file translates to an R option `lintr.option_name`.
For example, `options(lintr.exclude = "# skip lint")` will take precedence over `exclude: # nolint` in the linter file.
### Using arguments to `lint()`
The settings can also be passed as arguments to linting functions directly.
In case of `exclusions`, these will be combined with the globally parsed settings.
Other settings will be overridden.
If only the specified settings should be changed, and the remaining settings should be taken directly from the defaults, the argument `parse_settings = FALSE` can be added to the function calls.
This will suppress reading of the `.lintr` configuration.
This is particularly useful for tests which should not exclude example files containing lints while the package-level `.lintr` excludes those files because the lints are intentional.
### Defaults {#defaults}
The default settings of `lintr` are intended to conform to the [tidyverse style guide](https://style.tidyverse.org/).
However, the behavior can be customized using different methods.
Apart from `lintr.linter_file`, which defaults to `".lintr"`, there are the following settings:
```{r show_default_settings, echo = FALSE}
default_settings <- lintr::default_settings
default_settings$linters <- "`lintr::default_linters`"
default_settings$comment_token <- "(lintr-bot comment token for automatic GitHub comments)"
default_settings$exclusions <- "(empty)"
make_string <- function(x) {
if (inherits(x, "regex")) {
paste0("regex: `", x, "`")
} else {
as.character(x)
}
}
defaults_table <- data.frame(default = vapply(default_settings, make_string, character(1L)))
# avoid conflict when loading lintr in echo=TRUE cell below
rm(default_settings)
knitr::kable(defaults_table)
```
Note that the default `encoding` setting depends on the file to be linted.
If an Encoding is found in a `.Rproj` file or a `DESCRIPTION` file, that encoding overrides the default of UTF-8.
#### Customizing active linters
If you only want to customize some linters, you can use the helper function `linters_with_defaults()`, which will keep all unnamed linters with the default settings.
Disable a linter by passing `NULL`.
For example, to set the line length limit to 120 characters and globally disable the `whitespace_linter()`, you can put this into your `.lintr`:
``` r
linters: linters_with_defaults(
line_length_linter = line_length_linter(120L),
whitespace_linter = NULL
)
```
By default, the following linters are enabled.
Where applicable, the default settings are also shown.
```{r show_linter_defaults, echo = FALSE}
library(lintr) # needed here for formalArgs
default_linters <- lintr::default_linters
linters_with_args <- lapply(
setNames(nm = intersect(names(default_linters), lintr::available_linters(tags = "configurable")$linter)),
formalArgs
)
make_setting_string <- function(linter_name) {
linter_args <- linters_with_args[[linter_name]]
if (is.null(linter_args)) {
return("")
}
arglist <- vapply(linter_args, function(arg) {
env <- environment(default_linters[[linter_name]])
deparse(env[[arg]])
}, character(1L))
paste0(linter_args, " = ", arglist, collapse = ", ")
}
defaults_table <- data.frame(
row.names = names(default_linters),
settings = vapply(names(default_linters), make_setting_string, character(1L))
)
knitr::kable(defaults_table)
```
Another way to customize linters is by specifying tags in `linters_with_tags()`.
The available tags are listed below:
```{r show_tags}
lintr::available_tags(packages = "lintr")
```
You can select tags of interest to see which linters are included:
```{r show_tag_linters}
linters <- lintr::linters_with_tags(tags = c("package_development", "readability"))
names(linters)
```
You can include tag-based linters in the configuration file, and customize them further:
```yaml
linters: linters_with_tags(
tags = c("package_development", "readability"),
yoda_test_linter = NULL
)
```
#### Using all available linters
The default lintr configuration includes only linters relevant to the tidyverse style guide, but there are many other linters available in `{lintr}`. You can see a list of all available linters using
```{r show_all_linter_names}
names(lintr::all_linters())
```
If you want to use all available linters, you can include this in your `.lintr` file:
```yaml
linters: all_linters()
```
If you want to use all available linters *except* a few, you can exclude them using `NULL`:
```yaml
linters: all_linters(
commented_code_linter = NULL,
implicit_integer_linter = NULL
)
```
#### Advanced: programmatic retrieval of linters
For some use cases, it may be useful to specify linters by string instead of by name, i.e. `"assignment_linter"` instead of writing out `assignment_linter()`.
Beware that in such cases, a simple `get()` is not enough:
```{r programmatic_lintr}
library(lintr)
linter_name <- "assignment_linter"
show_lint <- function(l) {
lint_df <- as.data.frame(l)
print(lint_df[, c("line_number", "message", "linter")])
}
hline <- function() cat(strrep("-", getOption("width") - 5L), "\n", sep = "")
withr::with_tempfile("tmp", {
writeLines("a = 1", tmp)
# linter column is just 'get'
show_lint(lint(tmp, linters = get(linter_name)()))
hline()
this_linter <- get(linter_name)()
attr(this_linter, "name") <- linter_name
# linter column is 'assignment_linter'
show_lint(lint(tmp, linters = this_linter))
hline()
# more concise alternative for this case: use eval(call(.))
show_lint(lint(tmp, linters = eval(call(linter_name))))
})
```
## Exclusions
Sometimes, linters should not be globally disabled.
Instead, one might want to exclude some code from linting altogether or selectively disable some linters on some part of the code.
> Note that the preferred way of excluding lints from source code is to use the narrowest possible scope and specify exactly which linters should not throw a lint on a marked line.
> This prevents accidental suppression of justified lints that happen to be on the same line as a lint that needs to be suppressed.
### Excluding lines of code
Within source files, special comments can be used to exclude single lines of code from linting.
All lints produced on the marked line are excluded from the results.
By default, this special comment is `# nolint`:
**file.R**
``` r
X = 42L # -------------- this comment overflows the default 80 chars line length.
```
`> lint("file.R")`
```{r show_long_line_lint, echo = FALSE}
lint("X = 42L # -------------- this comment overflows the default 80 chars line length.\n",
parse_settings = FALSE
)
```
**file2.R**
``` r
X = 42L # nolint ------ this comment overflows the default 80 chars line length.
```
`> lint("file2.R")`
```{r show_nolint, echo = FALSE}
lint("X = 42L # nolint ------ this comment overflows the default 80 chars line length.\n",
parse_settings = FALSE
)
```
Observe how all lints were suppressed and no output is shown.
Sometimes, only a specific linter needs to be excluded.
In this case, the *name* of the linter can be appended to the `# nolint` comment preceded by a colon and terminated by a dot.
### Excluding only some linters
**file3.R**
``` r
X = 42L # nolint: object_name_linter. this comment overflows the default 80 chars line length.
```
`> lint("file3.R")`
```{r show_long_line_lint_not_skipped, echo = FALSE}
lint("X = 42L # nolint: object_name_linter. this comment overflows the default 80 chars line length.\n",
parse_settings = FALSE
)
```
Observe how only the `object_name_linter` was suppressed.
This is preferable to blanket `# nolint` statements because blanket exclusions may accidentally silence a linter that was not intentionally suppressed.
Multiple linters can be specified by listing them with a comma as a separator:
**file4.R**
``` r
X = 42L # nolint: object_name_linter, line_length_linter. this comment overflows the default 80 chars line length.
```
`> lint("file4.R")`
```{r show_nolint_multiple, echo = FALSE}
lint(
paste(
"X = 42L",
"# nolint: object_name_linter, line_length_linter. this comment overflows the default 80 chars line length.\n"
),
parse_settings = FALSE
)
```
You can also specify the linter names by a unique prefix instead of their full name:
**file5.R**
``` r
X = 42L # nolint: object_name, line_len. this comment still overflows the default 80 chars line length.
```
`> lint("file5.R")`
```{r show_nolint_abbrev, echo = FALSE}
lint(
paste(
"X = 42L",
"# nolint: object_name, line_len. this comment still overflows the default 80 chars line length.\n"
),
parse_settings = FALSE
)
```
### Excluding multiple lines of codes
If any or all linters should be disabled for a contiguous block of code, the `exclude_start` and `exclude_end` patterns can be used.
They default to `# nolint start` and `# nolint end` respectively.
`# nolint start` accepts the same syntax as `# nolint` to disable specific linters in the following lines until a `# nolint end` is encountered.
``` r
# x <- 42L
# print(x)
```
```{r show_comment_code_lint, echo = FALSE}
lint("# x <- 42L\n# print(x)\n", parse_settings = FALSE)
```
``` r
# nolint start: commented_code_linter.
# x <- 42L
# print(x)
# nolint end
```
```{r show_comment_code_nolint, echo = FALSE}
lint("# nolint start: commented_code_linter.\n# x <- 42L\n# print(x)\n# nolint end\n",
parse_settings = FALSE
)
```
(No lints)
### Excluding lines via the config file
Individual lines can also be excluded via the config file by setting the key `exclusions` to a list with elements corresponding to different files.
To exclude all lints for line 1 of file `R/bad.R` and `line_length_linter` for lines 4 to 6 of the same file, one can set
``` r
exclusions: list(
"R/bad.R" = list(
1, # global exclusions are unnamed
line_length_linter = 4:6
)
)
```
All paths are interpreted relative to the location of the `.lintr` file.
### Excluding files completely
The linter configuration can also be used to exclude a file entirely, or a linter for a file entirely.
Use the sentinel line number `Inf` to mark all lines as excluded within a file.
If a file is only given as a character vector, full exclusion is implied.
``` r
exclusions: list(
# excluded from all lints:
"R/excluded1.R",
"R/excluded2.R" = Inf,
"R/excluded3.R" = list(Inf),
# excluded from line_length_linter:
"R/no-line-length.R" = list(
line_length_linter = Inf
)
)
```
### Excluding directories completely
Entire directories are also recognized when supplied as strings in the `exclusions` key.
For example, to exclude the `renv` folder from linting in a R project using `renv`, set
``` r
exclusions: list(
"renv"
)
```
This is particularly useful for projects which include external code in subdirectories.