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()
:lint(filename = "R/bad.R")
-
Lint a directory using
lint_dir()
:lint_dir(path = "R")
This will apply
lint()
to all R source files matching thepattern
argument. By default, this means all.R
files as well asknitr
formats (e.g..Rmd
,.Rnw
).lint_dir
is vectorized overpath
, so multiple directories can be linted at the same time. -
Lint all relevant directories of an R package using
lint_package()
: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 producedata
files.
-
For more information about the assumed package structure, see R Packages.
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.
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:
- 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 variableR_LINTR_LINTER_FILE
, if set. - A project-local linter file; that is, either
- 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 tolint()
; or - a linter file in the
.github/linters
child directory of the currently-searched directory.
- a linter file (that is, a file named like
- 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. - A linter file in the user’s
HOME
directory. - A linter file called
config
in the user’s configuration path (given bytools::R_user_dir("lintr", which = "config")
).
If no linter file is found, only default settings take effect (see 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
The default settings of lintr
are intended to conform to
the tidyverse style guide.
However, the behavior can be customized using different methods.
Apart from lintr.linter_file
, which defaults to
".lintr"
, there are the following settings:
default | |
---|---|
linters | lintr::default_linters |
encoding | UTF-8 |
exclude | regex: #[[:space:]]*nolint
|
exclude_next | regex: #[[:space:]]*nolint next
|
exclude_start | regex: #[[:space:]]*nolint start
|
exclude_end | regex: #[[:space:]]*nolint end
|
exclude_linter | regex:
^[[:space:]]*:[[:space:]]*(?<linters>(?:(?:[^,.])+[[:space:]]*,[[:space:]]*)*(?:[^,.])+)\.
|
exclude_linter_sep | regex: [[:space:]]*,[[:space:]]*
|
exclusions | (empty) |
cache_directory | /home/runner/.cache/R/lintr |
comment_token | (lintr-bot comment token for automatic GitHub comments) |
error_on_lint | FALSE |
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
:
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.
settings | |
---|---|
assignment_linter | allow_cascading_assign = TRUE, allow_right_assign = FALSE, allow_trailing = TRUE, allow_pipe_assign = FALSE |
brace_linter | allow_single_line = FALSE |
commas_linter | allow_trailing = FALSE |
commented_code_linter | |
equals_na_linter | |
function_left_parentheses_linter | |
indentation_linter | indent = 2L, hanging_indent_style = “tidy”, assignment_as_infix = TRUE |
infix_spaces_linter | exclude_operators = NULL, allow_multiple_spaces = TRUE |
line_length_linter | length = 80L |
object_length_linter | length = 30L |
object_name_linter | styles = c(“snake_case”, “symbols”), regexes = character(0) |
object_usage_linter | interpret_glue = TRUE, skip_with = TRUE |
paren_body_linter | |
pipe_continuation_linter | |
quotes_linter | delimiter = “"” |
return_linter | return_style = “implicit”, allow_implicit_else = TRUE, return_functions = NULL, except = NULL, except_regex = NULL |
semicolon_linter | allow_compound = FALSE, allow_trailing = FALSE |
seq_linter | |
spaces_inside_linter | |
spaces_left_parentheses_linter | |
T_and_F_symbol_linter | |
trailing_blank_lines_linter | |
trailing_whitespace_linter | allow_empty_lines = FALSE, allow_in_strings = TRUE |
vector_logic_linter | |
whitespace_linter |
Another way to customize linters is by specifying tags in
linters_with_tags()
. The available tags are listed
below:
lintr::available_tags(packages = "lintr")
#> [1] "best_practices" "common_mistakes" "configurable"
#> [4] "consistency" "correctness" "default"
#> [7] "deprecated" "efficiency" "executing"
#> [10] "package_development" "pkg_testthat" "readability"
#> [13] "regex" "robustness" "style"
#> [16] "tidy_design"
You can select tags of interest to see which linters are included:
linters <- lintr::linters_with_tags(tags = c("package_development", "readability"))
names(linters)
#> [1] "backport_linter" "boolean_arithmetic_linter"
#> [3] "brace_linter" "commas_linter"
#> [5] "commented_code_linter" "comparison_negation_linter"
#> [7] "conjunct_test_linter" "consecutive_assertion_linter"
#> [9] "consecutive_mutate_linter" "cyclocomp_linter"
#> [11] "empty_assignment_linter" "expect_comparison_linter"
#> [13] "expect_identical_linter" "expect_length_linter"
#> [15] "expect_named_linter" "expect_not_linter"
#> [17] "expect_null_linter" "expect_s3_class_linter"
#> [19] "expect_s4_class_linter" "expect_true_false_linter"
#> [21] "expect_type_linter" "fixed_regex_linter"
#> [23] "for_loop_index_linter" "function_left_parentheses_linter"
#> [25] "function_return_linter" "if_not_else_linter"
#> [27] "if_switch_linter" "implicit_assignment_linter"
#> [29] "indentation_linter" "infix_spaces_linter"
#> [31] "inner_combine_linter" "is_numeric_linter"
#> [33] "keyword_quote_linter" "length_levels_linter"
#> [35] "lengths_linter" "library_call_linter"
#> [37] "line_length_linter" "matrix_apply_linter"
#> [39] "nested_ifelse_linter" "nested_pipe_linter"
#> [41] "numeric_leading_zero_linter" "object_length_linter"
#> [43] "object_overwrite_linter" "object_usage_linter"
#> [45] "one_call_pipe_linter" "outer_negation_linter"
#> [47] "package_hooks_linter" "paren_body_linter"
#> [49] "pipe_call_linter" "pipe_consistency_linter"
#> [51] "pipe_continuation_linter" "quotes_linter"
#> [53] "redundant_equals_linter" "rep_len_linter"
#> [55] "repeat_linter" "sample_int_linter"
#> [57] "scalar_in_linter" "semicolon_linter"
#> [59] "sort_linter" "spaces_inside_linter"
#> [61] "spaces_left_parentheses_linter" "stopifnot_all_linter"
#> [63] "string_boundary_linter" "system_file_linter"
#> [65] "T_and_F_symbol_linter" "unnecessary_concatenation_linter"
#> [67] "unnecessary_lambda_linter" "unnecessary_nesting_linter"
#> [69] "unnecessary_placeholder_linter" "unreachable_code_linter"
#> [71] "which_grepl_linter" "yoda_test_linter"
You can include tag-based linters in the configuration file, and customize them further:
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
names(lintr::all_linters())
#> [1] "absolute_path_linter" "any_duplicated_linter"
#> [3] "any_is_na_linter" "assignment_linter"
#> [5] "backport_linter" "boolean_arithmetic_linter"
#> [7] "brace_linter" "class_equals_linter"
#> [9] "commas_linter" "commented_code_linter"
#> [11] "comparison_negation_linter" "condition_call_linter"
#> [13] "condition_message_linter" "conjunct_test_linter"
#> [15] "consecutive_assertion_linter" "consecutive_mutate_linter"
#> [17] "cyclocomp_linter" "duplicate_argument_linter"
#> [19] "empty_assignment_linter" "equals_na_linter"
#> [21] "expect_comparison_linter" "expect_identical_linter"
#> [23] "expect_length_linter" "expect_named_linter"
#> [25] "expect_not_linter" "expect_null_linter"
#> [27] "expect_s3_class_linter" "expect_s4_class_linter"
#> [29] "expect_true_false_linter" "expect_type_linter"
#> [31] "fixed_regex_linter" "for_loop_index_linter"
#> [33] "function_argument_linter" "function_left_parentheses_linter"
#> [35] "function_return_linter" "if_not_else_linter"
#> [37] "if_switch_linter" "ifelse_censor_linter"
#> [39] "implicit_assignment_linter" "implicit_integer_linter"
#> [41] "indentation_linter" "infix_spaces_linter"
#> [43] "inner_combine_linter" "is_numeric_linter"
#> [45] "keyword_quote_linter" "length_levels_linter"
#> [47] "length_test_linter" "lengths_linter"
#> [49] "library_call_linter" "line_length_linter"
#> [51] "list_comparison_linter" "literal_coercion_linter"
#> [53] "matrix_apply_linter" "missing_argument_linter"
#> [55] "missing_package_linter" "namespace_linter"
#> [57] "nested_ifelse_linter" "nested_pipe_linter"
#> [59] "nonportable_path_linter" "nrow_subset_linter"
#> [61] "numeric_leading_zero_linter" "nzchar_linter"
#> [63] "object_length_linter" "object_name_linter"
#> [65] "object_overwrite_linter" "object_usage_linter"
#> [67] "one_call_pipe_linter" "outer_negation_linter"
#> [69] "package_hooks_linter" "paren_body_linter"
#> [71] "paste_linter" "pipe_call_linter"
#> [73] "pipe_consistency_linter" "pipe_continuation_linter"
#> [75] "pipe_return_linter" "print_linter"
#> [77] "quotes_linter" "redundant_equals_linter"
#> [79] "redundant_ifelse_linter" "regex_subset_linter"
#> [81] "rep_len_linter" "repeat_linter"
#> [83] "return_linter" "routine_registration_linter"
#> [85] "sample_int_linter" "scalar_in_linter"
#> [87] "semicolon_linter" "seq_linter"
#> [89] "sort_linter" "spaces_inside_linter"
#> [91] "spaces_left_parentheses_linter" "sprintf_linter"
#> [93] "stopifnot_all_linter" "string_boundary_linter"
#> [95] "strings_as_factors_linter" "system_file_linter"
#> [97] "T_and_F_symbol_linter" "terminal_close_linter"
#> [99] "todo_comment_linter" "trailing_blank_lines_linter"
#> [101] "trailing_whitespace_linter" "undesirable_function_linter"
#> [103] "undesirable_operator_linter" "unnecessary_concatenation_linter"
#> [105] "unnecessary_lambda_linter" "unnecessary_nesting_linter"
#> [107] "unnecessary_placeholder_linter" "unreachable_code_linter"
#> [109] "unused_import_linter" "vector_logic_linter"
#> [111] "which_grepl_linter" "whitespace_linter"
#> [113] "yoda_test_linter"
If you want to use all available linters, you can include this in
your .lintr
file:
If you want to use all available linters except a few, you
can exclude them using 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:
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))))
})
#> line_number message linter
#> 1 1 Use <-, not =, for assignment. get
#> ---------------------------------------------------------------------------
#> line_number message linter
#> 1 1 Use <-, not =, for assignment. assignment_linter
#> ---------------------------------------------------------------------------
#> line_number message linter
#> 1 1 Use <-, not =, for assignment. assignment_linter
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
X = 42L # -------------- this comment overflows the default 80 chars line length.
> lint("file.R")
#> <text>:1:1: style: [object_name_linter] Variable and function name style should match snake_case or symbols.
#> X = 42L # -------------- this comment overflows the default 80 chars line length.
#> ^
#> <text>:1:3: style: [assignment_linter] Use <-, not =, for assignment.
#> X = 42L # -------------- this comment overflows the default 80 chars line length.
#> ^
#> <text>:1:81: style: [line_length_linter] Lines should not be more than 80 characters. This line is 81 characters.
#> X = 42L # -------------- this comment overflows the default 80 chars line length.
#> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
file2.R
X = 42L # nolint ------ this comment overflows the default 80 chars line length.
> lint("file2.R")
#> ℹ No lints found.
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
X = 42L # nolint: object_name_linter. this comment overflows the default 80 chars line length.
> lint("file3.R")
#> <text>:1:3: style: [assignment_linter] Use <-, not =, for assignment.
#> X = 42L # nolint: object_name_linter. this comment overflows the default 80 chars line length.
#> ^
#> <text>:1:81: style: [line_length_linter] Lines should not be more than 80 characters. This line is 94 characters.
#> X = 42L # nolint: object_name_linter. this comment overflows the default 80 chars line length.
#> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~
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
X = 42L # nolint: object_name_linter, line_length_linter. this comment overflows the default 80 chars line length.
> lint("file4.R")
#> <text>:1:3: style: [assignment_linter] Use <-, not =, for assignment.
#> X = 42L # nolint: object_name_linter, line_length_linter. this comment overflows the default 80 chars line length.
#> ^
You can also specify the linter names by a unique prefix instead of their full name:
file5.R
X = 42L # nolint: object_name, line_len. this comment still overflows the default 80 chars line length.
> lint("file5.R")
#> <text>:1:3: style: [assignment_linter] Use <-, not =, for assignment.
#> X = 42L # nolint: object_name, line_len. this comment still overflows the default 80 chars line length.
#> ^
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.
# x <- 42L
# print(x)
#> <text>:1:3: style: [commented_code_linter] Remove commented code.
#> # x <- 42L
#> ^~~~~~~~
#> <text>:2:3: style: [commented_code_linter] Remove commented code.
#> # print(x)
#> ^~~~~~~~
# nolint start: commented_code_linter.
# x <- 42L
# print(x)
# nolint end
#> ℹ No lints found.
(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
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.
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
exclusions: list(
"renv"
)
This is particularly useful for projects which include external code in subdirectories.