Title: | A 'Linter' for R Code |
---|---|
Description: | Checks adherence to a given style, syntax errors and possible semantic issues. Supports on the fly checking of R code edited with 'RStudio IDE', 'Emacs', 'Vim', 'Sublime Text', 'Atom' and 'Visual Studio Code'. |
Authors: | Jim Hester [aut], Florent Angly [aut] (fangly), Russ Hyde [aut], Michael Chirico [aut, cre], Kun Ren [aut], Alexander Rosenstock [aut] (AshesITR), Indrajeet Patil [aut] (<https://orcid.org/0000-0003-1995-6531>, @patilindrajeets) |
Maintainer: | Michael Chirico <[email protected]> |
License: | MIT + file LICENSE |
Version: | 3.1.2.9000 |
Built: | 2024-12-20 06:31:09 UTC |
Source: | https://github.com/r-lib/lintr |
Check that no absolute paths are used (e.g. "/var", "C:\System", "~/docs").
absolute_path_linter(lax = TRUE)
absolute_path_linter(lax = TRUE)
lax |
Less stringent linting, leading to fewer false positives.
If
|
best_practices, configurable, robustness
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'R"--[/blah/file.txt]--"', linters = absolute_path_linter() ) # okay lint( text = 'R"(./blah)"', linters = absolute_path_linter() )
# will produce lints lint( text = 'R"--[/blah/file.txt]--"', linters = absolute_path_linter() ) # okay lint( text = 'R"(./blah)"', linters = absolute_path_linter() )
Create a linter configuration based on all available linters
all_linters(..., packages = "lintr")
all_linters(..., packages = "lintr")
... |
Arguments of elements to change. If unnamed, the argument is automatically named.
If the named argument already exists in the list of linters, it is replaced by the new element.
If it does not exist, it is added. If the value is |
packages |
A character vector of packages to search for linters. |
linters_with_defaults for basing off lintr's set of default linters.
linters_with_tags for basing off tags attached to linters, possibly across multiple packages.
available_linters to get a data frame of available linters.
linters for a complete list of linters available in lintr.
names(all_linters())
names(all_linters())
Lists of function names and operators for undesirable_function_linter()
and undesirable_operator_linter()
.
There is a list for the default elements and another that contains all available elements.
Use modify_defaults()
to produce a custom list.
all_undesirable_functions default_undesirable_functions all_undesirable_operators default_undesirable_operators
all_undesirable_functions default_undesirable_functions all_undesirable_operators default_undesirable_operators
A named list of character strings.
The following functions are sometimes regarded as undesirable:
.libPaths()
As an alternative, use withr::with_libpaths()
for a temporary change instead of permanently modifying the library location.
attach()
As an alternative, use roxygen2's @importFrom statement in packages, or ::
in scripts. attach()
modifies the global search path.
browser()
As an alternative, remove this likely leftover from debugging. It pauses execution when run.
debug()
As an alternative, remove this likely leftover from debugging. It traps a function and causes execution to pause when that function is run.
debugcall()
As an alternative, remove this likely leftover from debugging. It traps a function and causes execution to pause when that function is run.
debugonce()
As an alternative, remove this likely leftover from debugging. It traps a function and causes execution to pause when that function is run.
detach()
As an alternative, avoid modifying the global search path. Detaching environments from the search path is rarely necessary in production code.
library()
As an alternative, use roxygen2's @importFrom statement in packages and ::
in scripts, instead of modifying the global search path.
mapply()
As an alternative, use Map()
to guarantee a list is returned and simplify accordingly.
options()
As an alternative, use withr::with_options()
for a temporary change instead of permanently modifying the session options.
par()
As an alternative, use withr::with_par()
for a temporary change instead of permanently modifying the graphics device parameters.
require()
As an alternative, use roxygen2's @importFrom statement in packages and library()
or ::
in scripts, instead of modifying the global search path.
sapply()
As an alternative, use vapply()
with an appropriate FUN.VALUE=
argument to obtain type-stable simplification.
setwd()
As an alternative, use withr::with_dir()
for a temporary change instead of modifying the global working directory.
sink()
As an alternative, use withr::with_sink()
for a temporary redirection instead of permanently redirecting output.
source()
As an alternative, manage dependencies through packages. source()
loads code into the global environment unless local = TRUE
is used, which can cause hard-to-predict behavior.
structure()
As an alternative, Use class<-
, names<-
, and attr<-
to set attributes.
Sys.setenv()
As an alternative, use withr::with_envvar()
for a temporary change instead of permanently modifying global environment variables.
Sys.setlocale()
As an alternative, use withr::with_locale()
for a temporary change instead of permanently modifying the session locale.
trace()
As an alternative, remove this likely leftover from debugging. It traps a function and causes execution of arbitrary code when that function is run.
undebug()
As an alternative, remove this likely leftover from debugging. It is only useful for interactive debugging with debug()
.
untrace()
As an alternative, remove this likely leftover from debugging. It is only useful for interactive debugging with trace()
.
The following operators are sometimes regarded as undesirable:
<<-
. It assigns outside the current environment in a way that can be hard to reason about. Prefer fully-encapsulated functions wherever possible, or, if necessary, assign to a specific environment with assign()
. Recall that you can create an environment at the desired scope with new.env()
.
:::
. It accesses non-exported functions inside packages. Code relying on these is likely to break in future versions of the package because the functions are not part of the public interface and may be changed or removed by the maintainers without notice. Use public functions via ::
instead.
<<-
. It assigns outside the current environment in a way that can be hard to reason about. Prefer fully-encapsulated functions wherever possible, or, if necessary, assign to a specific environment with assign()
. Recall that you can create an environment at the desired scope with new.env()
.
anyDuplicated(x) > 0
over any(duplicated(x))
anyDuplicated()
exists as a replacement for any(duplicated(.))
, which is
more efficient for simple objects, and is at worst equally efficient.
Therefore, it should be used in all situations instead of the latter.
any_duplicated_linter()
any_duplicated_linter()
Also match usage like length(unique(x$col)) == nrow(x)
, which can
be replaced by anyDuplicated(x$col) == 0L
.
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "any(duplicated(x), na.rm = TRUE)", linters = any_duplicated_linter() ) lint( text = "length(unique(x)) == length(x)", linters = any_duplicated_linter() ) # okay lint( text = "anyDuplicated(x)", linters = any_duplicated_linter() ) lint( text = "anyDuplicated(x) == 0L", linters = any_duplicated_linter() )
# will produce lints lint( text = "any(duplicated(x), na.rm = TRUE)", linters = any_duplicated_linter() ) lint( text = "length(unique(x)) == length(x)", linters = any_duplicated_linter() ) # okay lint( text = "anyDuplicated(x)", linters = any_duplicated_linter() ) lint( text = "anyDuplicated(x) == 0L", linters = any_duplicated_linter() )
anyNA(x)
over any(is.na(x))
anyNA()
exists as a replacement for any(is.na(x))
which is more efficient
for simple objects, and is at worst equally efficient.
Therefore, it should be used in all situations instead of the latter.
any_is_na_linter()
any_is_na_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "any(is.na(x), na.rm = TRUE)", linters = any_is_na_linter() ) lint( text = "any(is.na(foo(x)))", linters = any_is_na_linter() ) # okay lint( text = "anyNA(x)", linters = any_is_na_linter() ) lint( text = "anyNA(foo(x))", linters = any_is_na_linter() ) lint( text = "any(!is.na(x), na.rm = TRUE)", linters = any_is_na_linter() )
# will produce lints lint( text = "any(is.na(x), na.rm = TRUE)", linters = any_is_na_linter() ) lint( text = "any(is.na(foo(x)))", linters = any_is_na_linter() ) # okay lint( text = "anyNA(x)", linters = any_is_na_linter() ) lint( text = "anyNA(foo(x))", linters = any_is_na_linter() ) lint( text = "any(!is.na(x), na.rm = TRUE)", linters = any_is_na_linter() )
Check that <-
is always used for assignment.
assignment_linter( allow_cascading_assign = TRUE, allow_right_assign = FALSE, allow_trailing = TRUE, allow_pipe_assign = FALSE )
assignment_linter( allow_cascading_assign = TRUE, allow_right_assign = FALSE, allow_trailing = TRUE, allow_pipe_assign = FALSE )
allow_cascading_assign |
Logical, default |
allow_right_assign |
Logical, default |
allow_trailing |
Logical, default |
allow_pipe_assign |
Logical, default |
configurable, consistency, default, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x = mean(x)", linters = assignment_linter() ) code_lines <- "1 -> x\n2 ->> y" writeLines(code_lines) lint( text = code_lines, linters = assignment_linter() ) lint( text = "x %<>% as.character()", linters = assignment_linter() ) # okay lint( text = "x <- mean(x)", linters = assignment_linter() ) code_lines <- "x <- 1\ny <<- 2" writeLines(code_lines) lint( text = code_lines, linters = assignment_linter() ) # customizing using arguments code_lines <- "1 -> x\n2 ->> y" writeLines(code_lines) lint( text = code_lines, linters = assignment_linter(allow_right_assign = TRUE) ) lint( text = "x <<- 1", linters = assignment_linter(allow_cascading_assign = FALSE) ) writeLines("foo(bar = \n 1)") lint( text = "foo(bar = \n 1)", linters = assignment_linter(allow_trailing = FALSE) ) lint( text = "x %<>% as.character()", linters = assignment_linter(allow_pipe_assign = TRUE) )
# will produce lints lint( text = "x = mean(x)", linters = assignment_linter() ) code_lines <- "1 -> x\n2 ->> y" writeLines(code_lines) lint( text = code_lines, linters = assignment_linter() ) lint( text = "x %<>% as.character()", linters = assignment_linter() ) # okay lint( text = "x <- mean(x)", linters = assignment_linter() ) code_lines <- "x <- 1\ny <<- 2" writeLines(code_lines) lint( text = code_lines, linters = assignment_linter() ) # customizing using arguments code_lines <- "1 -> x\n2 ->> y" writeLines(code_lines) lint( text = code_lines, linters = assignment_linter(allow_right_assign = TRUE) ) lint( text = "x <<- 1", linters = assignment_linter(allow_cascading_assign = FALSE) ) writeLines("foo(bar = \n 1)") lint( text = "foo(bar = \n 1)", linters = assignment_linter(allow_trailing = FALSE) ) lint( text = "x %<>% as.character()", linters = assignment_linter(allow_pipe_assign = TRUE) )
available_linters()
obtains a tagged list of all Linters available in a package.
available_tags()
searches for available tags.
available_linters(packages = "lintr", tags = NULL, exclude_tags = "deprecated") available_tags(packages = "lintr")
available_linters(packages = "lintr", tags = NULL, exclude_tags = "deprecated") available_tags(packages = "lintr")
packages |
A character vector of packages to search for linters. |
tags |
Optional character vector of tags to search. Only linters with at least one matching tag will be
returned. If |
exclude_tags |
Tags to exclude from the results. Linters with at least one matching tag will not be returned.
If |
available_linters
returns a data frame with columns 'linter', 'package' and 'tags':
A character column naming the function associated with the linter.
A character column containing the name of the package providing the linter.
A list column containing tags associated with the linter.
available_tags
returns a character vector of linter tags used by the packages.
To implement available_linters()
for your package, include a file inst/lintr/linters.csv
in your
package.
The CSV file must contain the columns 'linter' and 'tags', and be UTF-8 encoded.
Additional columns will be silently ignored if present and the columns are identified by name.
Each row describes a linter by
its function name (e.g. "assignment_linter"
) in the column 'linter'.
space-separated tags associated with the linter (e.g. "style consistency default"
) in the column 'tags'.
Tags should be snake_case.
See available_tags("lintr")
to find out what tags are already used by lintr.
linters for a complete list of linters available in lintr.
available_tags()
to retrieve the set of valid tags.
lintr_linters <- available_linters() # If the package doesn't exist or isn't installed, an empty data frame will be returned available_linters("does-not-exist") lintr_linters2 <- available_linters(c("lintr", "does-not-exist")) identical(lintr_linters, lintr_linters2) available_tags()
lintr_linters <- available_linters() # If the package doesn't exist or isn't installed, an empty data frame will be returned available_linters("does-not-exist") lintr_linters2 <- available_linters(c("lintr", "does-not-exist")) identical(lintr_linters, lintr_linters2) available_tags()
Check for usage of unavailable functions. Not reliable for testing r-devel dependencies.
backport_linter(r_version = getRversion(), except = character())
backport_linter(r_version = getRversion(), except = character())
r_version |
Minimum R version to test for compatibility |
except |
Character vector of functions to be excluded from linting.
Use this to list explicitly defined backports, e.g. those imported from the |
configurable, package_development, robustness
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "trimws(x)", linters = backport_linter("3.0.0") ) lint( text = "str2lang(x)", linters = backport_linter("3.2.0") ) # okay lint( text = "trimws(x)", linters = backport_linter("3.6.0") ) lint( text = "str2lang(x)", linters = backport_linter("4.0.0") ) lint( text = "str2lang(x)", linters = backport_linter("3.2.0", except = "str2lang") )
# will produce lints lint( text = "trimws(x)", linters = backport_linter("3.0.0") ) lint( text = "str2lang(x)", linters = backport_linter("3.2.0") ) # okay lint( text = "trimws(x)", linters = backport_linter("3.6.0") ) lint( text = "str2lang(x)", linters = backport_linter("4.0.0") ) lint( text = "str2lang(x)", linters = backport_linter("3.2.0", except = "str2lang") )
Linters checking the use of coding best practices, such as explicit typing of numeric constants.
The following linters are tagged with 'best_practices':
linters for a complete list of linters available in lintr.
length(which(x == y)) == 0
is the same as !any(x == y)
, but the latter
is more readable and more efficient.
boolean_arithmetic_linter()
boolean_arithmetic_linter()
best_practices, efficiency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "length(which(x == y)) == 0L", linters = boolean_arithmetic_linter() ) lint( text = "sum(grepl(pattern, x)) == 0", linters = boolean_arithmetic_linter() ) # okay lint( text = "!any(x == y)", linters = boolean_arithmetic_linter() ) lint( text = "!any(grepl(pattern, x))", linters = boolean_arithmetic_linter() )
# will produce lints lint( text = "length(which(x == y)) == 0L", linters = boolean_arithmetic_linter() ) lint( text = "sum(grepl(pattern, x)) == 0", linters = boolean_arithmetic_linter() ) # okay lint( text = "!any(x == y)", linters = boolean_arithmetic_linter() ) lint( text = "!any(grepl(pattern, x))", linters = boolean_arithmetic_linter() )
Perform various style checks related to placement and spacing of curly braces:
brace_linter(allow_single_line = FALSE)
brace_linter(allow_single_line = FALSE)
allow_single_line |
if |
Opening curly braces are never on their own line and are always followed by a newline.
Opening curly braces have a space before them.
Closing curly braces are on their own line unless they are followed by an else
.
Closing curly braces in if
conditions are on the same line as the corresponding else
.
Either both or neither branch in if
/else
use curly braces, i.e., either both branches use {...}
or neither
does.
Functions spanning multiple lines use curly braces.
configurable, default, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "f <- function() { 1 }", linters = brace_linter() ) writeLines("if (TRUE) {\n return(1) }") lint( text = "if (TRUE) {\n return(1) }", linters = brace_linter() ) # okay writeLines("f <- function() {\n 1\n}") lint( text = "f <- function() {\n 1\n}", linters = brace_linter() ) writeLines("if (TRUE) { \n return(1) \n}") lint( text = "if (TRUE) { \n return(1) \n}", linters = brace_linter() ) # customizing using arguments writeLines("if (TRUE) { return(1) }") lint( text = "if (TRUE) { return(1) }", linters = brace_linter(allow_single_line = TRUE) )
# will produce lints lint( text = "f <- function() { 1 }", linters = brace_linter() ) writeLines("if (TRUE) {\n return(1) }") lint( text = "if (TRUE) {\n return(1) }", linters = brace_linter() ) # okay writeLines("f <- function() {\n 1\n}") lint( text = "f <- function() {\n 1\n}", linters = brace_linter() ) writeLines("if (TRUE) { \n return(1) \n}") lint( text = "if (TRUE) { \n return(1) \n}", linters = brace_linter() ) # customizing using arguments writeLines("if (TRUE) { return(1) }") lint( text = "if (TRUE) { return(1) }", linters = brace_linter(allow_single_line = TRUE) )
Generate a report of the linting results using the Checkstyle XML format.
checkstyle_output(lints, filename = "lintr_results.xml")
checkstyle_output(lints, filename = "lintr_results.xml")
lints |
the linting results. |
filename |
the name of the output report |
==
Usage like class(x) == "character"
is prone to error since class in R
is in general a vector. The correct version for S3 classes is inherits()
:
inherits(x, "character")
. Often, class k
will have an is.
equivalent,
for example is.character()
or is.data.frame()
.
class_equals_linter()
class_equals_linter()
Similar reasoning applies for class(x) %in% "character"
.
best_practices, consistency, robustness
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'is_lm <- class(x) == "lm"', linters = class_equals_linter() ) lint( text = 'if ("lm" %in% class(x)) is_lm <- TRUE', linters = class_equals_linter() ) # okay lint( text = 'is_lm <- inherits(x, "lm")', linters = class_equals_linter() ) lint( text = 'if (inherits(x, "lm")) is_lm <- TRUE', linters = class_equals_linter() )
# will produce lints lint( text = 'is_lm <- class(x) == "lm"', linters = class_equals_linter() ) lint( text = 'if ("lm" %in% class(x)) is_lm <- TRUE', linters = class_equals_linter() ) # okay lint( text = 'is_lm <- inherits(x, "lm")', linters = class_equals_linter() ) lint( text = 'if (inherits(x, "lm")) is_lm <- TRUE', linters = class_equals_linter() )
Clear the lintr cache
clear_cache(file = NULL, path = NULL)
clear_cache(file = NULL, path = NULL)
file |
filename whose cache to clear. If you pass |
path |
directory to store caches. Reads option 'lintr.cache_directory' as the default. |
0 for success, 1 for failure, invisibly.
Check that all commas are followed by spaces, but do not have spaces before them.
commas_linter(allow_trailing = FALSE)
commas_linter(allow_trailing = FALSE)
allow_trailing |
If |
configurable, default, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "switch(op , x = foo, y = bar)", linters = commas_linter() ) lint( text = "mean(x,trim = 0.2,na.rm = TRUE)", linters = commas_linter() ) lint( text = "x[ ,, drop=TRUE]", linters = commas_linter() ) lint( text = "x[1,]", linters = commas_linter() ) # okay lint( text = "switch(op, x = foo, y = bar)", linters = commas_linter() ) lint( text = "switch(op, x = , y = bar)", linters = commas_linter() ) lint( text = "mean(x, trim = 0.2, na.rm = TRUE)", linters = commas_linter() ) lint( text = "a[1, , 2, , 3]", linters = commas_linter() ) lint( text = "x[1,]", linters = commas_linter(allow_trailing = TRUE) )
# will produce lints lint( text = "switch(op , x = foo, y = bar)", linters = commas_linter() ) lint( text = "mean(x,trim = 0.2,na.rm = TRUE)", linters = commas_linter() ) lint( text = "x[ ,, drop=TRUE]", linters = commas_linter() ) lint( text = "x[1,]", linters = commas_linter() ) # okay lint( text = "switch(op, x = foo, y = bar)", linters = commas_linter() ) lint( text = "switch(op, x = , y = bar)", linters = commas_linter() ) lint( text = "mean(x, trim = 0.2, na.rm = TRUE)", linters = commas_linter() ) lint( text = "a[1, , 2, , 3]", linters = commas_linter() ) lint( text = "x[1,]", linters = commas_linter(allow_trailing = TRUE) )
Check that there is no commented code outside roxygen blocks.
commented_code_linter()
commented_code_linter()
best_practices, default, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "# x <- 1", linters = commented_code_linter() ) lint( text = "x <- f() # g()", linters = commented_code_linter() ) lint( text = "x + y # + z[1, 2]", linters = commented_code_linter() ) # okay lint( text = "x <- 1; x <- f(); x + y", linters = commented_code_linter() ) lint( text = "#' x <- 1", linters = commented_code_linter() )
# will produce lints lint( text = "# x <- 1", linters = commented_code_linter() ) lint( text = "x <- f() # g()", linters = commented_code_linter() ) lint( text = "x + y # + z[1, 2]", linters = commented_code_linter() ) # okay lint( text = "x <- 1; x <- f(); x + y", linters = commented_code_linter() ) lint( text = "#' x <- 1", linters = commented_code_linter() )
Linters highlighting common mistakes, such as duplicate arguments.
The following linters are tagged with 'common_mistakes':
linters for a complete list of linters available in lintr.
!(x == y)
is more readably expressed as x != y
. The same is true of
other negations of simple comparisons like !(x > y)
and !(x <= y)
.
comparison_negation_linter()
comparison_negation_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "!x == 2", linters = comparison_negation_linter() ) lint( text = "!(x > 2)", linters = comparison_negation_linter() ) # okay lint( text = "!(x == 2 & y > 2)", linters = comparison_negation_linter() ) lint( text = "!(x & y)", linters = comparison_negation_linter() ) lint( text = "x != 2", linters = comparison_negation_linter() )
# will produce lints lint( text = "!x == 2", linters = comparison_negation_linter() ) lint( text = "!(x > 2)", linters = comparison_negation_linter() ) # okay lint( text = "!(x == 2 & y > 2)", linters = comparison_negation_linter() ) lint( text = "!(x & y)", linters = comparison_negation_linter() ) lint( text = "x != 2", linters = comparison_negation_linter() )
call. = FALSE
in conditionsThis linter, with the default display_call = FALSE
, enforces the
recommendation of the tidyverse design guide regarding displaying error
calls.
condition_call_linter(display_call = FALSE)
condition_call_linter(display_call = FALSE)
display_call |
Logical specifying expected behavior regarding
|
best_practices, configurable, style, tidy_design
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "stop('test')", linters = condition_call_linter() ) lint( text = "stop('test', call. = TRUE)", linters = condition_call_linter() ) lint( text = "stop('test', call. = FALSE)", linters = condition_call_linter(display_call = TRUE) ) lint( text = "stop('this is a', 'test', call. = FALSE)", linters = condition_call_linter(display_call = TRUE) ) # okay lint( text = "stop('test', call. = FALSE)", linters = condition_call_linter() ) lint( text = "stop('this is a', 'test', call. = FALSE)", linters = condition_call_linter() ) lint( text = "stop('test', call. = TRUE)", linters = condition_call_linter(display_call = TRUE) )
# will produce lints lint( text = "stop('test')", linters = condition_call_linter() ) lint( text = "stop('test', call. = TRUE)", linters = condition_call_linter() ) lint( text = "stop('test', call. = FALSE)", linters = condition_call_linter(display_call = TRUE) ) lint( text = "stop('this is a', 'test', call. = FALSE)", linters = condition_call_linter(display_call = TRUE) ) # okay lint( text = "stop('test', call. = FALSE)", linters = condition_call_linter() ) lint( text = "stop('this is a', 'test', call. = FALSE)", linters = condition_call_linter() ) lint( text = "stop('test', call. = TRUE)", linters = condition_call_linter(display_call = TRUE) )
paste()
and paste0()
with messaging functions using ...
This linter discourages combining condition functions like stop()
with string concatenation
functions paste()
and paste0()
. This is because
stop(paste0(...))
is redundant as it is exactly equivalent to stop(...)
stop(paste(...))
is similarly equivalent to stop(...)
with separators (see examples)
The same applies to the other default condition functions as well, i.e., warning()
, message()
,
and packageStartupMessage()
.
condition_message_linter()
condition_message_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'stop(paste("a string", "another"))', linters = condition_message_linter() ) lint( text = 'warning(paste0("a string", " another"))', linters = condition_message_linter() ) # okay lint( text = 'stop("a string", " another")', linters = condition_message_linter() ) lint( text = 'warning("a string", " another")', linters = condition_message_linter() ) lint( text = 'warning(paste("a string", "another", sep = "-"))', linters = condition_message_linter() )
# will produce lints lint( text = 'stop(paste("a string", "another"))', linters = condition_message_linter() ) lint( text = 'warning(paste0("a string", " another"))', linters = condition_message_linter() ) # okay lint( text = 'stop("a string", " another")', linters = condition_message_linter() ) lint( text = 'warning("a string", " another")', linters = condition_message_linter() ) lint( text = 'warning(paste("a string", "another", sep = "-"))', linters = condition_message_linter() )
Generic linters which support custom configuration to your needs.
The following linters are tagged with 'configurable':
linters for a complete list of linters available in lintr.
&&
conditions to be written separately where appropriateFor readability of test outputs, testing only one thing per call to
testthat::expect_true()
is preferable, i.e.,
expect_true(A); expect_true(B)
is better than expect_true(A && B)
, and
expect_false(A); expect_false(B)
is better than expect_false(A || B)
.
conjunct_test_linter( allow_named_stopifnot = TRUE, allow_filter = c("never", "not_dplyr", "always") )
conjunct_test_linter( allow_named_stopifnot = TRUE, allow_filter = c("never", "not_dplyr", "always") )
allow_named_stopifnot |
Logical, |
allow_filter |
Character naming the method for linting calls to |
Similar reasoning applies to &&
usage inside stopifnot()
and assertthat::assert_that()
calls.
Relatedly, dplyr::filter(DF, A & B)
is the same as dplyr::filter(DF, A, B)
, but the latter will be more readable
/ easier to format for long conditions. Note that this linter assumes usages of filter()
are dplyr::filter()
;
if you're using another function named filter()
, e.g. stats::filter()
, please namespace-qualify it to avoid
false positives. You can omit linting filter()
expressions altogether via allow_filter = TRUE
.
best_practices, configurable, package_development, pkg_testthat, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "expect_true(x && y)", linters = conjunct_test_linter() ) lint( text = "expect_false(x || (y && z))", linters = conjunct_test_linter() ) lint( text = "stopifnot('x must be a logical scalar' = length(x) == 1 && is.logical(x) && !is.na(x))", linters = conjunct_test_linter(allow_named_stopifnot = FALSE) ) lint( text = "dplyr::filter(mtcars, mpg > 20 & vs == 0)", linters = conjunct_test_linter() ) lint( text = "filter(mtcars, mpg > 20 & vs == 0)", linters = conjunct_test_linter() ) # okay lint( text = "expect_true(x || (y && z))", linters = conjunct_test_linter() ) lint( text = 'stopifnot("x must be a logical scalar" = length(x) == 1 && is.logical(x) && !is.na(x))', linters = conjunct_test_linter(allow_named_stopifnot = TRUE) ) lint( text = "dplyr::filter(mtcars, mpg > 20 & vs == 0)", linters = conjunct_test_linter(allow_filter = "always") ) lint( text = "filter(mtcars, mpg > 20 & vs == 0)", linters = conjunct_test_linter(allow_filter = "not_dplyr") ) lint( text = "stats::filter(mtcars$cyl, mtcars$mpg > 20 & mtcars$vs == 0)", linters = conjunct_test_linter() )
# will produce lints lint( text = "expect_true(x && y)", linters = conjunct_test_linter() ) lint( text = "expect_false(x || (y && z))", linters = conjunct_test_linter() ) lint( text = "stopifnot('x must be a logical scalar' = length(x) == 1 && is.logical(x) && !is.na(x))", linters = conjunct_test_linter(allow_named_stopifnot = FALSE) ) lint( text = "dplyr::filter(mtcars, mpg > 20 & vs == 0)", linters = conjunct_test_linter() ) lint( text = "filter(mtcars, mpg > 20 & vs == 0)", linters = conjunct_test_linter() ) # okay lint( text = "expect_true(x || (y && z))", linters = conjunct_test_linter() ) lint( text = 'stopifnot("x must be a logical scalar" = length(x) == 1 && is.logical(x) && !is.na(x))', linters = conjunct_test_linter(allow_named_stopifnot = TRUE) ) lint( text = "dplyr::filter(mtcars, mpg > 20 & vs == 0)", linters = conjunct_test_linter(allow_filter = "always") ) lint( text = "filter(mtcars, mpg > 20 & vs == 0)", linters = conjunct_test_linter(allow_filter = "not_dplyr") ) lint( text = "stats::filter(mtcars$cyl, mtcars$mpg > 20 & mtcars$vs == 0)", linters = conjunct_test_linter() )
stopifnot()
accepts any number of tests, so sequences like
stopifnot(x); stopifnot(y)
are redundant. Ditto for tests using
assertthat::assert_that()
without specifying msg=
.
consecutive_assertion_linter()
consecutive_assertion_linter()
consistency, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "stopifnot(x); stopifnot(y)", linters = consecutive_assertion_linter() ) lint( text = "assert_that(x); assert_that(y)", linters = consecutive_assertion_linter() ) # okay lint( text = "stopifnot(x, y)", linters = consecutive_assertion_linter() ) lint( text = 'assert_that(x, msg = "Bad x!"); assert_that(y)', linters = consecutive_assertion_linter() )
# will produce lints lint( text = "stopifnot(x); stopifnot(y)", linters = consecutive_assertion_linter() ) lint( text = "assert_that(x); assert_that(y)", linters = consecutive_assertion_linter() ) # okay lint( text = "stopifnot(x, y)", linters = consecutive_assertion_linter() ) lint( text = 'assert_that(x, msg = "Bad x!"); assert_that(y)', linters = consecutive_assertion_linter() )
dplyr::mutate()
accepts any number of columns, so sequences like
DF %>% dplyr::mutate(..1) %>% dplyr::mutate(..2)
are redundant –
they can always be expressed with a single call to dplyr::mutate()
.
consecutive_mutate_linter(invalid_backends = "dbplyr")
consecutive_mutate_linter(invalid_backends = "dbplyr")
invalid_backends |
Character vector of packages providing dplyr backends
which may not be compatible with combining |
An exception is for some SQL back-ends, where the translation logic may not be
as sophisticated as that in the default dplyr
, for example in
DF %>% mutate(a = a + 1) %>% mutate(b = a - 2)
.
configurable, consistency, efficiency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x %>% mutate(a = 1) %>% mutate(b = 2)", linters = consecutive_mutate_linter() ) # okay lint( text = "x %>% mutate(a = 1, b = 2)", linters = consecutive_mutate_linter() ) code <- "library(dbplyr)\nx %>% mutate(a = 1) %>% mutate(a = a + 1)" writeLines(code) lint( text = code, linters = consecutive_mutate_linter() )
# will produce lints lint( text = "x %>% mutate(a = 1) %>% mutate(b = 2)", linters = consecutive_mutate_linter() ) # okay lint( text = "x %>% mutate(a = 1, b = 2)", linters = consecutive_mutate_linter() ) code <- "library(dbplyr)\nx %>% mutate(a = 1) %>% mutate(a = a + 1)" writeLines(code) lint( text = code, linters = consecutive_mutate_linter() )
Linters checking enforcing a consistent alternative if there are multiple syntactically valid ways to write something.
The following linters are tagged with 'consistency':
linters for a complete list of linters available in lintr.
Linters highlighting possible programming mistakes, such as unused variables.
The following linters are tagged with 'correctness':
linters for a complete list of linters available in lintr.
Check for overly complicated expressions. See cyclocomp()
function from {cyclocomp}
.
cyclocomp_linter(complexity_limit = 15L)
cyclocomp_linter(complexity_limit = 15L)
complexity_limit |
Maximum cyclomatic complexity, default |
best_practices, configurable, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "if (TRUE) 1 else 2", linters = cyclocomp_linter(complexity_limit = 1L) ) # okay lint( text = "if (TRUE) 1 else 2", linters = cyclocomp_linter(complexity_limit = 2L) )
# will produce lints lint( text = "if (TRUE) 1 else 2", linters = cyclocomp_linter(complexity_limit = 1L) ) # okay lint( text = "if (TRUE) 1 else 2", linters = cyclocomp_linter(complexity_limit = 2L) )
List of default linters for lint()
. Use
linters_with_defaults()
to customize it. Most of the default linters
are based on the tidyverse style guide.
The set of default linters is as follows (any parameterized linters, e.g., line_length_linter
use their default
argument(s), see ?<linter_name>
for details):
default_linters
default_linters
An object of class list
of length 25.
The following linters are tagged with 'default':
linters for a complete list of linters available in lintr.
The default settings consist of
linters
: a list of default linters (see default_linters()
)
encoding
: the character encoding assumed for the file
exclude
: pattern used to exclude a line of code
exclude_start
, exclude_end
: patterns used to mark start and end of the code block to exclude
exclude_linter
, exclude_linter_sep
: patterns used to exclude linters
exclusions
: a list of exclusions, see exclude()
for a complete description of valid values.
cache_directory
: location of cache directory
comment_token
: a GitHub token character
error_on_lint
: decides if error should be produced when any lints are found
There are no settings without defaults, i.e., this list describes every valid setting.
default_settings
default_settings
An object of class list
of length 12.
read_settings()
, default_linters
# available settings names(default_settings) # linters included by default names(default_settings$linters) # default values for a few of the other settings default_settings[c( "encoding", "exclude", "exclude_start", "exclude_end", "exclude_linter", "exclude_linter_sep", "exclusions", "error_on_lint" )]
# available settings names(default_settings) # linters included by default names(default_settings$linters) # default values for a few of the other settings default_settings[c( "encoding", "exclude", "exclude_start", "exclude_end", "exclude_linter", "exclude_linter_sep", "exclusions", "error_on_lint" )]
Linters that are deprecated and provided for backwards compatibility only.
These linters will be excluded from linters_with_tags()
by default.
The following linters are tagged with 'deprecated':
linters for a complete list of linters available in lintr.
Check for duplicate arguments in function calls. Some cases are run-time errors
(e.g. mean(x = 1:5, x = 2:3)
), otherwise this linter is used to discourage
explicitly providing duplicate names to objects (e.g. c(a = 1, a = 2)
).
Duplicate-named objects are hard to work with programmatically and
should typically be avoided.
duplicate_argument_linter(except = c("mutate", "transmute"))
duplicate_argument_linter(except = c("mutate", "transmute"))
except |
A character vector of function names as exceptions. Defaults to
functions that allow sequential updates to variables, currently |
common_mistakes, configurable, correctness
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "list(x = 1, x = 2)", linters = duplicate_argument_linter() ) lint( text = "fun(arg = 1, arg = 2)", linters = duplicate_argument_linter() ) # okay lint( text = "list(x = 1, x = 2)", linters = duplicate_argument_linter(except = "list") ) lint( text = "df %>% dplyr::mutate(x = a + b, x = x + d)", linters = duplicate_argument_linter() )
# will produce lints lint( text = "list(x = 1, x = 2)", linters = duplicate_argument_linter() ) lint( text = "fun(arg = 1, arg = 2)", linters = duplicate_argument_linter() ) # okay lint( text = "list(x = 1, x = 2)", linters = duplicate_argument_linter(except = "list") ) lint( text = "df %>% dplyr::mutate(x = a + b, x = x + d)", linters = duplicate_argument_linter() )
Linters highlighting code efficiency problems, such as unnecessary function calls.
The following linters are tagged with 'efficiency':
linters for a complete list of linters available in lintr.
{}
Assignment of {}
is the same as assignment of NULL
; use the latter
for clarity. Closely related: unnecessary_concatenation_linter()
.
empty_assignment_linter()
empty_assignment_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x <- {}", linters = empty_assignment_linter() ) writeLines("x = {\n}") lint( text = "x = {\n}", linters = empty_assignment_linter() ) # okay lint( text = "x <- { 3 + 4 }", linters = empty_assignment_linter() ) lint( text = "x <- NULL", linters = empty_assignment_linter() )
# will produce lints lint( text = "x <- {}", linters = empty_assignment_linter() ) writeLines("x = {\n}") lint( text = "x = {\n}", linters = empty_assignment_linter() ) # okay lint( text = "x <- { 3 + 4 }", linters = empty_assignment_linter() ) lint( text = "x <- NULL", linters = empty_assignment_linter() )
Check for x == NA
, x != NA
and x %in% NA
. Such usage is almost surely incorrect –
checks for missing values should be done with is.na()
.
equals_na_linter()
equals_na_linter()
common_mistakes, correctness, default, robustness
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x == NA", linters = equals_na_linter() ) lint( text = "x != NA", linters = equals_na_linter() ) lint( text = "x %in% NA", linters = equals_na_linter() ) # okay lint( text = "is.na(x)", linters = equals_na_linter() ) lint( text = "!is.na(x)", linters = equals_na_linter() )
# will produce lints lint( text = "x == NA", linters = equals_na_linter() ) lint( text = "x != NA", linters = equals_na_linter() ) lint( text = "x %in% NA", linters = equals_na_linter() ) # okay lint( text = "is.na(x)", linters = equals_na_linter() ) lint( text = "!is.na(x)", linters = equals_na_linter() )
Linters that evaluate parts of the linted code, such as loading referenced packages.
These linters should not be used with untrusted code, and may need dependencies of the linted package or project to
be available in order to function correctly. For package authors, note that this includes loading the package itself,
e.g. with pkgload::load_all()
or installing and attaching the package.
The following linters are tagged with 'executing':
linters for a complete list of linters available in lintr.
expect_gt(x, y)
over expect_true(x > y)
(and similar)testthat::expect_gt()
, testthat::expect_gte()
, testthat::expect_lt()
,
testthat::expect_lte()
, and testthat::expect_equal()
exist specifically
for testing comparisons between two objects. testthat::expect_true()
can
also be used for such tests, but it is better to use the tailored function
instead.
expect_comparison_linter()
expect_comparison_linter()
best_practices, package_development, pkg_testthat
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "expect_true(x > y)", linters = expect_comparison_linter() ) lint( text = "expect_true(x <= y)", linters = expect_comparison_linter() ) lint( text = "expect_true(x == (y == 2))", linters = expect_comparison_linter() ) # okay lint( text = "expect_gt(x, y)", linters = expect_comparison_linter() ) lint( text = "expect_lte(x, y)", linters = expect_comparison_linter() ) lint( text = "expect_identical(x, y == 2)", linters = expect_comparison_linter() ) lint( text = "expect_true(x < y | x > y^2)", linters = expect_comparison_linter() )
# will produce lints lint( text = "expect_true(x > y)", linters = expect_comparison_linter() ) lint( text = "expect_true(x <= y)", linters = expect_comparison_linter() ) lint( text = "expect_true(x == (y == 2))", linters = expect_comparison_linter() ) # okay lint( text = "expect_gt(x, y)", linters = expect_comparison_linter() ) lint( text = "expect_lte(x, y)", linters = expect_comparison_linter() ) lint( text = "expect_identical(x, y == 2)", linters = expect_comparison_linter() ) lint( text = "expect_true(x < y | x > y^2)", linters = expect_comparison_linter() )
expect_identical(x, y)
where appropriateThis linter enforces the usage of testthat::expect_identical()
as the
default expectation for comparisons in a testthat suite. expect_true(identical(x, y))
is an equivalent but unadvised method of the same test. Further,
testthat::expect_equal()
should only be used when expect_identical()
is inappropriate, i.e., when x
and y
need only be numerically
equivalent instead of fully identical (in which case, provide the
tolerance=
argument to expect_equal()
explicitly). This also applies
when it's inconvenient to check full equality (e.g., names can be ignored,
in which case ignore_attr = "names"
should be supplied to
expect_equal()
(or, for 2nd edition, check.attributes = FALSE
).
expect_identical_linter()
expect_identical_linter()
The linter allows expect_equal()
in three circumstances:
A named argument is set (e.g. ignore_attr
or tolerance
)
Comparison is made to an explicit decimal, e.g.
expect_equal(x, 1.0)
(implicitly setting tolerance
)
...
is passed (wrapper functions which might set
arguments such as ignore_attr
or tolerance
)
package_development, pkg_testthat
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "expect_equal(x, y)", linters = expect_identical_linter() ) lint( text = "expect_true(identical(x, y))", linters = expect_identical_linter() ) # okay lint( text = "expect_identical(x, y)", linters = expect_identical_linter() ) lint( text = "expect_equal(x, y, check.attributes = FALSE)", linters = expect_identical_linter() ) lint( text = "expect_equal(x, y, tolerance = 1e-6)", linters = expect_identical_linter() )
# will produce lints lint( text = "expect_equal(x, y)", linters = expect_identical_linter() ) lint( text = "expect_true(identical(x, y))", linters = expect_identical_linter() ) # okay lint( text = "expect_identical(x, y)", linters = expect_identical_linter() ) lint( text = "expect_equal(x, y, check.attributes = FALSE)", linters = expect_identical_linter() ) lint( text = "expect_equal(x, y, tolerance = 1e-6)", linters = expect_identical_linter() )
expect_length(x, n)
over expect_equal(length(x), n)
testthat::expect_length()
exists specifically for testing the length()
of
an object. testthat::expect_equal()
can also be used for such tests,
but it is better to use the tailored function instead.
expect_length_linter()
expect_length_linter()
best_practices, package_development, pkg_testthat, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "expect_equal(length(x), 2L)", linters = expect_length_linter() ) # okay lint( text = "expect_length(x, 2L)", linters = expect_length_linter() )
# will produce lints lint( text = "expect_equal(length(x), 2L)", linters = expect_length_linter() ) # okay lint( text = "expect_length(x, 2L)", linters = expect_length_linter() )
These are expectation functions to test specified linters on sample code in the testthat
testing framework.
expect_lint
asserts that specified lints are generated.
expect_no_lint
asserts that no lints are generated.
expect_lint(content, checks, ..., file = NULL, language = "en") expect_no_lint(content, ..., file = NULL, language = "en")
expect_lint(content, checks, ..., file = NULL, language = "en") expect_no_lint(content, ..., file = NULL, language = "en")
content |
a character vector for the file content to be linted, each vector element representing a line of text. |
checks |
checks to be performed:
Named vectors are also accepted instead of named lists, but this is a compatibility feature that is not recommended for new code. |
... |
arguments passed to |
file |
if not |
language |
temporarily override Rs |
NULL
, invisibly.
# no expected lint expect_no_lint("a", trailing_blank_lines_linter()) # one expected lint expect_lint("a\n", "trailing blank", trailing_blank_lines_linter()) expect_lint("a\n", list(message = "trailing blank", line_number = 2), trailing_blank_lines_linter()) # several expected lints expect_lint("a\n\n", list("trailing blank", "trailing blank"), trailing_blank_lines_linter()) expect_lint( "a\n\n", list( list(message = "trailing blank", line_number = 2), list(message = "trailing blank", line_number = 3) ), trailing_blank_lines_linter() )
# no expected lint expect_no_lint("a", trailing_blank_lines_linter()) # one expected lint expect_lint("a\n", "trailing blank", trailing_blank_lines_linter()) expect_lint("a\n", list(message = "trailing blank", line_number = 2), trailing_blank_lines_linter()) # several expected lints expect_lint("a\n\n", list("trailing blank", "trailing blank"), trailing_blank_lines_linter()) expect_lint( "a\n\n", list( list(message = "trailing blank", line_number = 2), list(message = "trailing blank", line_number = 3) ), trailing_blank_lines_linter() )
This function is a thin wrapper around lint_package that simply tests there are no lints in the package. It can be used to ensure that your tests fail if the package contains lints.
expect_lint_free(...)
expect_lint_free(...)
... |
arguments passed to |
expect_named(x, n)
over expect_equal(names(x), n)
testthat::expect_named()
exists specifically for testing the names()
of
an object. testthat::expect_equal()
can also be used for such tests,
but it is better to use the tailored function instead.
expect_named_linter()
expect_named_linter()
best_practices, package_development, pkg_testthat, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'expect_equal(names(x), "a")', linters = expect_named_linter() ) # okay lint( text = 'expect_named(x, "a")', linters = expect_named_linter() ) lint( text = 'expect_equal(colnames(x), "a")', linters = expect_named_linter() ) lint( text = 'expect_equal(dimnames(x), "a")', linters = expect_named_linter() )
# will produce lints lint( text = 'expect_equal(names(x), "a")', linters = expect_named_linter() ) # okay lint( text = 'expect_named(x, "a")', linters = expect_named_linter() ) lint( text = 'expect_equal(colnames(x), "a")', linters = expect_named_linter() ) lint( text = 'expect_equal(dimnames(x), "a")', linters = expect_named_linter() )
expect_false(x)
over expect_true(!x)
testthat::expect_false()
exists specifically for testing that an output is
FALSE
. testthat::expect_true()
can also be used for such tests by
negating the output, but it is better to use the tailored function instead.
The reverse is also true – use expect_false(A)
instead of
expect_true(!A)
.
expect_not_linter()
expect_not_linter()
best_practices, package_development, pkg_testthat, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "expect_true(!x)", linters = expect_not_linter() ) # okay lint( text = "expect_false(x)", linters = expect_not_linter() )
# will produce lints lint( text = "expect_true(!x)", linters = expect_not_linter() ) # okay lint( text = "expect_false(x)", linters = expect_not_linter() )
expect_null
for checking NULL
Require usage of expect_null(x)
over expect_equal(x, NULL)
and similar
usages.
expect_null_linter()
expect_null_linter()
testthat::expect_null()
exists specifically for testing for NULL
objects.
testthat::expect_equal()
, testthat::expect_identical()
, and
testthat::expect_true()
can also be used for such tests,
but it is better to use the tailored function instead.
best_practices, package_development, pkg_testthat
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "expect_equal(x, NULL)", linters = expect_null_linter() ) lint( text = "expect_identical(x, NULL)", linters = expect_null_linter() ) lint( text = "expect_true(is.null(x))", linters = expect_null_linter() ) # okay lint( text = "expect_null(x)", linters = expect_null_linter() )
# will produce lints lint( text = "expect_equal(x, NULL)", linters = expect_null_linter() ) lint( text = "expect_identical(x, NULL)", linters = expect_null_linter() ) lint( text = "expect_true(is.null(x))", linters = expect_null_linter() ) # okay lint( text = "expect_null(x)", linters = expect_null_linter() )
expect_s3_class()
testthat::expect_s3_class()
exists specifically for testing the class
of S3 objects. testthat::expect_equal()
, testthat::expect_identical()
,
and testthat::expect_true()
can also be used for such tests,
but it is better to use the tailored function instead.
expect_s3_class_linter()
expect_s3_class_linter()
best_practices, package_development, pkg_testthat
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'expect_equal(class(x), "data.frame")', linters = expect_s3_class_linter() ) lint( text = 'expect_equal(class(x), "numeric")', linters = expect_s3_class_linter() ) # okay lint( text = 'expect_s3_class(x, "data.frame")', linters = expect_s3_class_linter() ) lint( text = 'expect_type(x, "double")', linters = expect_s3_class_linter() )
# will produce lints lint( text = 'expect_equal(class(x), "data.frame")', linters = expect_s3_class_linter() ) lint( text = 'expect_equal(class(x), "numeric")', linters = expect_s3_class_linter() ) # okay lint( text = 'expect_s3_class(x, "data.frame")', linters = expect_s3_class_linter() ) lint( text = 'expect_type(x, "double")', linters = expect_s3_class_linter() )
expect_s4_class(x, k)
over expect_true(is(x, k))
testthat::expect_s4_class()
exists specifically for testing the class
of S4 objects. testthat::expect_true()
can also be used for such tests,
but it is better to use the tailored function instead.
expect_s4_class_linter()
expect_s4_class_linter()
best_practices, package_development, pkg_testthat
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'expect_true(is(x, "Matrix"))', linters = expect_s4_class_linter() ) # okay lint( text = 'expect_s4_class(x, "Matrix")', linters = expect_s4_class_linter() )
# will produce lints lint( text = 'expect_true(is(x, "Matrix"))', linters = expect_s4_class_linter() ) # okay lint( text = 'expect_s4_class(x, "Matrix")', linters = expect_s4_class_linter() )
expect_true(x)
over expect_equal(x, TRUE)
testthat::expect_true()
and testthat::expect_false()
exist specifically
for testing the TRUE
/FALSE
value of an object.
testthat::expect_equal()
and testthat::expect_identical()
can also be
used for such tests, but it is better to use the tailored function instead.
expect_true_false_linter()
expect_true_false_linter()
best_practices, package_development, pkg_testthat, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "expect_equal(x, TRUE)", linters = expect_true_false_linter() ) lint( text = "expect_equal(x, FALSE)", linters = expect_true_false_linter() ) # okay lint( text = "expect_true(x)", linters = expect_true_false_linter() ) lint( text = "expect_false(x)", linters = expect_true_false_linter() )
# will produce lints lint( text = "expect_equal(x, TRUE)", linters = expect_true_false_linter() ) lint( text = "expect_equal(x, FALSE)", linters = expect_true_false_linter() ) # okay lint( text = "expect_true(x)", linters = expect_true_false_linter() ) lint( text = "expect_false(x)", linters = expect_true_false_linter() )
expect_type(x, type)
over expect_equal(typeof(x), type)
testthat::expect_type()
exists specifically for testing the storage type
of objects. testthat::expect_equal()
, testthat::expect_identical()
, and
testthat::expect_true()
can also be used for such tests,
but it is better to use the tailored function instead.
expect_type_linter()
expect_type_linter()
best_practices, package_development, pkg_testthat
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'expect_equal(typeof(x), "double")', linters = expect_type_linter() ) lint( text = 'expect_identical(typeof(x), "double")', linters = expect_type_linter() ) # okay lint( text = 'expect_type(x, "double")', linters = expect_type_linter() )
# will produce lints lint( text = 'expect_equal(typeof(x), "double")', linters = expect_type_linter() ) lint( text = 'expect_identical(typeof(x), "double")', linters = expect_type_linter() ) # okay lint( text = 'expect_type(x, "double")', linters = expect_type_linter() )
fixed=TRUE
in regular expressions where appropriateInvoking a regular expression engine is overkill for cases when the search pattern only involves static patterns.
fixed_regex_linter(allow_unescaped = FALSE)
fixed_regex_linter(allow_unescaped = FALSE)
allow_unescaped |
Logical, default |
NB: for stringr
functions, that means wrapping the pattern in stringr::fixed()
.
NB: this linter is likely not able to distinguish every possible case when a fixed regular expression is preferable, rather it seeks to identify likely cases. It should never report false positives, however; please report false positives as an error.
best_practices, configurable, efficiency, readability, regex
linters for a complete list of linters available in lintr.
# will produce lints code_lines <- 'gsub("\\\\.", "", x)' writeLines(code_lines) lint( text = code_lines, linters = fixed_regex_linter() ) lint( text = 'grepl("a[*]b", x)', linters = fixed_regex_linter() ) lint( text = 'grepl("a[*]b", x)', linters = fixed_regex_linter(allow_unescaped = TRUE) ) code_lines <- 'stringr::str_subset(x, "\\\\$")' writeLines(code_lines) lint( text = code_lines, linters = fixed_regex_linter() ) lint( text = 'grepl("Munich", address)', linters = fixed_regex_linter() ) # okay code_lines <- 'gsub("\\\\.", "", x, fixed = TRUE)' writeLines(code_lines) lint( text = code_lines, linters = fixed_regex_linter() ) lint( text = 'grepl("a*b", x, fixed = TRUE)', linters = fixed_regex_linter() ) lint( text = 'stringr::str_subset(x, stringr::fixed("$"))', linters = fixed_regex_linter() ) lint( text = 'grepl("Munich", address, fixed = TRUE)', linters = fixed_regex_linter() ) lint( text = 'grepl("Munich", address)', linters = fixed_regex_linter(allow_unescaped = TRUE) )
# will produce lints code_lines <- 'gsub("\\\\.", "", x)' writeLines(code_lines) lint( text = code_lines, linters = fixed_regex_linter() ) lint( text = 'grepl("a[*]b", x)', linters = fixed_regex_linter() ) lint( text = 'grepl("a[*]b", x)', linters = fixed_regex_linter(allow_unescaped = TRUE) ) code_lines <- 'stringr::str_subset(x, "\\\\$")' writeLines(code_lines) lint( text = code_lines, linters = fixed_regex_linter() ) lint( text = 'grepl("Munich", address)', linters = fixed_regex_linter() ) # okay code_lines <- 'gsub("\\\\.", "", x, fixed = TRUE)' writeLines(code_lines) lint( text = code_lines, linters = fixed_regex_linter() ) lint( text = 'grepl("a*b", x, fixed = TRUE)', linters = fixed_regex_linter() ) lint( text = 'stringr::str_subset(x, stringr::fixed("$"))', linters = fixed_regex_linter() ) lint( text = 'grepl("Munich", address, fixed = TRUE)', linters = fixed_regex_linter() ) lint( text = 'grepl("Munich", address)', linters = fixed_regex_linter(allow_unescaped = TRUE) )
for (x in x)
is a poor choice of indexing variable. This overwrites
x
in the calling scope and is confusing to read.
for_loop_index_linter()
for_loop_index_linter()
best_practices, readability, robustness
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "for (x in x) { TRUE }", linters = for_loop_index_linter() ) lint( text = "for (x in foo(x, y)) { TRUE }", linters = for_loop_index_linter() ) # okay lint( text = "for (xi in x) { TRUE }", linters = for_loop_index_linter() ) lint( text = "for (col in DF$col) { TRUE }", linters = for_loop_index_linter() )
# will produce lints lint( text = "for (x in x) { TRUE }", linters = for_loop_index_linter() ) lint( text = "for (x in foo(x, y)) { TRUE }", linters = for_loop_index_linter() ) # okay lint( text = "for (xi in x) { TRUE }", linters = for_loop_index_linter() ) lint( text = "for (col in DF$col) { TRUE }", linters = for_loop_index_linter() )
Check that arguments with defaults come last in all function declarations, as per the tidyverse design guide.
Changing the argument order can be a breaking change. An alternative to changing the argument order
is to instead set the default for such arguments to NULL
.
function_argument_linter()
function_argument_linter()
best_practices, consistency, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "function(y = 1, z = 2, x) {}", linters = function_argument_linter() ) lint( text = "function(x, y, z = 1, ..., w) {}", linters = function_argument_linter() ) # okay lint( text = "function(x, y = 1, z = 2) {}", linters = function_argument_linter() ) lint( text = "function(x, y, w, z = 1, ...) {}", linters = function_argument_linter() ) lint( text = "function(y = 1, z = 2, x = NULL) {}", linters = function_argument_linter() ) lint( text = "function(x, y, z = 1, ..., w = NULL) {}", linters = function_argument_linter() )
# will produce lints lint( text = "function(y = 1, z = 2, x) {}", linters = function_argument_linter() ) lint( text = "function(x, y, z = 1, ..., w) {}", linters = function_argument_linter() ) # okay lint( text = "function(x, y = 1, z = 2) {}", linters = function_argument_linter() ) lint( text = "function(x, y, w, z = 1, ...) {}", linters = function_argument_linter() ) lint( text = "function(y = 1, z = 2, x = NULL) {}", linters = function_argument_linter() ) lint( text = "function(x, y, z = 1, ..., w = NULL) {}", linters = function_argument_linter() )
Check that all left parentheses in a function call do not have spaces before them
(e.g. mean (1:3)
). Although this is syntactically valid, it makes the code
difficult to read.
function_left_parentheses_linter()
function_left_parentheses_linter()
Exceptions are made for control flow functions (if
, for
, etc.).
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "mean (x)", linters = function_left_parentheses_linter() ) lint( text = "stats::sd(c (x, y, z))", linters = function_left_parentheses_linter() ) # okay lint( text = "mean(x)", linters = function_left_parentheses_linter() ) lint( text = "stats::sd(c(x, y, z))", linters = function_left_parentheses_linter() ) lint( text = "foo <- function(x) (x + 1)", linters = function_left_parentheses_linter() )
# will produce lints lint( text = "mean (x)", linters = function_left_parentheses_linter() ) lint( text = "stats::sd(c (x, y, z))", linters = function_left_parentheses_linter() ) # okay lint( text = "mean(x)", linters = function_left_parentheses_linter() ) lint( text = "stats::sd(c(x, y, z))", linters = function_left_parentheses_linter() ) lint( text = "foo <- function(x) (x + 1)", linters = function_left_parentheses_linter() )
return(x <- ...)
is either distracting (because x
is ignored), or
confusing (because assigning to x
has some side effect that is muddled
by the dual-purpose expression).
function_return_linter()
function_return_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "foo <- function(x) return(y <- x + 1)", linters = function_return_linter() ) lint( text = "foo <- function(x) return(x <<- x + 1)", linters = function_return_linter() ) writeLines("e <- new.env() \nfoo <- function(x) return(e$val <- x + 1)") lint( text = "e <- new.env() \nfoo <- function(x) return(e$val <- x + 1)", linters = function_return_linter() ) # okay lint( text = "foo <- function(x) return(x + 1)", linters = function_return_linter() ) code_lines <- " foo <- function(x) { x <<- x + 1 return(x) } " lint( text = code_lines, linters = function_return_linter() ) code_lines <- " e <- new.env() foo <- function(x) { e$val <- x + 1 return(e$val) } " writeLines(code_lines) lint( text = code_lines, linters = function_return_linter() )
# will produce lints lint( text = "foo <- function(x) return(y <- x + 1)", linters = function_return_linter() ) lint( text = "foo <- function(x) return(x <<- x + 1)", linters = function_return_linter() ) writeLines("e <- new.env() \nfoo <- function(x) return(e$val <- x + 1)") lint( text = "e <- new.env() \nfoo <- function(x) return(e$val <- x + 1)", linters = function_return_linter() ) # okay lint( text = "foo <- function(x) return(x + 1)", linters = function_return_linter() ) code_lines <- " foo <- function(x) { x <<- x + 1 return(x) } " lint( text = code_lines, linters = function_return_linter() ) code_lines <- " e <- new.env() foo <- function(x) { e$val <- x + 1 return(e$val) } " writeLines(code_lines) lint( text = code_lines, linters = function_return_linter() )
STR_CONST
nodesConvert STR_CONST
text()
values into R strings. This is useful to account for arbitrary
character literals, e.g. R"------[hello]------"
, which is parsed in R as "hello"
.
It is quite cumbersome to write XPaths allowing for strings like this, so whenever your
linter logic requires testing a STR_CONST
node's value, use this function.
NB: this is also properly vectorized on s
, and accepts a variety of inputs. Empty inputs
will become NA
outputs, which helps ensure that length(get_r_string(s)) == length(s)
.
get_r_string(s, xpath = NULL)
get_r_string(s, xpath = NULL)
s |
An input string or strings. If |
xpath |
An XPath, passed on to |
tmp <- tempfile() writeLines("c('a', 'b')", tmp) expr_as_xml <- get_source_expressions(tmp)$expressions[[1L]]$xml_parsed_content writeLines(as.character(expr_as_xml)) get_r_string(expr_as_xml, "expr[2]") get_r_string(expr_as_xml, "expr[3]") unlink(tmp) # more importantly, extract raw strings correctly tmp_raw <- tempfile() writeLines("c(R'(a\\b)', R'--[a\\\"\'\"\\b]--')", tmp_raw) expr_as_xml_raw <- get_source_expressions(tmp_raw)$expressions[[1L]]$xml_parsed_content writeLines(as.character(expr_as_xml_raw)) get_r_string(expr_as_xml_raw, "expr[2]") get_r_string(expr_as_xml_raw, "expr[3]") unlink(tmp_raw)
tmp <- tempfile() writeLines("c('a', 'b')", tmp) expr_as_xml <- get_source_expressions(tmp)$expressions[[1L]]$xml_parsed_content writeLines(as.character(expr_as_xml)) get_r_string(expr_as_xml, "expr[2]") get_r_string(expr_as_xml, "expr[3]") unlink(tmp) # more importantly, extract raw strings correctly tmp_raw <- tempfile() writeLines("c(R'(a\\b)', R'--[a\\\"\'\"\\b]--')", tmp_raw) expr_as_xml_raw <- get_source_expressions(tmp_raw)$expressions[[1L]]$xml_parsed_content writeLines(as.character(expr_as_xml_raw)) get_r_string(expr_as_xml_raw, "expr[2]") get_r_string(expr_as_xml_raw, "expr[3]") unlink(tmp_raw)
This object is given as input to each linter.
get_source_expressions(filename, lines = NULL)
get_source_expressions(filename, lines = NULL)
filename |
the file to be parsed. |
lines |
a character vector of lines.
If |
The file is read using the encoding
setting.
This setting is found by taking the first valid result from the following locations
The encoding
key from the usual lintr configuration settings.
The Encoding
field from a Package DESCRIPTION
file in a parent directory.
The Encoding
field from an R Project .Rproj
file in a parent directory.
"UTF-8"
as a fallback.
A list
with three components:
a list
of
n+1
objects. The first n
elements correspond to each expression in
filename
, and consist of a list of 8 elements:
filename
(character
) the name of the file.
line
(integer
) the line in the file where this expression begins.
column
(integer
) the column in the file where this expression begins.
lines
(named character
) vector of all lines spanned by this
expression, named with the corresponding line numbers.
parsed_content
(data.frame
) as given by utils::getParseData()
for this expression.
xml_parsed_content
(xml_document
) the XML parse tree of this expression as given by
xmlparsedata::xml_parse_data()
.
content
(character
) the same as lines
as a single string (not split across lines).
xml_find_function_calls(function_names)
(function
) a function that returns all SYMBOL_FUNCTION_CALL
XML nodes from xml_parsed_content
with specified function names.
The final element of expressions
is a list corresponding to the full file
consisting of 7 elements:
filename
(character
) the name of this file.
file_lines
(character
) the readLines()
output for this file.
content
(character
) for .R files, the same as file_lines
;
for .Rmd or .qmd scripts, this is the extracted R source code (as text).
full_parsed_content
(data.frame
) as given by
utils::getParseData()
for the full content.
full_xml_parsed_content
(xml_document
) the XML parse tree of all
expressions as given by xmlparsedata::xml_parse_data()
.
terminal_newline
(logical
) records whether filename
has a terminal
newline (as determined by readLines()
producing a corresponding warning).
xml_find_function_calls(function_names)
(function
) a function that returns all SYMBOL_FUNCTION_CALL
XML nodes from full_xml_parsed_content
with specified function names.
A Lint
object describing any parsing error.
The readLines()
output for this file.
tmp <- tempfile() writeLines(c("x <- 1", "y <- x + 1"), tmp) get_source_expressions(tmp) unlink(tmp)
tmp <- tempfile() writeLines(c("x <- 1", "y <- x + 1"), tmp) get_source_expressions(tmp) unlink(tmp)
Gets the source IDs (row indices) corresponding to given token.
ids_with_token(source_expression, value, fun = `==`, source_file = NULL) with_id(source_expression, id, source_file)
ids_with_token(source_expression, value, fun = `==`, source_file = NULL) with_id(source_expression, id, source_file)
source_expression |
A list of source expressions, the result of a call to |
value |
Character. String corresponding to the token to search for. For example:
|
fun |
For additional flexibility, a function to search for in
the |
source_file |
(DEPRECATED) Same as |
id |
Integer. The index corresponding to the desired row
of |
ids_with_token
: The indices of the parsed_content
data frame
entry of the list of source expressions. Indices correspond to the
rows where fun
evaluates to TRUE
for the value
in the token column.
with_id
: A data frame corresponding to the row(s) specified in id
.
with_id()
: Return the row of the parsed_content
entry of the [get_source_expressions]()
object. Typically used in
conjunction with ids_with_token
to iterate over rows containing desired tokens.
tmp <- tempfile() writeLines(c("x <- 1", "y <- x + 1"), tmp) source_exprs <- get_source_expressions(tmp) ids_with_token(source_exprs$expressions[[1L]], value = "SYMBOL") with_id(source_exprs$expressions[[1L]], 2L) unlink(tmp)
tmp <- tempfile() writeLines(c("x <- 1", "y <- x + 1"), tmp) source_exprs <- get_source_expressions(tmp) ids_with_token(source_exprs$expressions[[1L]], value = "SYMBOL") with_id(source_exprs$expressions[[1L]], 2L) unlink(tmp)
if (!A) x else y
is the same as if (A) y else x
, but the latter is
easier to reason about in the else
case. The former requires
double negation that can be avoided by switching the statement order.
if_not_else_linter(exceptions = c("is.null", "is.na", "missing"))
if_not_else_linter(exceptions = c("is.null", "is.na", "missing"))
exceptions |
Character vector of calls to exclude from linting.
By default, |
This only applies in the simple if/else
case. Statements like
if (!A) x else if (B) y else z
don't always have a simpler or
more readable form.
It also applies to ifelse()
and the package equivalents
dplyr::if_else()
and data.table::fifelse()
.
configurable, consistency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "if (!A) x else y", linters = if_not_else_linter() ) lint( text = "if (!A) x else if (!B) y else z", linters = if_not_else_linter() ) lint( text = "ifelse(!is_treatment, x, y)", linters = if_not_else_linter() ) lint( text = "if (!is.null(x)) x else 2", linters = if_not_else_linter(exceptions = character()) ) # okay lint( text = "if (A) x else y", linters = if_not_else_linter() ) lint( text = "if (!A) x else if (B) z else y", linters = if_not_else_linter() ) lint( text = "ifelse(is_treatment, y, x)", linters = if_not_else_linter() ) lint( text = "if (!is.null(x)) x else 2", linters = if_not_else_linter() )
# will produce lints lint( text = "if (!A) x else y", linters = if_not_else_linter() ) lint( text = "if (!A) x else if (!B) y else z", linters = if_not_else_linter() ) lint( text = "ifelse(!is_treatment, x, y)", linters = if_not_else_linter() ) lint( text = "if (!is.null(x)) x else 2", linters = if_not_else_linter(exceptions = character()) ) # okay lint( text = "if (A) x else y", linters = if_not_else_linter() ) lint( text = "if (!A) x else if (B) z else y", linters = if_not_else_linter() ) lint( text = "ifelse(is_treatment, y, x)", linters = if_not_else_linter() ) lint( text = "if (!is.null(x)) x else 2", linters = if_not_else_linter() )
switch()
statements in R are used to delegate behavior based
on the value of some input scalar string, e.g.
switch(x, a = 1, b = 3, c = 7, d = 8)
will be one of
1
, 3
, 7
, or 8
, depending on the value of x
.
if_switch_linter(max_branch_lines = 0L, max_branch_expressions = 0L)
if_switch_linter(max_branch_lines = 0L, max_branch_expressions = 0L)
max_branch_lines , max_branch_expressions
|
Integer, default 0 indicates "no maximum".
If set any |
This can also be accomplished by repeated if
/else
statements like
so: if (x == "a") 1 else if (x == "b") 2 else if (x == "c") 7 else 8
(implicitly, the last else
assumes x only takes 4 possible values),
but this is more cluttered and slower (note that switch()
takes the same
time to evaluate regardless of the value of x
, and is faster even
when x
takes the first value (here a
), and that the if
/else
approach is roughly linear in the number of conditions that need to
be evaluated, here up to 3 times).
best_practices, configurable, consistency, efficiency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "if (x == 'a') 1 else if (x == 'b') 2 else 3", linters = if_switch_linter() ) code <- paste( "if (x == 'a') {", " 1", "} else if (x == 'b') {", " 2", "} else if (x == 'c') {", " y <- x", " z <- sqrt(match(y, letters))", " z", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = if_switch_linter() ) code <- paste( "if (x == 'a') {", " 1", "} else if (x == 'b') {", " 2", "} else if (x == 'c') {", " y <- x", " z <- sqrt(", " match(y, letters)", " )", " z", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = if_switch_linter() ) code <- paste( "switch(x,", " a = {", " 1", " 2", " 3", " },", " b = {", " 1", " 2", " }", ")", sep = "\n" ) writeLines(code) lint( text = code, linters = if_switch_linter(max_branch_lines = 2L) ) # okay lint( text = "switch(x, a = 1, b = 2, 3)", linters = if_switch_linter() ) # switch() version not as clear lint( text = "if (x == 'a') 1 else if (x == 'b' & y == 2) 2 else 3", linters = if_switch_linter() ) code <- paste( "if (x == 'a') {", " 1", "} else if (x == 'b') {", " 2", "} else if (x == 'c') {", " y <- x", " z <- sqrt(match(y, letters))", " z", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = if_switch_linter(max_branch_lines = 2L) ) code <- paste( "if (x == 'a') {", " 1", "} else if (x == 'b') {", " 2", "} else if (x == 'c') {", " y <- x", " z <- sqrt(", " match(y, letters)", " )", " z", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = if_switch_linter(max_branch_expressions = 2L) ) code <- paste( "switch(x,", " a = {", " 1", " 2", " 3", " },", " b = {", " 1", " 2", " }", ")", sep = "\n" ) writeLines(code) lint( text = code, linters = if_switch_linter(max_branch_lines = 3L) )
# will produce lints lint( text = "if (x == 'a') 1 else if (x == 'b') 2 else 3", linters = if_switch_linter() ) code <- paste( "if (x == 'a') {", " 1", "} else if (x == 'b') {", " 2", "} else if (x == 'c') {", " y <- x", " z <- sqrt(match(y, letters))", " z", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = if_switch_linter() ) code <- paste( "if (x == 'a') {", " 1", "} else if (x == 'b') {", " 2", "} else if (x == 'c') {", " y <- x", " z <- sqrt(", " match(y, letters)", " )", " z", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = if_switch_linter() ) code <- paste( "switch(x,", " a = {", " 1", " 2", " 3", " },", " b = {", " 1", " 2", " }", ")", sep = "\n" ) writeLines(code) lint( text = code, linters = if_switch_linter(max_branch_lines = 2L) ) # okay lint( text = "switch(x, a = 1, b = 2, 3)", linters = if_switch_linter() ) # switch() version not as clear lint( text = "if (x == 'a') 1 else if (x == 'b' & y == 2) 2 else 3", linters = if_switch_linter() ) code <- paste( "if (x == 'a') {", " 1", "} else if (x == 'b') {", " 2", "} else if (x == 'c') {", " y <- x", " z <- sqrt(match(y, letters))", " z", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = if_switch_linter(max_branch_lines = 2L) ) code <- paste( "if (x == 'a') {", " 1", "} else if (x == 'b') {", " 2", "} else if (x == 'c') {", " y <- x", " z <- sqrt(", " match(y, letters)", " )", " z", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = if_switch_linter(max_branch_expressions = 2L) ) code <- paste( "switch(x,", " a = {", " 1", " 2", " 3", " },", " b = {", " 1", " 2", " }", ")", sep = "\n" ) writeLines(code) lint( text = code, linters = if_switch_linter(max_branch_lines = 3L) )
ifelse()
where pmin()
or pmax()
is more appropriateifelse(x > M, M, x)
is the same as pmin(x, M)
, but harder
to read and requires several passes over the vector.
ifelse_censor_linter()
ifelse_censor_linter()
The same goes for other similar ways to censor a vector, e.g.
ifelse(x <= M, x, M)
is pmin(x, M)
,
ifelse(x < m, m, x)
is pmax(x, m)
, and
ifelse(x >= m, x, m)
is pmax(x, m)
.
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "ifelse(5:1 < pi, 5:1, pi)", linters = ifelse_censor_linter() ) lint( text = "ifelse(x > 0, x, 0)", linters = ifelse_censor_linter() ) # okay lint( text = "pmin(5:1, pi)", linters = ifelse_censor_linter() ) lint( text = "pmax(x, 0)", linters = ifelse_censor_linter() )
# will produce lints lint( text = "ifelse(5:1 < pi, 5:1, pi)", linters = ifelse_censor_linter() ) lint( text = "ifelse(x > 0, x, 0)", linters = ifelse_censor_linter() ) # okay lint( text = "pmin(5:1, pi)", linters = ifelse_censor_linter() ) lint( text = "pmax(x, 0)", linters = ifelse_censor_linter() )
Assigning inside function calls makes the code difficult to read, and should
be avoided, except for functions that capture side-effects (e.g. capture.output()
).
implicit_assignment_linter( except = c("bquote", "expression", "expr", "quo", "quos", "quote"), allow_lazy = FALSE, allow_scoped = FALSE )
implicit_assignment_linter( except = c("bquote", "expression", "expr", "quo", "quos", "quote"), allow_lazy = FALSE, allow_scoped = FALSE )
except |
A character vector of functions to be excluded from linting. |
allow_lazy |
logical, default |
allow_scoped |
Logical, default |
best_practices, configurable, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "if (x <- 1L) TRUE", linters = implicit_assignment_linter() ) lint( text = "mean(x <- 1:4)", linters = implicit_assignment_linter() ) # okay lines <- "x <- 1L\nif (x) TRUE" writeLines(lines) lint( text = lines, linters = implicit_assignment_linter() ) lines <- "x <- 1:4\nmean(x)" writeLines(lines) lint( text = lines, linters = implicit_assignment_linter() ) lint( text = "A && (B <- foo(A))", linters = implicit_assignment_linter(allow_lazy = TRUE) ) lines <- c( "if (any(idx <- x < 0)) {", " stop('negative elements: ', toString(which(idx)))", "}" ) writeLines(lines) lint( text = lines, linters = implicit_assignment_linter(allow_scoped = TRUE) )
# will produce lints lint( text = "if (x <- 1L) TRUE", linters = implicit_assignment_linter() ) lint( text = "mean(x <- 1:4)", linters = implicit_assignment_linter() ) # okay lines <- "x <- 1L\nif (x) TRUE" writeLines(lines) lint( text = lines, linters = implicit_assignment_linter() ) lines <- "x <- 1:4\nmean(x)" writeLines(lines) lint( text = lines, linters = implicit_assignment_linter() ) lint( text = "A && (B <- foo(A))", linters = implicit_assignment_linter(allow_lazy = TRUE) ) lines <- c( "if (any(idx <- x < 0)) {", " stop('negative elements: ', toString(which(idx)))", "}" ) writeLines(lines) lint( text = lines, linters = implicit_assignment_linter(allow_scoped = TRUE) )
Check that integers are explicitly typed using the form 1L
instead of 1
.
implicit_integer_linter(allow_colon = FALSE)
implicit_integer_linter(allow_colon = FALSE)
allow_colon |
Logical, default |
best_practices, configurable, consistency, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x <- 1", linters = implicit_integer_linter() ) lint( text = "x[2]", linters = implicit_integer_linter() ) lint( text = "1:10", linters = implicit_integer_linter() ) # okay lint( text = "x <- 1.0", linters = implicit_integer_linter() ) lint( text = "x <- 1L", linters = implicit_integer_linter() ) lint( text = "x[2L]", linters = implicit_integer_linter() ) lint( text = "1:10", linters = implicit_integer_linter(allow_colon = TRUE) )
# will produce lints lint( text = "x <- 1", linters = implicit_integer_linter() ) lint( text = "x[2]", linters = implicit_integer_linter() ) lint( text = "1:10", linters = implicit_integer_linter() ) # okay lint( text = "x <- 1.0", linters = implicit_integer_linter() ) lint( text = "x <- 1L", linters = implicit_integer_linter() ) lint( text = "x[2L]", linters = implicit_integer_linter() ) lint( text = "1:10", linters = implicit_integer_linter(allow_colon = TRUE) )
Check that indentation is consistent
indentation_linter( indent = 2L, hanging_indent_style = c("tidy", "always", "never"), assignment_as_infix = TRUE )
indentation_linter( indent = 2L, hanging_indent_style = c("tidy", "always", "never"), assignment_as_infix = TRUE )
indent |
Number of spaces, that a code block should be indented by relative to its parent code block.
Used for multi-line code blocks ( |
hanging_indent_style |
Indentation style for multi-line function calls with arguments in their first line.
Defaults to tidyverse style, i.e. a block indent is used if the function call terminates with # complies to any style map( x, f, additional_arg = 42 ) # complies to "tidy" and "never" map(x, f, additional_arg = 42 ) # complies to "always" map(x, f, additional_arg = 42 ) # complies to "tidy" and "always" map(x, f, additional_arg = 42) # complies to "never" map(x, f, additional_arg = 42) # complies to "tidy" function( a, b) { # body } |
assignment_as_infix |
Treat # complies to any style variable <- a %+% b %+% c # complies to assignment_as_infix = TRUE variable <- a %+% b %+% c # complies to assignment_as_infix = FALSE variable <- a %+% b %+% c |
configurable, default, readability, style
linters for a complete list of linters available in lintr.
# will produce lints code_lines <- "if (TRUE) {\n1 + 1\n}" writeLines(code_lines) lint( text = code_lines, linters = indentation_linter() ) code_lines <- "if (TRUE) {\n 1 + 1\n}" writeLines(code_lines) lint( text = code_lines, linters = indentation_linter() ) code_lines <- "map(x, f,\n additional_arg = 42\n)" writeLines(code_lines) lint( text = code_lines, linters = indentation_linter(hanging_indent_style = "always") ) code_lines <- "map(x, f,\n additional_arg = 42)" writeLines(code_lines) lint( text = code_lines, linters = indentation_linter(hanging_indent_style = "never") ) # okay code_lines <- "map(x, f,\n additional_arg = 42\n)" writeLines(code_lines) lint( text = code_lines, linters = indentation_linter() ) code_lines <- "if (TRUE) {\n 1 + 1\n}" writeLines(code_lines) lint( text = code_lines, linters = indentation_linter(indent = 4) )
# will produce lints code_lines <- "if (TRUE) {\n1 + 1\n}" writeLines(code_lines) lint( text = code_lines, linters = indentation_linter() ) code_lines <- "if (TRUE) {\n 1 + 1\n}" writeLines(code_lines) lint( text = code_lines, linters = indentation_linter() ) code_lines <- "map(x, f,\n additional_arg = 42\n)" writeLines(code_lines) lint( text = code_lines, linters = indentation_linter(hanging_indent_style = "always") ) code_lines <- "map(x, f,\n additional_arg = 42)" writeLines(code_lines) lint( text = code_lines, linters = indentation_linter(hanging_indent_style = "never") ) # okay code_lines <- "map(x, f,\n additional_arg = 42\n)" writeLines(code_lines) lint( text = code_lines, linters = indentation_linter() ) code_lines <- "if (TRUE) {\n 1 + 1\n}" writeLines(code_lines) lint( text = code_lines, linters = indentation_linter(indent = 4) )
Check that infix operators are surrounded by spaces. Enforces the corresponding Tidyverse style guide rule; see https://style.tidyverse.org/syntax.html#infix-operators.
infix_spaces_linter(exclude_operators = NULL, allow_multiple_spaces = TRUE)
infix_spaces_linter(exclude_operators = NULL, allow_multiple_spaces = TRUE)
exclude_operators |
Character vector of operators to exclude from consideration for linting.
Default is to include the following "low-precedence" operators:
|
allow_multiple_spaces |
Logical, default |
configurable, default, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x<-1L", linters = infix_spaces_linter() ) lint( text = "1:4 %>%sum()", linters = infix_spaces_linter() ) # okay lint( text = "x <- 1L", linters = infix_spaces_linter() ) lint( text = "1:4 %>% sum()", linters = infix_spaces_linter() ) code_lines <- " ab <- 1L abcdef <- 2L " writeLines(code_lines) lint( text = code_lines, linters = infix_spaces_linter(allow_multiple_spaces = TRUE) ) lint( text = "a||b", linters = infix_spaces_linter(exclude_operators = "||") ) lint( text = "sum(1:10, na.rm=TRUE)", linters = infix_spaces_linter(exclude_operators = "EQ_SUB") )
# will produce lints lint( text = "x<-1L", linters = infix_spaces_linter() ) lint( text = "1:4 %>%sum()", linters = infix_spaces_linter() ) # okay lint( text = "x <- 1L", linters = infix_spaces_linter() ) lint( text = "1:4 %>% sum()", linters = infix_spaces_linter() ) code_lines <- " ab <- 1L abcdef <- 2L " writeLines(code_lines) lint( text = code_lines, linters = infix_spaces_linter(allow_multiple_spaces = TRUE) ) lint( text = "a||b", linters = infix_spaces_linter(exclude_operators = "||") ) lint( text = "sum(1:10, na.rm=TRUE)", linters = infix_spaces_linter(exclude_operators = "EQ_SUB") )
c()
to be applied before relatively expensive vectorized functionsas.Date(c(a, b))
is logically equivalent to c(as.Date(a), as.Date(b))
.
The same equivalence holds for several other vectorized functions like
as.POSIXct()
and math functions like sin()
. The former is to be
preferred so that the most expensive part of the operation (as.Date()
)
is applied only once.
inner_combine_linter()
inner_combine_linter()
Note that strptime()
has one idiosyncrasy to be aware of, namely that
auto-detected format=
is set by the first matching input, which means
that a case like c(as.POSIXct("2024-01-01"), as.POSIXct("2024-01-01 01:02:03"))
gives different results to as.POSIXct(c("2024-01-01", "2024-01-01 01:02:03"))
.
This false positive is rare; a workaround where possible is to use
consistent formatting, i.e., "2024-01-01 00:00:00"
in the example.
consistency, efficiency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "c(log10(x), log10(y), log10(z))", linters = inner_combine_linter() ) # okay lint( text = "log10(c(x, y, z))", linters = inner_combine_linter() ) lint( text = "c(log(x, base = 10), log10(x, base = 2))", linters = inner_combine_linter() )
# will produce lints lint( text = "c(log10(x), log10(y), log10(z))", linters = inner_combine_linter() ) # okay lint( text = "log10(c(x, y, z))", linters = inner_combine_linter() ) lint( text = "c(log(x, base = 10), log10(x, base = 2))", linters = inner_combine_linter() )
Helper for determining whether the current source_expression
contains
all expressions in the current file, or just a single expression.
is_lint_level(source_expression, level = c("expression", "file"))
is_lint_level(source_expression, level = c("expression", "file"))
source_expression |
A parsed expression object, i.e., an element
of the object returned by |
level |
Which level of expression is being tested? |
tmp <- tempfile() writeLines(c("x <- 1", "y <- x + 1"), tmp) source_exprs <- get_source_expressions(tmp) is_lint_level(source_exprs$expressions[[1L]], level = "expression") is_lint_level(source_exprs$expressions[[1L]], level = "file") is_lint_level(source_exprs$expressions[[3L]], level = "expression") is_lint_level(source_exprs$expressions[[3L]], level = "file") unlink(tmp)
tmp <- tempfile() writeLines(c("x <- 1", "y <- x + 1"), tmp) source_exprs <- get_source_expressions(tmp) is_lint_level(source_exprs$expressions[[1L]], level = "expression") is_lint_level(source_exprs$expressions[[1L]], level = "file") is_lint_level(source_exprs$expressions[[3L]], level = "expression") is_lint_level(source_exprs$expressions[[3L]], level = "file") unlink(tmp)
is.numeric(x) || is.integer(x)
to just use is.numeric(x)
is.numeric()
returns TRUE
when typeof(x)
is double
or integer
–
testing is.numeric(x) || is.integer(x)
is thus redundant.
is_numeric_linter()
is_numeric_linter()
NB: This linter plays well with class_equals_linter()
, which can help
avoid further is.numeric()
equivalents like
any(class(x) == c("numeric", "integer"))
.
best_practices, consistency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "is.numeric(y) || is.integer(y)", linters = is_numeric_linter() ) lint( text = 'class(z) %in% c("numeric", "integer")', linters = is_numeric_linter() ) # okay lint( text = "is.numeric(y) || is.factor(y)", linters = is_numeric_linter() ) lint( text = 'class(z) %in% c("numeric", "integer", "factor")', linters = is_numeric_linter() )
# will produce lints lint( text = "is.numeric(y) || is.integer(y)", linters = is_numeric_linter() ) lint( text = 'class(z) %in% c("numeric", "integer")', linters = is_numeric_linter() ) # okay lint( text = "is.numeric(y) || is.factor(y)", linters = is_numeric_linter() ) lint( text = 'class(z) %in% c("numeric", "integer", "factor")', linters = is_numeric_linter() )
Any valid symbol can be used as a keyword argument to an R function call. Sometimes, it is necessary to quote (or backtick) an argument that is not an otherwise valid symbol (e.g. creating a vector whose names have spaces); besides this edge case, quoting should not be done.
keyword_quote_linter()
keyword_quote_linter()
The most common source of violation for this is creating named vectors, lists, or data.frame-alikes, but it can be observed in other calls as well.
Similar reasoning applies to extractions with $
or @
.
consistency, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'data.frame("a" = 1)', linters = keyword_quote_linter() ) lint( text = "data.frame(`a` = 1)", linters = keyword_quote_linter() ) lint( text = 'my_list$"key"', linters = keyword_quote_linter() ) lint( text = 's4obj@"key"', linters = keyword_quote_linter() ) # okay lint( text = "data.frame(`a b` = 1)", linters = keyword_quote_linter() ) lint( text = "my_list$`a b`", linters = keyword_quote_linter() )
# will produce lints lint( text = 'data.frame("a" = 1)', linters = keyword_quote_linter() ) lint( text = "data.frame(`a` = 1)", linters = keyword_quote_linter() ) lint( text = 'my_list$"key"', linters = keyword_quote_linter() ) lint( text = 's4obj@"key"', linters = keyword_quote_linter() ) # okay lint( text = "data.frame(`a b` = 1)", linters = keyword_quote_linter() ) lint( text = "my_list$`a b`", linters = keyword_quote_linter() )
length(levels(x))
is the same as nlevels(x)
, but harder to read.
length_levels_linter()
length_levels_linter()
best_practices, consistency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "length(levels(x))", linters = length_levels_linter() ) # okay lint( text = "length(c(levels(x), levels(y)))", linters = length_levels_linter() )
# will produce lints lint( text = "length(levels(x))", linters = length_levels_linter() ) # okay lint( text = "length(c(levels(x), levels(y)))", linters = length_levels_linter() )
Usage like length(x == 0)
is a mistake. If you intended to check x
is empty,
use length(x) == 0
. Other mistakes are possible, but running length()
on the
outcome of a logical comparison is never the best choice.
length_test_linter()
length_test_linter()
best_practices, consistency, robustness
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "length(x == 0)", linters = length_test_linter() ) # okay lint( text = "length(x) > 0", linters = length_test_linter() )
# will produce lints lint( text = "length(x == 0)", linters = length_test_linter() ) # okay lint( text = "length(x) > 0", linters = length_test_linter() )
lengths()
where possiblelengths()
is a function that was added to base R in version 3.2.0 to
get the length of each element of a list. It is equivalent to
sapply(x, length)
, but faster and more readable.
lengths_linter()
lengths_linter()
best_practices, efficiency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "sapply(x, length)", linters = lengths_linter() ) lint( text = "vapply(x, length, integer(1L))", linters = lengths_linter() ) lint( text = "purrr::map_int(x, length)", linters = lengths_linter() ) # okay lint( text = "lengths(x)", linters = lengths_linter() )
# will produce lints lint( text = "sapply(x, length)", linters = lengths_linter() ) lint( text = "vapply(x, length, integer(1L))", linters = lengths_linter() ) lint( text = "purrr::map_int(x, length)", linters = lengths_linter() ) # okay lint( text = "lengths(x)", linters = lengths_linter() )
This linter covers several rules related to library()
calls:
library_call_linter(allow_preamble = TRUE)
library_call_linter(allow_preamble = TRUE)
allow_preamble |
Logical, default |
Enforce such calls to all be at the top of the script.
Block usage of argument character.only
, in particular
for loading packages in a loop.
Block consecutive calls to suppressMessages(library(.))
in favor of using suppressMessages()
only once to suppress
messages from all library()
calls. Ditto suppressPackageStartupMessages()
.
best_practices, configurable, readability, style
linters for a complete list of linters available in lintr.
# will produce lints code <- "library(dplyr)\nprint('test')\nlibrary(tidyr)" writeLines(code) lint( text = code, linters = library_call_linter() ) lint( text = "library('dplyr', character.only = TRUE)", linters = library_call_linter() ) code <- paste( "pkg <- c('dplyr', 'tibble')", "sapply(pkg, library, character.only = TRUE)", sep = "\n" ) writeLines(code) lint( text = code, linters = library_call_linter() ) code <- "suppressMessages(library(dplyr))\nsuppressMessages(library(tidyr))" writeLines(code) lint( text = code, linters = library_call_linter() ) # okay code <- "library(dplyr)\nprint('test')" writeLines(code) lint( text = code, linters = library_call_linter() ) code <- "# comment\nlibrary(dplyr)" lint( text = code, linters = library_call_linter() ) code <- paste( "foo <- function(pkg) {", " sapply(pkg, library, character.only = TRUE)", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = library_call_linter() ) code <- "suppressMessages({\n library(dplyr)\n library(tidyr)\n})" writeLines(code) lint( text = code, linters = library_call_linter() )
# will produce lints code <- "library(dplyr)\nprint('test')\nlibrary(tidyr)" writeLines(code) lint( text = code, linters = library_call_linter() ) lint( text = "library('dplyr', character.only = TRUE)", linters = library_call_linter() ) code <- paste( "pkg <- c('dplyr', 'tibble')", "sapply(pkg, library, character.only = TRUE)", sep = "\n" ) writeLines(code) lint( text = code, linters = library_call_linter() ) code <- "suppressMessages(library(dplyr))\nsuppressMessages(library(tidyr))" writeLines(code) lint( text = code, linters = library_call_linter() ) # okay code <- "library(dplyr)\nprint('test')" writeLines(code) lint( text = code, linters = library_call_linter() ) code <- "# comment\nlibrary(dplyr)" lint( text = code, linters = library_call_linter() ) code <- paste( "foo <- function(pkg) {", " sapply(pkg, library, character.only = TRUE)", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = library_call_linter() ) code <- "suppressMessages({\n library(dplyr)\n library(tidyr)\n})" writeLines(code) lint( text = code, linters = library_call_linter() )
Check that the line length of both comments and code is less than length
.
line_length_linter(length = 80L)
line_length_linter(length = 80L)
length |
maximum line length allowed. Default is 80L (Hollerith limit). |
configurable, default, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = strrep("x", 23L), linters = line_length_linter(length = 20L) ) # okay lint( text = strrep("x", 21L), linters = line_length_linter(length = 40L) )
# will produce lints lint( text = strrep("x", 23L), linters = line_length_linter(length = 20L) ) # okay lint( text = strrep("x", 21L), linters = line_length_linter(length = 40L) )
lint()
lints a single file.
lint_dir()
lints all files in a directory.
lint_package()
lints all likely locations for R files in a package, i.e.
R/
, tests/
, inst/
, vignettes/
, data-raw/
, demo/
, and exec/
.
lint( filename, linters = NULL, ..., cache = FALSE, parse_settings = TRUE, text = NULL ) lint_dir( path = ".", ..., relative_path = TRUE, exclusions = list("renv", "packrat"), pattern = "(?i)[.](r|rmd|qmd|rnw|rhtml|rrst|rtex|rtxt)$", parse_settings = TRUE, show_progress = NULL ) lint_package( path = ".", ..., relative_path = TRUE, exclusions = list("R/RcppExports.R"), parse_settings = TRUE, show_progress = NULL )
lint( filename, linters = NULL, ..., cache = FALSE, parse_settings = TRUE, text = NULL ) lint_dir( path = ".", ..., relative_path = TRUE, exclusions = list("renv", "packrat"), pattern = "(?i)[.](r|rmd|qmd|rnw|rhtml|rrst|rtex|rtxt)$", parse_settings = TRUE, show_progress = NULL ) lint_package( path = ".", ..., relative_path = TRUE, exclusions = list("R/RcppExports.R"), parse_settings = TRUE, show_progress = NULL )
filename |
Either the filename for a file to lint, or a character string of inline R code for linting.
The latter (inline data) applies whenever |
linters |
A named list of linter functions to apply. See linters for a full list of default and available linters. |
... |
Provide additional arguments to be passed to: |
cache |
When logical, toggle caching of lint results. If passed a character string, store the cache in this directory. |
parse_settings |
Logical, default |
text |
Optional argument for supplying a string or lines directly, e.g. if the file is already in memory or linting is being done ad hoc. |
path |
For the base directory of the project (for |
relative_path |
if |
exclusions |
exclusions for |
pattern |
pattern for files, by default it will take files with any of the extensions .R, .Rmd, .qmd, .Rnw, .Rhtml, .Rrst, .Rtex, .Rtxt allowing for lowercase r (.r, ...). |
show_progress |
Logical controlling whether to show linting progress with a simple text
progress bar via |
Read vignette("lintr")
to learn how to configure which linters are run
by default.
Note that if files contain unparseable encoding problems, only the encoding problem will be linted to avoid
unintelligible error messages from other linters.
An object of class c("lints", "list")
, each element of which is a "list"
object.
# linting inline-code lint("a = 123\n") lint(text = "a = 123") # linting a file f <- tempfile() writeLines("a=1", f) lint(f) unlink(f) if (FALSE) { lint_dir() lint_dir( linters = list(semicolon_linter()), exclusions = list( "inst/doc/creating_linters.R" = 1, "inst/example/bad.R", "renv" ) ) } if (FALSE) { lint_package() lint_package( linters = linters_with_defaults(semicolon_linter = semicolon_linter()), exclusions = list("inst/doc/creating_linters.R" = 1, "inst/example/bad.R") ) }
# linting inline-code lint("a = 123\n") lint(text = "a = 123") # linting a file f <- tempfile() writeLines("a=1", f) lint(f) unlink(f) if (FALSE) { lint_dir() lint_dir( linters = list(semicolon_linter()), exclusions = list( "inst/doc/creating_linters.R" = 1, "inst/example/bad.R", "renv" ) ) } if (FALSE) { lint_package() lint_package( linters = linters_with_defaults(semicolon_linter = semicolon_linter()), exclusions = list("inst/doc/creating_linters.R" = 1, "inst/example/bad.R") ) }
lint
objectCreate a lint
object
Lint( filename, line_number = 1L, column_number = 1L, type = c("style", "warning", "error"), message = "", line = "", ranges = NULL, linter = "" )
Lint( filename, line_number = 1L, column_number = 1L, type = c("style", "warning", "error"), message = "", line = "", ranges = NULL, linter = "" )
filename |
path to the source file that was linted. |
line_number |
line number where the lint occurred. |
column_number |
column number where the lint occurred. |
type |
type of lint. |
message |
message used to describe the lint error |
line |
code source where the lint occurred |
ranges |
a list of ranges on the line that should be emphasized. |
linter |
deprecated. No longer used. |
an object of class c("lint", "list")
.
linter
closureCreate a linter
closure
Linter( fun, name = linter_auto_name(), linter_level = c(NA_character_, "file", "expression") )
Linter( fun, name = linter_auto_name(), linter_level = c(NA_character_, "file", "expression") )
fun |
A function that takes a source file and returns |
name |
Default name of the Linter.
Lints produced by the linter will be labelled with |
linter_level |
Which level of expression is the linter working with?
|
The same function with its class set to 'linter'.
A variety of linters are available in lintr. The most popular ones are readily
accessible through default_linters()
.
Within a lint()
function call, the linters in use are initialized with the provided
arguments and fed with the source file (provided by get_source_expressions()
).
A data frame of all available linters can be retrieved using available_linters()
.
Documentation for linters is structured into tags to allow for easier discovery;
see also available_tags()
.
The following tags exist:
best_practices (63 linters)
common_mistakes (11 linters)
configurable (44 linters)
consistency (32 linters)
correctness (7 linters)
default (25 linters)
deprecated (6 linters)
efficiency (29 linters)
executing (6 linters)
package_development (14 linters)
pkg_testthat (12 linters)
readability (64 linters)
regex (4 linters)
robustness (17 linters)
style (40 linters)
tidy_design (1 linters)
The following linters exist:
absolute_path_linter
(tags: best_practices, configurable, robustness)
any_duplicated_linter
(tags: best_practices, efficiency)
any_is_na_linter
(tags: best_practices, efficiency)
assignment_linter
(tags: configurable, consistency, default, style)
backport_linter
(tags: configurable, package_development, robustness)
boolean_arithmetic_linter
(tags: best_practices, efficiency, readability)
brace_linter
(tags: configurable, default, readability, style)
class_equals_linter
(tags: best_practices, consistency, robustness)
commas_linter
(tags: configurable, default, readability, style)
commented_code_linter
(tags: best_practices, default, readability, style)
comparison_negation_linter
(tags: consistency, readability)
condition_call_linter
(tags: best_practices, configurable, style, tidy_design)
condition_message_linter
(tags: best_practices, consistency)
conjunct_test_linter
(tags: best_practices, configurable, package_development, pkg_testthat, readability)
consecutive_assertion_linter
(tags: consistency, readability, style)
consecutive_mutate_linter
(tags: configurable, consistency, efficiency, readability)
cyclocomp_linter
(tags: best_practices, configurable, readability, style)
duplicate_argument_linter
(tags: common_mistakes, configurable, correctness)
empty_assignment_linter
(tags: best_practices, readability)
equals_na_linter
(tags: common_mistakes, correctness, default, robustness)
expect_comparison_linter
(tags: best_practices, package_development, pkg_testthat)
expect_identical_linter
(tags: package_development, pkg_testthat)
expect_length_linter
(tags: best_practices, package_development, pkg_testthat, readability)
expect_named_linter
(tags: best_practices, package_development, pkg_testthat, readability)
expect_not_linter
(tags: best_practices, package_development, pkg_testthat, readability)
expect_null_linter
(tags: best_practices, package_development, pkg_testthat)
expect_s3_class_linter
(tags: best_practices, package_development, pkg_testthat)
expect_s4_class_linter
(tags: best_practices, package_development, pkg_testthat)
expect_true_false_linter
(tags: best_practices, package_development, pkg_testthat, readability)
expect_type_linter
(tags: best_practices, package_development, pkg_testthat)
fixed_regex_linter
(tags: best_practices, configurable, efficiency, readability, regex)
for_loop_index_linter
(tags: best_practices, readability, robustness)
function_argument_linter
(tags: best_practices, consistency, style)
function_left_parentheses_linter
(tags: default, readability, style)
function_return_linter
(tags: best_practices, readability)
if_not_else_linter
(tags: configurable, consistency, readability)
if_switch_linter
(tags: best_practices, configurable, consistency, efficiency, readability)
ifelse_censor_linter
(tags: best_practices, efficiency)
implicit_assignment_linter
(tags: best_practices, configurable, readability, style)
implicit_integer_linter
(tags: best_practices, configurable, consistency, style)
indentation_linter
(tags: configurable, default, readability, style)
infix_spaces_linter
(tags: configurable, default, readability, style)
inner_combine_linter
(tags: consistency, efficiency, readability)
is_numeric_linter
(tags: best_practices, consistency, readability)
keyword_quote_linter
(tags: consistency, readability, style)
length_levels_linter
(tags: best_practices, consistency, readability)
length_test_linter
(tags: common_mistakes, efficiency)
lengths_linter
(tags: best_practices, efficiency, readability)
library_call_linter
(tags: best_practices, configurable, readability, style)
line_length_linter
(tags: configurable, default, readability, style)
list_comparison_linter
(tags: best_practices, common_mistakes)
literal_coercion_linter
(tags: best_practices, consistency, efficiency)
matrix_apply_linter
(tags: efficiency, readability)
missing_argument_linter
(tags: common_mistakes, configurable, correctness)
missing_package_linter
(tags: common_mistakes, robustness)
namespace_linter
(tags: configurable, correctness, executing, robustness)
nested_ifelse_linter
(tags: efficiency, readability)
nested_pipe_linter
(tags: configurable, consistency, readability)
nonportable_path_linter
(tags: best_practices, configurable, robustness)
nrow_subset_linter
(tags: best_practices, consistency, efficiency)
numeric_leading_zero_linter
(tags: consistency, readability, style)
nzchar_linter
(tags: best_practices, consistency, efficiency)
object_length_linter
(tags: configurable, default, executing, readability, style)
object_name_linter
(tags: configurable, consistency, default, executing, style)
object_overwrite_linter
(tags: best_practices, configurable, executing, readability, robustness)
object_usage_linter
(tags: configurable, correctness, default, executing, readability, style)
one_call_pipe_linter
(tags: readability, style)
outer_negation_linter
(tags: best_practices, efficiency, readability)
package_hooks_linter
(tags: correctness, package_development, style)
paren_body_linter
(tags: default, readability, style)
paste_linter
(tags: best_practices, configurable, consistency)
pipe_call_linter
(tags: readability, style)
pipe_consistency_linter
(tags: configurable, readability, style)
pipe_continuation_linter
(tags: default, readability, style)
pipe_return_linter
(tags: best_practices, common_mistakes)
print_linter
(tags: best_practices, consistency)
quotes_linter
(tags: configurable, consistency, default, readability, style)
redundant_equals_linter
(tags: best_practices, common_mistakes, efficiency, readability)
redundant_ifelse_linter
(tags: best_practices, configurable, consistency, efficiency)
regex_subset_linter
(tags: best_practices, efficiency, regex)
rep_len_linter
(tags: best_practices, consistency, readability)
repeat_linter
(tags: readability, style)
return_linter
(tags: configurable, default, style)
routine_registration_linter
(tags: best_practices, efficiency, robustness)
sample_int_linter
(tags: efficiency, readability, robustness)
scalar_in_linter
(tags: best_practices, configurable, consistency, efficiency, readability)
semicolon_linter
(tags: configurable, default, readability, style)
seq_linter
(tags: best_practices, consistency, default, efficiency, robustness)
sort_linter
(tags: best_practices, efficiency, readability)
spaces_inside_linter
(tags: default, readability, style)
spaces_left_parentheses_linter
(tags: default, readability, style)
sprintf_linter
(tags: common_mistakes, correctness)
stopifnot_all_linter
(tags: best_practices, readability)
string_boundary_linter
(tags: configurable, efficiency, readability, regex)
strings_as_factors_linter
(tags: robustness)
system_file_linter
(tags: best_practices, consistency, readability)
T_and_F_symbol_linter
(tags: best_practices, consistency, default, readability, robustness, style)
terminal_close_linter
(tags: best_practices, robustness)
todo_comment_linter
(tags: configurable, style)
trailing_blank_lines_linter
(tags: default, style)
trailing_whitespace_linter
(tags: configurable, default, style)
undesirable_function_linter
(tags: best_practices, configurable, robustness, style)
undesirable_operator_linter
(tags: best_practices, configurable, robustness, style)
unnecessary_concatenation_linter
(tags: configurable, efficiency, readability, style)
unnecessary_lambda_linter
(tags: best_practices, configurable, efficiency, readability)
unnecessary_nesting_linter
(tags: best_practices, configurable, consistency, readability)
unnecessary_placeholder_linter
(tags: best_practices, readability)
unreachable_code_linter
(tags: best_practices, configurable, readability)
unused_import_linter
(tags: best_practices, common_mistakes, configurable, executing)
vector_logic_linter
(tags: best_practices, common_mistakes, default, efficiency)
which_grepl_linter
(tags: consistency, efficiency, readability, regex)
whitespace_linter
(tags: consistency, default, style)
yoda_test_linter
(tags: best_practices, package_development, pkg_testthat, readability)
Make a new list based on lintr's default linters.
The result of this function is meant to be passed to the linters
argument of lint()
,
or to be put in your configuration file.
linters_with_defaults(..., defaults = default_linters)
linters_with_defaults(..., defaults = default_linters)
... |
Arguments of elements to change. If unnamed, the argument is automatically named.
If the named argument already exists in the list of linters, it is replaced by the new element.
If it does not exist, it is added. If the value is |
defaults |
Default list of linters to modify. Must be named. |
linters_with_tags for basing off tags attached to linters, possibly across multiple packages.
all_linters for basing off all available linters in lintr.
available_linters to get a data frame of available linters.
linters for a complete list of linters available in lintr.
# When using interactively you will usually pass the result onto `lint` or `lint_package()` f <- tempfile() writeLines("my_slightly_long_variable_name <- 2.3", f) lint(f, linters = linters_with_defaults(line_length_linter = line_length_linter(120L))) unlink(f) # the default linter list with a different line length cutoff my_linters <- linters_with_defaults(line_length_linter = line_length_linter(120L)) # omit the argument name if you are just using different arguments my_linters <- linters_with_defaults(defaults = my_linters, object_name_linter("camelCase")) # remove assignment checks (with NULL), add absolute path checks my_linters <- linters_with_defaults( defaults = my_linters, assignment_linter = NULL, absolute_path_linter() ) # checking the included linters names(my_linters)
# When using interactively you will usually pass the result onto `lint` or `lint_package()` f <- tempfile() writeLines("my_slightly_long_variable_name <- 2.3", f) lint(f, linters = linters_with_defaults(line_length_linter = line_length_linter(120L))) unlink(f) # the default linter list with a different line length cutoff my_linters <- linters_with_defaults(line_length_linter = line_length_linter(120L)) # omit the argument name if you are just using different arguments my_linters <- linters_with_defaults(defaults = my_linters, object_name_linter("camelCase")) # remove assignment checks (with NULL), add absolute path checks my_linters <- linters_with_defaults( defaults = my_linters, assignment_linter = NULL, absolute_path_linter() ) # checking the included linters names(my_linters)
Make a new list based on all linters provided by packages
and tagged with tags
.
The result of this function is meant to be passed to the linters
argument of lint()
,
or to be put in your configuration file.
linters_with_tags(tags, ..., packages = "lintr", exclude_tags = "deprecated")
linters_with_tags(tags, ..., packages = "lintr", exclude_tags = "deprecated")
tags |
Optional character vector of tags to search. Only linters with at least one matching tag will be
returned. If |
... |
Arguments of elements to change. If unnamed, the argument is automatically named.
If the named argument already exists in the list of linters, it is replaced by the new element.
If it does not exist, it is added. If the value is |
packages |
A character vector of packages to search for linters. |
exclude_tags |
Tags to exclude from the results. Linters with at least one matching tag will not be returned.
If |
A modified list of linters.
linters_with_defaults for basing off lintr's set of default linters.
all_linters for basing off all available linters in lintr.
available_linters to get a data frame of available linters.
linters for a complete list of linters available in lintr.
# `linters_with_defaults()` and `linters_with_tags("default")` are the same: all.equal(linters_with_defaults(), linters_with_tags("default")) # Get all linters useful for package development linters <- linters_with_tags(tags = c("package_development", "style")) names(linters) # Get all linters tagged as "default" from lintr and mypkg if (FALSE) { linters_with_tags("default", packages = c("lintr", "mypkg")) }
# `linters_with_defaults()` and `linters_with_tags("default")` are the same: all.equal(linters_with_defaults(), linters_with_tags("default")) # Get all linters useful for package development linters <- linters_with_tags(tags = c("package_development", "style")) names(linters) # Get all linters tagged as "default" from lintr and mypkg if (FALSE) { linters_with_tags("default", packages = c("lintr", "mypkg")) }
Usage like lapply(x, sum) > 10
is awkward because the list must first
be coerced to a vector for comparison. A function like vapply()
should be preferred.
list_comparison_linter()
list_comparison_linter()
best_practices, common_mistakes
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "lapply(x, sum) > 10", linters = list_comparison_linter() ) # okay lint( text = "unlist(lapply(x, sum)) > 10", linters = list_comparison_linter() )
# will produce lints lint( text = "lapply(x, sum) > 10", linters = list_comparison_linter() ) # okay lint( text = "unlist(lapply(x, sum)) > 10", linters = list_comparison_linter() )
as.integer(1)
(or rlang::int(1)
) is the same as 1L
but the latter is
more concise and gets typed correctly at compilation.
literal_coercion_linter()
literal_coercion_linter()
The same applies to missing sentinels like NA
– typically, it is not
necessary to specify the storage type of NA
, but when it is, prefer
using the typed version (e.g. NA_real_
) instead of a coercion
(like as.numeric(NA)
).
best_practices, consistency, efficiency
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "int(1)", linters = literal_coercion_linter() ) lint( text = "as.character(NA)", linters = literal_coercion_linter() ) lint( text = "rlang::lgl(1L)", linters = literal_coercion_linter() ) # okay lint( text = "1L", linters = literal_coercion_linter() ) lint( text = "NA_character_", linters = literal_coercion_linter() ) lint( text = "TRUE", linters = literal_coercion_linter() )
# will produce lints lint( text = "int(1)", linters = literal_coercion_linter() ) lint( text = "as.character(NA)", linters = literal_coercion_linter() ) lint( text = "rlang::lgl(1L)", linters = literal_coercion_linter() ) # okay lint( text = "1L", linters = literal_coercion_linter() ) lint( text = "NA_character_", linters = literal_coercion_linter() ) lint( text = "TRUE", linters = literal_coercion_linter() )
Create a linter from an XPath
make_linter_from_xpath( xpath, lint_message, type = c("warning", "style", "error"), level = c("expression", "file") ) make_linter_from_function_xpath( function_names, xpath, lint_message, type = c("warning", "style", "error"), level = c("expression", "file") )
make_linter_from_xpath( xpath, lint_message, type = c("warning", "style", "error"), level = c("expression", "file") ) make_linter_from_function_xpath( function_names, xpath, lint_message, type = c("warning", "style", "error"), level = c("expression", "file") )
xpath |
Character string, an XPath identifying R code to lint.
For |
lint_message |
The message to be included as the |
type |
type of lint. |
level |
Which level of expression is being tested? |
function_names |
Character vector, names of functions whose calls to examine.. |
number_linter <- make_linter_from_xpath("//NUM_CONST", "This is a number.") lint(text = "1 + 2", linters = number_linter())
number_linter <- make_linter_from_xpath("//NUM_CONST", "This is a number.") lint(text = "1 + 2", linters = number_linter())
colSums(x)
or rowSums(x)
over apply(x, ., sum)
colSums()
and rowSums()
are clearer and more performant alternatives to
apply(x, 2, sum)
and apply(x, 1, sum)
respectively in the case of 2D
arrays, or matrices
matrix_apply_linter()
matrix_apply_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "apply(x, 1, sum)", linters = matrix_apply_linter() ) lint( text = "apply(x, 2, sum)", linters = matrix_apply_linter() ) lint( text = "apply(x, 2, sum, na.rm = TRUE)", linters = matrix_apply_linter() ) lint( text = "apply(x, 2:4, sum)", linters = matrix_apply_linter() )
# will produce lints lint( text = "apply(x, 1, sum)", linters = matrix_apply_linter() ) lint( text = "apply(x, 2, sum)", linters = matrix_apply_linter() ) lint( text = "apply(x, 2, sum, na.rm = TRUE)", linters = matrix_apply_linter() ) lint( text = "apply(x, 2:4, sum)", linters = matrix_apply_linter() )
Check for missing arguments in function calls (e.g. stats::median(1:10, )
).
missing_argument_linter( except = c("alist", "quote", "switch"), allow_trailing = FALSE )
missing_argument_linter( except = c("alist", "quote", "switch"), allow_trailing = FALSE )
except |
a character vector of function names as exceptions. |
allow_trailing |
always allow trailing empty arguments? |
common_mistakes, configurable, correctness
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'tibble(x = "a", )', linters = missing_argument_linter() ) # okay lint( text = 'tibble(x = "a")', linters = missing_argument_linter() ) lint( text = 'tibble(x = "a", )', linters = missing_argument_linter(except = "tibble") ) lint( text = 'tibble(x = "a", )', linters = missing_argument_linter(allow_trailing = TRUE) )
# will produce lints lint( text = 'tibble(x = "a", )', linters = missing_argument_linter() ) # okay lint( text = 'tibble(x = "a")', linters = missing_argument_linter() ) lint( text = 'tibble(x = "a", )', linters = missing_argument_linter(except = "tibble") ) lint( text = 'tibble(x = "a", )', linters = missing_argument_linter(allow_trailing = TRUE) )
Check for missing packages in library()
, require()
, loadNamespace()
, and requireNamespace()
calls.
missing_package_linter()
missing_package_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "library(xyzxyz)", linters = missing_package_linter() ) # okay lint( text = "library(stats)", linters = missing_package_linter() )
# will produce lints lint( text = "library(xyzxyz)", linters = missing_package_linter() ) # okay lint( text = "library(stats)", linters = missing_package_linter() )
Modify a list of defaults by name, allowing for replacement, deletion and addition of new elements.
modify_defaults(defaults, ...)
modify_defaults(defaults, ...)
defaults |
named list of elements to modify. |
... |
arguments of elements to change. If unnamed, the argument is automatically named.
If the named argument already exists in |
A modified list of elements, sorted by name. To achieve this sort in a platform-independent way, two
transformations are applied to the names: (1) replace _
with 0
and (2) convert tolower()
.
linters_with_defaults for basing off lintr's set of default linters.
all_linters for basing off all available linters in lintr.
linters_with_tags for basing off tags attached to linters, possibly across multiple packages.
available_linters to get a data frame of available linters.
linters for a complete list of linters available in lintr.
# custom list of undesirable functions: # remove `sapply` (using `NULL`) # add `cat` (with an accompanying message), # add `print` (unnamed, i.e. with no accompanying message) # add `source` (as taken from `all_undesirable_functions`) my_undesirable_functions <- modify_defaults( defaults = default_undesirable_functions, sapply = NULL, "cat" = "No cat allowed", "print", all_undesirable_functions[["source"]] ) # list names of functions specified as undesirable names(my_undesirable_functions)
# custom list of undesirable functions: # remove `sapply` (using `NULL`) # add `cat` (with an accompanying message), # add `print` (unnamed, i.e. with no accompanying message) # add `source` (as taken from `all_undesirable_functions`) my_undesirable_functions <- modify_defaults( defaults = default_undesirable_functions, sapply = NULL, "cat" = "No cat allowed", "print", all_undesirable_functions[["source"]] ) # list names of functions specified as undesirable names(my_undesirable_functions)
Check for missing packages and symbols in namespace calls.
Note that using check_exports=TRUE
or check_nonexports=TRUE
will load packages used in user code so it could
potentially change the global state.
namespace_linter(check_exports = TRUE, check_nonexports = TRUE)
namespace_linter(check_exports = TRUE, check_nonexports = TRUE)
check_exports |
Check if |
check_nonexports |
Check if |
configurable, correctness, executing, robustness
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "xyzxyz::sd(c(1, 2, 3))", linters = namespace_linter() ) lint( text = "stats::ssd(c(1, 2, 3))", linters = namespace_linter() ) # okay lint( text = "stats::sd(c(1, 2, 3))", linters = namespace_linter() ) lint( text = "stats::ssd(c(1, 2, 3))", linters = namespace_linter(check_exports = FALSE) ) lint( text = "stats:::ssd(c(1, 2, 3))", linters = namespace_linter(check_nonexports = FALSE) )
# will produce lints lint( text = "xyzxyz::sd(c(1, 2, 3))", linters = namespace_linter() ) lint( text = "stats::ssd(c(1, 2, 3))", linters = namespace_linter() ) # okay lint( text = "stats::sd(c(1, 2, 3))", linters = namespace_linter() ) lint( text = "stats::ssd(c(1, 2, 3))", linters = namespace_linter(check_exports = FALSE) ) lint( text = "stats:::ssd(c(1, 2, 3))", linters = namespace_linter(check_nonexports = FALSE) )
ifelse()
callsCalling ifelse()
in nested calls is problematic for two main reasons:
It can be hard to read – mapping the code to the expected output for such code can be a messy task/require a lot of mental bandwidth, especially for code that nests more than once
It is inefficient – ifelse()
can evaluate all of its arguments at
both yes and no (see https://stackoverflow.com/q/16275149); this issue
is exacerbated for nested calls
nested_ifelse_linter()
nested_ifelse_linter()
Users can instead rely on a more readable alternative modeled after SQL CASE WHEN statements.
Let's say this is our original code:
ifelse( x == "a", 2L, ifelse(x == "b", 3L, 1L) )
Here are a few ways to avoid nesting and make the code more readable:
Use data.table::fcase()
data.table::fcase( x == "a", 2L, x == "b", 3L, default = 1L )
Use dplyr::case_match()
dplyr::case_match( x, "a" ~ 2L, "b" ~ 3L, .default = 1L )
Use a look-up-and-merge approach (build a mapping table between values and outputs and merge this to the input)
default <- 1L values <- data.frame( a = 2L, b = 3L ) found_value <- values[[x]] ifelse(is.null(found_value), default, found_value)
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'ifelse(x == "a", 1L, ifelse(x == "b", 2L, 3L))', linters = nested_ifelse_linter() ) # okay lint( text = 'dplyr::case_when(x == "a" ~ 1L, x == "b" ~ 2L, TRUE ~ 3L)', linters = nested_ifelse_linter() ) lint( text = 'data.table::fcase(x == "a", 1L, x == "b", 2L, default = 3L)', linters = nested_ifelse_linter() )
# will produce lints lint( text = 'ifelse(x == "a", 1L, ifelse(x == "b", 2L, 3L))', linters = nested_ifelse_linter() ) # okay lint( text = 'dplyr::case_when(x == "a" ~ 1L, x == "b" ~ 2L, TRUE ~ 3L)', linters = nested_ifelse_linter() ) lint( text = 'data.table::fcase(x == "a", 1L, x == "b", 2L, default = 3L)', linters = nested_ifelse_linter() )
Nesting pipes harms readability; extract sub-steps to separate variables, append further pipeline steps, or otherwise refactor such usage away.
nested_pipe_linter( allow_inline = TRUE, allow_outer_calls = c("try", "tryCatch", "withCallingHandlers") )
nested_pipe_linter( allow_inline = TRUE, allow_outer_calls = c("try", "tryCatch", "withCallingHandlers") )
allow_inline |
Logical, default |
allow_outer_calls |
Character vector dictating which "outer"
calls to exempt from the requirement to unnest (see examples). Defaults
to |
configurable, consistency, readability
linters for a complete list of linters available in lintr.
# will produce lints code <- "df1 %>%\n inner_join(df2 %>%\n select(a, b)\n )" writeLines(code) lint( text = code, linters = nested_pipe_linter() ) lint( text = "df1 %>% inner_join(df2 %>% select(a, b))", linters = nested_pipe_linter(allow_inline = FALSE) ) lint( text = "tryCatch(x %>% filter(grp == 'a'), error = identity)", linters = nested_pipe_linter(allow_outer_calls = character()) ) # okay lint( text = "df1 %>% inner_join(df2 %>% select(a, b))", linters = nested_pipe_linter() ) code <- "df1 %>%\n inner_join(df2 %>%\n select(a, b)\n )" writeLines(code) lint( text = code, linters = nested_pipe_linter(allow_outer_calls = "inner_join") ) lint( text = "tryCatch(x %>% filter(grp == 'a'), error = identity)", linters = nested_pipe_linter() )
# will produce lints code <- "df1 %>%\n inner_join(df2 %>%\n select(a, b)\n )" writeLines(code) lint( text = code, linters = nested_pipe_linter() ) lint( text = "df1 %>% inner_join(df2 %>% select(a, b))", linters = nested_pipe_linter(allow_inline = FALSE) ) lint( text = "tryCatch(x %>% filter(grp == 'a'), error = identity)", linters = nested_pipe_linter(allow_outer_calls = character()) ) # okay lint( text = "df1 %>% inner_join(df2 %>% select(a, b))", linters = nested_pipe_linter() ) code <- "df1 %>%\n inner_join(df2 %>%\n select(a, b)\n )" writeLines(code) lint( text = code, linters = nested_pipe_linter(allow_outer_calls = "inner_join") ) lint( text = "tryCatch(x %>% filter(grp == 'a'), error = identity)", linters = nested_pipe_linter() )
Check that file.path()
is used to construct safe and portable paths.
nonportable_path_linter(lax = TRUE)
nonportable_path_linter(lax = TRUE)
lax |
Less stringent linting, leading to fewer false positives.
If
|
best_practices, configurable, robustness
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "'abcdefg/hijklmnop/qrst/uv/wxyz'", linters = nonportable_path_linter() ) # okay lint( text = "file.path('abcdefg', 'hijklmnop', 'qrst', 'uv', 'wxyz')", linters = nonportable_path_linter() )
# will produce lints lint( text = "'abcdefg/hijklmnop/qrst/uv/wxyz'", linters = nonportable_path_linter() ) # okay lint( text = "file.path('abcdefg', 'hijklmnop', 'qrst', 'uv', 'wxyz')", linters = nonportable_path_linter() )
nrow(subset(x, .))
Using nrow(subset(x, condition))
to count the instances where condition
applies inefficiently requires doing a full subset of x
just to
count the number of rows in the resulting subset.
There are a number of equivalent expressions that don't require the full
subset, e.g. with(x, sum(condition))
(or, more generically,
with(x, sum(condition, na.rm = TRUE))
).
nrow_subset_linter()
nrow_subset_linter()
best_practices, consistency, efficiency
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "nrow(subset(x, is_treatment))", linters = nrow_subset_linter() ) lint( text = "nrow(filter(x, is_treatment))", linters = nrow_subset_linter() ) lint( text = "x %>% filter(x, is_treatment) %>% nrow()", linters = nrow_subset_linter() ) # okay lint( text = "with(x, sum(is_treatment, na.rm = TRUE))", linters = nrow_subset_linter() )
# will produce lints lint( text = "nrow(subset(x, is_treatment))", linters = nrow_subset_linter() ) lint( text = "nrow(filter(x, is_treatment))", linters = nrow_subset_linter() ) lint( text = "x %>% filter(x, is_treatment) %>% nrow()", linters = nrow_subset_linter() ) # okay lint( text = "with(x, sum(is_treatment, na.rm = TRUE))", linters = nrow_subset_linter() )
While .1 and 0.1 mean the same thing, the latter is easier to read due to the small size of the '.' glyph.
numeric_leading_zero_linter()
numeric_leading_zero_linter()
consistency, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x <- .1", linters = numeric_leading_zero_linter() ) lint( text = "x <- -.1", linters = numeric_leading_zero_linter() ) # okay lint( text = "x <- 0.1", linters = numeric_leading_zero_linter() ) lint( text = "x <- -0.1", linters = numeric_leading_zero_linter() )
# will produce lints lint( text = "x <- .1", linters = numeric_leading_zero_linter() ) lint( text = "x <- -.1", linters = numeric_leading_zero_linter() ) # okay lint( text = "x <- 0.1", linters = numeric_leading_zero_linter() ) lint( text = "x <- -0.1", linters = numeric_leading_zero_linter() )
nzchar()
efficiently determines which of a vector of strings are empty
(i.e., are ""
). It should in most cases be used instead of
constructions like string == ""
or nchar(string) == 0
.
nzchar_linter()
nzchar_linter()
One crucial difference is in the default handling of NA_character_
, i.e.,
missing strings. nzchar(NA_character_)
is TRUE
, while NA_character_ == ""
and nchar(NA_character_) == 0
are both NA
. Therefore, for strict
compatibility, use nzchar(x, keepNA = TRUE)
. If the input is known to be
complete (no missing entries), this argument can be dropped for conciseness.
best_practices, consistency, efficiency
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x[x == '']", linters = nzchar_linter() ) lint( text = "x[nchar(x) > 0]", linters = nzchar_linter() ) # okay lint( text = "x[!nzchar(x, keepNA = TRUE)]", linters = nzchar_linter() ) lint( text = "x[nzchar(x, keepNA = TRUE)]", linters = nzchar_linter() )
# will produce lints lint( text = "x[x == '']", linters = nzchar_linter() ) lint( text = "x[nchar(x) > 0]", linters = nzchar_linter() ) # okay lint( text = "x[!nzchar(x, keepNA = TRUE)]", linters = nzchar_linter() ) lint( text = "x[nzchar(x, keepNA = TRUE)]", linters = nzchar_linter() )
Check that object names are not too long. The length of an object name is defined as the length in characters, after removing extraneous parts:
object_length_linter(length = 30L)
object_length_linter(length = 30L)
length |
maximum variable name length allowed. |
generic prefixes for implementations of S3 generics, e.g. as.data.frame.my_class
has length 8.
leading .
, e.g. .my_hidden_function
has length 18.
"%%" for infix operators, e.g. %my_op%
has length 5.
trailing <-
for assignment functions, e.g. my_attr<-
has length 7.
Note that this behavior relies in part on having packages in your Imports available;
see the detailed note in object_name_linter()
for more details.
configurable, default, executing, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "very_very_long_variable_name <- 1L", linters = object_length_linter(length = 10L) ) # okay lint( text = "very_very_long_variable_name <- 1L", linters = object_length_linter(length = 30L) ) lint( text = "var <- 1L", linters = object_length_linter(length = 10L) )
# will produce lints lint( text = "very_very_long_variable_name <- 1L", linters = object_length_linter(length = 10L) ) # okay lint( text = "very_very_long_variable_name <- 1L", linters = object_length_linter(length = 30L) ) lint( text = "var <- 1L", linters = object_length_linter(length = 10L) )
Check that object names conform to a naming style. The default naming styles are "snake_case" and "symbols".
object_name_linter(styles = c("snake_case", "symbols"), regexes = character())
object_name_linter(styles = c("snake_case", "symbols"), regexes = character())
styles |
A subset of
‘symbols’, ‘CamelCase’, ‘camelCase’, ‘snake_case’, ‘SNAKE_CASE’, ‘dotted.case’, ‘lowercase’, ‘UPPERCASE’. A name should
match at least one of these styles. The |
regexes |
A (possibly named) character vector specifying a custom naming convention.
If named, the names will be used in the lint message. Otherwise, the regexes enclosed by |
Quotes (`"'
) and specials (%
and trailing <-
) are not considered part of the object name.
Note when used in a package, in order to ignore objects imported
from other namespaces, this linter will attempt getNamespaceExports()
whenever an import(PKG)
or importFrom(PKG, ...)
statement is found
in your NAMESPACE file. If requireNamespace()
fails (e.g., the package
is not yet installed), the linter won't be able to ignore some usages
that would otherwise be allowed.
Suppose, for example, you have import(upstream)
in your NAMESPACE,
which makes available its exported S3 generic function
a_really_quite_long_function_name
that you then extend in your package
by defining a corresponding method for your class my_class
.
Then, if upstream
is not installed when this linter runs, a lint
will be thrown on this object (even though you don't "own" its full name).
The best way to get lintr to work correctly is to install the package so that it's available in the session where this linter is running.
configurable, consistency, default, executing, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "my_var <- 1L", linters = object_name_linter(styles = "CamelCase") ) lint( text = "xYz <- 1L", linters = object_name_linter(styles = c("UPPERCASE", "lowercase")) ) lint( text = "MyVar <- 1L", linters = object_name_linter(styles = "dotted.case") ) lint( text = "asd <- 1L", linters = object_name_linter(regexes = c(my_style = "F$", "f$")) ) # okay lint( text = "my_var <- 1L", linters = object_name_linter(styles = "snake_case") ) lint( text = "xyz <- 1L", linters = object_name_linter(styles = "lowercase") ) lint( text = "my.var <- 1L; myvar <- 2L", linters = object_name_linter(styles = c("dotted.case", "lowercase")) ) lint( text = "asdf <- 1L; asdF <- 1L", linters = object_name_linter(regexes = c(my_style = "F$", "f$")) )
# will produce lints lint( text = "my_var <- 1L", linters = object_name_linter(styles = "CamelCase") ) lint( text = "xYz <- 1L", linters = object_name_linter(styles = c("UPPERCASE", "lowercase")) ) lint( text = "MyVar <- 1L", linters = object_name_linter(styles = "dotted.case") ) lint( text = "asd <- 1L", linters = object_name_linter(regexes = c(my_style = "F$", "f$")) ) # okay lint( text = "my_var <- 1L", linters = object_name_linter(styles = "snake_case") ) lint( text = "xyz <- 1L", linters = object_name_linter(styles = "lowercase") ) lint( text = "my.var <- 1L; myvar <- 2L", linters = object_name_linter(styles = c("dotted.case", "lowercase")) ) lint( text = "asdf <- 1L; asdF <- 1L", linters = object_name_linter(regexes = c(my_style = "F$", "f$")) )
base
R functionRe-using existing names creates a risk of subtle error best avoided. Avoiding this practice also encourages using better, more descriptive names.
object_overwrite_linter( packages = c("base", "stats", "utils", "tools", "methods", "graphics", "grDevices"), allow_names = character() )
object_overwrite_linter( packages = c("base", "stats", "utils", "tools", "methods", "graphics", "grDevices"), allow_names = character() )
packages |
Character vector of packages to search for names that should be avoided. Defaults to the most common default packages: base, stats, utils, tools, methods, graphics, and grDevices. |
allow_names |
Character vector of object names to ignore, i.e., which
are allowed to collide with exports from |
best_practices, configurable, executing, readability, robustness
linters for a complete list of linters available in lintr.
# will produce lints code <- "function(x) {\n data <- x\n data\n}" writeLines(code) lint( text = code, linters = object_overwrite_linter() ) code <- "function(x) {\n lint <- 'fun'\n lint\n}" writeLines(code) lint( text = code, linters = object_overwrite_linter(packages = "lintr") ) # okay code <- "function(x) {\n data('mtcars')\n}" writeLines(code) lint( text = code, linters = object_overwrite_linter() ) code <- "function(x) {\n data <- x\n data\n}" writeLines(code) lint( text = code, linters = object_overwrite_linter(packages = "base") ) # names in function signatures are ignored lint( text = "function(data) data <- subset(data, x > 0)", linters = object_overwrite_linter() )
# will produce lints code <- "function(x) {\n data <- x\n data\n}" writeLines(code) lint( text = code, linters = object_overwrite_linter() ) code <- "function(x) {\n lint <- 'fun'\n lint\n}" writeLines(code) lint( text = code, linters = object_overwrite_linter(packages = "lintr") ) # okay code <- "function(x) {\n data('mtcars')\n}" writeLines(code) lint( text = code, linters = object_overwrite_linter() ) code <- "function(x) {\n data <- x\n data\n}" writeLines(code) lint( text = code, linters = object_overwrite_linter(packages = "base") ) # names in function signatures are ignored lint( text = "function(data) data <- subset(data, x > 0)", linters = object_overwrite_linter() )
Check that closures have the proper usage using codetools::checkUsage()
.
Note that this runs base::eval()
on the code, so do not use with untrusted code.
object_usage_linter(interpret_glue = TRUE, skip_with = TRUE)
object_usage_linter(interpret_glue = TRUE, skip_with = TRUE)
interpret_glue |
If |
skip_with |
A logical. If |
The following linters are tagged with 'package_development':
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "foo <- function() { x <- 1 }", linters = object_usage_linter() ) # okay lint( text = "foo <- function(x) { x <- 1 }", linters = object_usage_linter() ) lint( text = "foo <- function() { x <- 1; return(x) }", linters = object_usage_linter() )
# will produce lints lint( text = "foo <- function() { x <- 1 }", linters = object_usage_linter() ) # okay lint( text = "foo <- function(x) { x <- 1 }", linters = object_usage_linter() ) lint( text = "foo <- function() { x <- 1; return(x) }", linters = object_usage_linter() )
Prefer using a plain call instead of a pipe with only one call,
i.e. 1:10 %>% sum()
should instead be sum(1:10)
. Note that
calls in the first %>%
argument count. rowSums(x) %>% max()
is OK
because there are two total calls (rowSums()
and max()
).
one_call_pipe_linter()
one_call_pipe_linter()
Note also that un-"called" steps are not counted, since they should
be calls (see pipe_call_linter()
).
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "(1:10) %>% sum()", linters = one_call_pipe_linter() ) lint( text = "DT %>% .[grp == 'a', sum(v)]", linters = one_call_pipe_linter() ) # okay lint( text = "rowSums(x) %>% mean()", linters = one_call_pipe_linter() ) lint( text = "DT[src == 'a', .N, by = grp] %>% .[N > 10]", linters = one_call_pipe_linter() ) # assignment pipe is exempted lint( text = "DF %<>% mutate(a = 2)", linters = one_call_pipe_linter() )
# will produce lints lint( text = "(1:10) %>% sum()", linters = one_call_pipe_linter() ) lint( text = "DT %>% .[grp == 'a', sum(v)]", linters = one_call_pipe_linter() ) # okay lint( text = "rowSums(x) %>% mean()", linters = one_call_pipe_linter() ) lint( text = "DT[src == 'a', .N, by = grp] %>% .[N > 10]", linters = one_call_pipe_linter() ) # assignment pipe is exempted lint( text = "DF %<>% mutate(a = 2)", linters = one_call_pipe_linter() )
!any(x)
over all(!x)
, !all(x)
over any(!x)
any(!x)
is logically equivalent to !all(x)
; ditto for the equivalence of
all(!x)
and !any(x)
. Negating after aggregation only requires inverting
one logical value, and is typically more readable.
outer_negation_linter()
outer_negation_linter()
best_practices, efficiency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "all(!x)", linters = outer_negation_linter() ) lint( text = "any(!x)", linters = outer_negation_linter() ) # okay lint( text = "!any(x)", linters = outer_negation_linter() ) lint( text = "!all(x)", linters = outer_negation_linter() )
# will produce lints lint( text = "all(!x)", linters = outer_negation_linter() ) lint( text = "any(!x)", linters = outer_negation_linter() ) # okay lint( text = "!any(x)", linters = outer_negation_linter() ) lint( text = "!all(x)", linters = outer_negation_linter() )
Linters useful to package developers, for example for writing consistent tests.
The following linters are tagged with 'package_development':
linters for a complete list of linters available in lintr.
Check various common "gotchas" in .onLoad()
, .onAttach()
, .Last.lib()
, and .onDetach()
namespace hooks that will cause R CMD check
issues. See Writing R Extensions for details.
package_hooks_linter()
package_hooks_linter()
.onLoad()
shouldn't call cat()
, message()
, print()
, writeLines()
, packageStartupMessage()
,
require()
, library()
, or installed.packages()
.
.onAttach()
shouldn't call cat()
, message()
, print()
, writeLines()
, library.dynam()
,
require()
, library()
, or installed.packages()
.
.Last.lib()
and .onDetach()
shouldn't call library.dynam.unload()
.
.onLoad()
and .onAttach()
should take two arguments, with names matching ^lib
and ^pkg
;
.Last.lib()
and .onDetach()
should take one argument with name matching ^lib
.
correctness, package_development, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = ".onLoad <- function(lib, ...) { }", linters = package_hooks_linter() ) lint( text = ".onAttach <- function(lib, pkg) { require(foo) }", linters = package_hooks_linter() ) lint( text = ".onDetach <- function(pkg) { }", linters = package_hooks_linter() ) # okay lint( text = ".onLoad <- function(lib, pkg) { }", linters = package_hooks_linter() ) lint( text = '.onAttach <- function(lib, pkg) { loadNamespace("foo") }', linters = package_hooks_linter() ) lint( text = ".onDetach <- function(lib) { }", linters = package_hooks_linter() )
# will produce lints lint( text = ".onLoad <- function(lib, ...) { }", linters = package_hooks_linter() ) lint( text = ".onAttach <- function(lib, pkg) { require(foo) }", linters = package_hooks_linter() ) lint( text = ".onDetach <- function(pkg) { }", linters = package_hooks_linter() ) # okay lint( text = ".onLoad <- function(lib, pkg) { }", linters = package_hooks_linter() ) lint( text = '.onAttach <- function(lib, pkg) { loadNamespace("foo") }', linters = package_hooks_linter() ) lint( text = ".onDetach <- function(lib) { }", linters = package_hooks_linter() )
Check that there is a space between right parenthesis and a body expression.
paren_body_linter()
paren_body_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "function(x)x + 1", linters = paren_body_linter() ) # okay lint( text = "function(x) x + 1", linters = paren_body_linter() )
# will produce lints lint( text = "function(x)x + 1", linters = paren_body_linter() ) # okay lint( text = "function(x) x + 1", linters = paren_body_linter() )
paste()
The following issues are linted by default by this linter (see arguments for which can be de-activated optionally):
Block usage of paste()
with sep = ""
. paste0()
is a faster, more concise alternative.
Block usage of paste()
or paste0()
with collapse = ", "
. toString()
is a direct
wrapper for this, and alternatives like glue::glue_collapse()
might give better messages for humans.
Block usage of paste0()
that supplies sep=
– this is not a formal argument to paste0
, and
is likely to be a mistake.
Block usage of paste()
/ paste0()
combined with rep()
that could be replaced by
strrep()
. strrep()
can handle the task of building a block of repeated strings
(e.g. often used to build "horizontal lines" for messages). This is both more readable and
skips the (likely small) overhead of putting two strings into the global string cache when only one is needed.
Only target scalar usages – strrep
can handle more complicated cases (e.g. strrep(letters, 26:1)
,
but those aren't as easily translated from a paste(collapse=)
call.
paste_linter( allow_empty_sep = FALSE, allow_to_string = FALSE, allow_file_path = c("double_slash", "always", "never") )
paste_linter( allow_empty_sep = FALSE, allow_to_string = FALSE, allow_file_path = c("double_slash", "always", "never") )
allow_empty_sep |
Logical, default |
allow_to_string |
Logical, default |
allow_file_path |
String, one of |
best_practices, configurable, consistency
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'paste("a", "b", sep = "")', linters = paste_linter() ) lint( text = 'paste(c("a", "b"), collapse = ", ")', linters = paste_linter() ) lint( text = 'paste0(c("a", "b"), sep = " ")', linters = paste_linter() ) lint( text = 'paste0(rep("*", 10L), collapse = "")', linters = paste_linter() ) lint( text = 'paste0("http://site.com/", path)', linters = paste_linter(allow_file_path = "never") ) lint( text = 'paste0(x, collapse = "")', linters = paste_linter() ) # okay lint( text = 'paste0("a", "b")', linters = paste_linter() ) lint( text = 'paste("a", "b", sep = "")', linters = paste_linter(allow_empty_sep = TRUE) ) lint( text = 'toString(c("a", "b"))', linters = paste_linter() ) lint( text = 'paste(c("a", "b"), collapse = ", ")', linters = paste_linter(allow_to_string = TRUE) ) lint( text = 'paste(c("a", "b"))', linters = paste_linter() ) lint( text = 'strrep("*", 10L)', linters = paste_linter() ) lint( text = 'paste0(year, "/", month, "/", day)', linters = paste_linter(allow_file_path = "always") ) lint( text = 'paste0("http://site.com/", path)', linters = paste_linter() ) lint( text = 'paste(x, collapse = "")', linters = paste_linter() )
# will produce lints lint( text = 'paste("a", "b", sep = "")', linters = paste_linter() ) lint( text = 'paste(c("a", "b"), collapse = ", ")', linters = paste_linter() ) lint( text = 'paste0(c("a", "b"), sep = " ")', linters = paste_linter() ) lint( text = 'paste0(rep("*", 10L), collapse = "")', linters = paste_linter() ) lint( text = 'paste0("http://site.com/", path)', linters = paste_linter(allow_file_path = "never") ) lint( text = 'paste0(x, collapse = "")', linters = paste_linter() ) # okay lint( text = 'paste0("a", "b")', linters = paste_linter() ) lint( text = 'paste("a", "b", sep = "")', linters = paste_linter(allow_empty_sep = TRUE) ) lint( text = 'toString(c("a", "b"))', linters = paste_linter() ) lint( text = 'paste(c("a", "b"), collapse = ", ")', linters = paste_linter(allow_to_string = TRUE) ) lint( text = 'paste(c("a", "b"))', linters = paste_linter() ) lint( text = 'strrep("*", 10L)', linters = paste_linter() ) lint( text = 'paste0(year, "/", month, "/", day)', linters = paste_linter(allow_file_path = "always") ) lint( text = 'paste0("http://site.com/", path)', linters = paste_linter() ) lint( text = 'paste(x, collapse = "")', linters = paste_linter() )
Force explicit calls in magrittr pipes, e.g., 1:3 %>% sum()
instead of 1:3 %>% sum
.
Note that native pipe always requires a function call, i.e. 1:3 |> sum
will produce an error.
pipe_call_linter()
pipe_call_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "1:3 %>% mean %>% as.character", linters = pipe_call_linter() ) # okay lint( text = "1:3 %>% mean() %>% as.character()", linters = pipe_call_linter() )
# will produce lints lint( text = "1:3 %>% mean %>% as.character", linters = pipe_call_linter() ) # okay lint( text = "1:3 %>% mean() %>% as.character()", linters = pipe_call_linter() )
Check that pipe operators are used consistently by file, or optionally specify one valid pipe operator.
pipe_consistency_linter(pipe = c("auto", "%>%", "|>"))
pipe_consistency_linter(pipe = c("auto", "%>%", "|>"))
pipe |
Which pipe operator is valid (either |
configurable, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "1:3 |> mean() %>% as.character()", linters = pipe_consistency_linter() ) lint( text = "1:3 %>% mean() %>% as.character()", linters = pipe_consistency_linter("|>") ) # okay lint( text = "1:3 %>% mean() %>% as.character()", linters = pipe_consistency_linter() ) lint( text = "1:3 |> mean() |> as.character()", linters = pipe_consistency_linter() )
# will produce lints lint( text = "1:3 |> mean() %>% as.character()", linters = pipe_consistency_linter() ) lint( text = "1:3 %>% mean() %>% as.character()", linters = pipe_consistency_linter("|>") ) # okay lint( text = "1:3 %>% mean() %>% as.character()", linters = pipe_consistency_linter() ) lint( text = "1:3 |> mean() |> as.character()", linters = pipe_consistency_linter() )
Check that each step in a pipeline is on a new line, or the entire pipe fits on one line.
pipe_continuation_linter()
pipe_continuation_linter()
linters for a complete list of linters available in lintr.
# will produce lints code_lines <- "1:3 %>%\n mean() %>% as.character()" writeLines(code_lines) lint( text = code_lines, linters = pipe_continuation_linter() ) code_lines <- "1:3 |> mean() |>\n as.character()" writeLines(code_lines) lint( text = code_lines, linters = pipe_continuation_linter() ) # okay lint( text = "1:3 %>% mean() %>% as.character()", linters = pipe_continuation_linter() ) code_lines <- "1:3 %>%\n mean() %>%\n as.character()" writeLines(code_lines) lint( text = code_lines, linters = pipe_continuation_linter() ) lint( text = "1:3 |> mean() |> as.character()", linters = pipe_continuation_linter() ) code_lines <- "1:3 |>\n mean() |>\n as.character()" writeLines(code_lines) lint( text = code_lines, linters = pipe_continuation_linter() )
# will produce lints code_lines <- "1:3 %>%\n mean() %>% as.character()" writeLines(code_lines) lint( text = code_lines, linters = pipe_continuation_linter() ) code_lines <- "1:3 |> mean() |>\n as.character()" writeLines(code_lines) lint( text = code_lines, linters = pipe_continuation_linter() ) # okay lint( text = "1:3 %>% mean() %>% as.character()", linters = pipe_continuation_linter() ) code_lines <- "1:3 %>%\n mean() %>%\n as.character()" writeLines(code_lines) lint( text = code_lines, linters = pipe_continuation_linter() ) lint( text = "1:3 |> mean() |> as.character()", linters = pipe_continuation_linter() ) code_lines <- "1:3 |>\n mean() |>\n as.character()" writeLines(code_lines) lint( text = code_lines, linters = pipe_continuation_linter() )
return()
inside a magrittr pipeline does not actually execute return()
like you'd expect: \(x) { x %>% return(); FALSE }
will return FALSE
!
It will technically work "as expected" if this is the final statement
in the function body, but such usage is misleading. Instead, assign
the pipe outcome to a variable and return that.
pipe_return_linter()
pipe_return_linter()
best_practices, common_mistakes
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "function(x) x %>% return()", linters = pipe_return_linter() ) # okay code <- "function(x) {\n y <- sum(x)\n return(y)\n}" writeLines(code) lint( text = code, linters = pipe_return_linter() )
# will produce lints lint( text = "function(x) x %>% return()", linters = pipe_return_linter() ) # okay code <- "function(x) {\n y <- sum(x)\n return(y)\n}" writeLines(code) lint( text = code, linters = pipe_return_linter() )
Linters encouraging best practices within testthat suites.
The following linters are tagged with 'pkg_testthat':
linters for a complete list of linters available in lintr.
The default print method for character vectors is appropriate for interactively inspecting objects,
not for logging messages. Thus checked-in usage like print(paste('Data has', nrow(DF), 'rows.'))
is better served by using cat()
, e.g. cat(sprintf('Data has %d rows.\n', nrow(DF)))
(noting that
using cat()
entails supplying your own line returns, and that glue::glue()
might be preferable
to sprintf()
for constructing templated strings). Lastly, note that message()
differs slightly
from cat()
in that it prints to stderr
by default, not stdout
, but is still a good option
to consider for logging purposes.
print_linter()
print_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "print('a')", linters = print_linter() ) lint( text = "print(paste(x, 'y'))", linters = print_linter() ) # okay lint( text = "print(x)", linters = print_linter() )
# will produce lints lint( text = "print('a')", linters = print_linter() ) lint( text = "print(paste(x, 'y'))", linters = print_linter() ) # okay lint( text = "print(x)", linters = print_linter() )
Check that the desired quote delimiter is used for string constants.
quotes_linter(delimiter = c("\"", "'"))
quotes_linter(delimiter = c("\"", "'"))
delimiter |
Which quote delimiter to accept. Defaults to the tidyverse
default of |
configurable, consistency, default, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "c('a', 'b')", linters = quotes_linter() ) # okay lint( text = 'c("a", "b")', linters = quotes_linter() ) code_lines <- "paste0(x, '\"this is fine\"')" writeLines(code_lines) lint( text = code_lines, linters = quotes_linter() ) # okay lint( text = "c('a', 'b')", linters = quotes_linter(delimiter = "'") )
# will produce lints lint( text = "c('a', 'b')", linters = quotes_linter() ) # okay lint( text = 'c("a", "b")', linters = quotes_linter() ) code_lines <- "paste0(x, '\"this is fine\"')" writeLines(code_lines) lint( text = code_lines, linters = quotes_linter() ) # okay lint( text = "c('a', 'b')", linters = quotes_linter(delimiter = "'") )
Lintr searches for settings for a given source file in the following order:
options defined as linter.setting
.
linter_file
in the same directory
linter_file
in the project directory
linter_file
in the user home directory
read_settings(filename, call = parent.frame())
read_settings(filename, call = parent.frame())
filename |
Source file to be linted. |
call |
Passed to malformed to ensure linear trace. |
The default linter_file name is .lintr
but it can be changed with option lintr.linter_file
or the environment variable R_LINTR_LINTER_FILE
This file is a DCF file, see base::read.dcf()
for details.
Here is an example of a .lintr
file:
linters: linters_with_defaults( any_duplicated_linter(), any_is_na_linter(), backport_linter("oldrel-4", except = c("R_user_dir", "str2lang")), line_length_linter(120L), missing_argument_linter(), unnecessary_concatenation_linter(allow_single_expression = FALSE), yoda_test_linter() ) exclusions: list( "inst/doc/creating_linters.R" = 1, "inst/example/bad.R", "tests/testthat/default_linter_testcode.R", "tests/testthat/dummy_packages" )
Experimentally, we also support keeping the config in a plain R file. By default we look for
a file named .lintr.R
(in the same directories where we search for .lintr
).
We are still deciding the future of config support in lintr, so user feedback is welcome.
The advantage of R is that it maps more closely to how the configs are actually stored,
whereas the DCF approach requires somewhat awkward formatting of parseable R code within
valid DCF key-value pairs. The main disadvantage of the R file is it might be too flexible,
with users tempted to write configs with side effects causing hard-to-detect bugs or
like YAML could work, but require new dependencies and are harder to parse
both programmatically and visually.
Here is an example of a .lintr.R
file:
linters <- linters_with_defaults( any_duplicated_linter(), any_is_na_linter(), backport_linter("oldrel-4", except = c("R_user_dir", "str2lang")), line_length_linter(120L), missing_argument_linter(), unnecessary_concatenation_linter(allow_single_expression = FALSE), yoda_test_linter() ) exclusions <- list( "inst/doc/creating_linters.R" = 1, "inst/example/bad.R", "tests/testthat/default_linter_testcode.R", "tests/testthat/dummy_packages" )
Linters highlighting readability issues, such as missing whitespace.
The following linters are tagged with 'readability':
linters for a complete list of linters available in lintr.
==
, !=
on logical vectorsTesting x == TRUE
is redundant if x
is a logical vector. Wherever this is
used to improve readability, the solution should instead be to improve the
naming of the object to better indicate that its contents are logical. This
can be done using prefixes (is, has, can, etc.). For example, is_child
,
has_parent_supervision
, can_watch_horror_movie
clarify their logical
nature, while child
, parent_supervision
, watch_horror_movie
don't.
redundant_equals_linter()
redundant_equals_linter()
best_practices, common_mistakes, efficiency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "if (any(x == TRUE)) 1", linters = redundant_equals_linter() ) lint( text = "if (any(x != FALSE)) 0", linters = redundant_equals_linter() ) lint( text = "dt[is_tall == FALSE, y]", linters = redundant_equals_linter() ) # okay lint( text = "if (any(x)) 1", linters = redundant_equals_linter() ) lint( text = "if (!all(x)) 0", linters = redundant_equals_linter() ) # in `{data.table}` semantics, `dt[x]` is a join, `dt[(x)]` is a subset lint( text = "dt[(!is_tall), y]", linters = redundant_equals_linter() )
# will produce lints lint( text = "if (any(x == TRUE)) 1", linters = redundant_equals_linter() ) lint( text = "if (any(x != FALSE)) 0", linters = redundant_equals_linter() ) lint( text = "dt[is_tall == FALSE, y]", linters = redundant_equals_linter() ) # okay lint( text = "if (any(x)) 1", linters = redundant_equals_linter() ) lint( text = "if (!all(x)) 0", linters = redundant_equals_linter() ) # in `{data.table}` semantics, `dt[x]` is a join, `dt[(x)]` is a subset lint( text = "dt[(!is_tall), y]", linters = redundant_equals_linter() )
ifelse()
from being used to produce TRUE
/FALSE
or 1
/0
Expressions like ifelse(x, TRUE, FALSE)
and ifelse(x, FALSE, TRUE)
are
redundant; just x
or !x
suffice in R code where logical vectors are a
core data structure. ifelse(x, 1, 0)
is also as.numeric(x)
, but even
this should be needed only rarely.
redundant_ifelse_linter(allow10 = FALSE)
redundant_ifelse_linter(allow10 = FALSE)
allow10 |
Logical, default |
best_practices, configurable, consistency, efficiency
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "ifelse(x >= 2.5, TRUE, FALSE)", linters = redundant_ifelse_linter() ) lint( text = "ifelse(x < 2.5, 1L, 0L)", linters = redundant_ifelse_linter() ) # okay lint( text = "x >= 2.5", linters = redundant_ifelse_linter() ) # Note that this is just to show the strict equivalent of the example above; # converting to integer is often unnecessary and the logical vector itself # should suffice. lint( text = "as.integer(x < 2.5)", linters = redundant_ifelse_linter() ) lint( text = "ifelse(x < 2.5, 1L, 0L)", linters = redundant_ifelse_linter(allow10 = TRUE) )
# will produce lints lint( text = "ifelse(x >= 2.5, TRUE, FALSE)", linters = redundant_ifelse_linter() ) lint( text = "ifelse(x < 2.5, 1L, 0L)", linters = redundant_ifelse_linter() ) # okay lint( text = "x >= 2.5", linters = redundant_ifelse_linter() ) # Note that this is just to show the strict equivalent of the example above; # converting to integer is often unnecessary and the logical vector itself # should suffice. lint( text = "as.integer(x < 2.5)", linters = redundant_ifelse_linter() ) lint( text = "ifelse(x < 2.5, 1L, 0L)", linters = redundant_ifelse_linter(allow10 = TRUE) )
Linters that examine the usage of regular expressions and functions executing them in user code.
The following linters are tagged with 'regex':
linters for a complete list of linters available in lintr.
Using value = TRUE
in grep()
returns the subset of the input that matches
the pattern, e.g. grep("[a-m]", letters, value = TRUE)
will return the
first 13 elements (a
through m
).
regex_subset_linter()
regex_subset_linter()
letters[grep("[a-m]", letters)]
and letters[grepl("[a-m]", letters)]
both return the same thing, but more circuitously and more verbosely.
The stringr
package also provides an even more readable alternative,
namely str_subset()
, which should be preferred to versions using
str_detect()
and str_which()
.
Note that x[grep(pattern, x)]
and grep(pattern, x, value = TRUE)
are not completely interchangeable when x
is not character
(most commonly, when x
is a factor), because the output of the
latter will be a character vector while the former remains a factor.
It still may be preferable to refactor such code, as it may be faster
to match the pattern on levels(x)
and use that to subset instead.
best_practices, efficiency, regex
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x[grep(pattern, x)]", linters = regex_subset_linter() ) lint( text = "x[stringr::str_which(x, pattern)]", linters = regex_subset_linter() ) # okay lint( text = "grep(pattern, x, value = TRUE)", linters = regex_subset_linter() ) lint( text = "stringr::str_subset(x, pattern)", linters = regex_subset_linter() )
# will produce lints lint( text = "x[grep(pattern, x)]", linters = regex_subset_linter() ) lint( text = "x[stringr::str_which(x, pattern)]", linters = regex_subset_linter() ) # okay lint( text = "grep(pattern, x, value = TRUE)", linters = regex_subset_linter() ) lint( text = "stringr::str_subset(x, pattern)", linters = regex_subset_linter() )
rep(x, length.out = n)
calls rep_len(x, n)
"under the hood". The latter
is thus more direct and equally readable.
rep_len_linter()
rep_len_linter()
best_practices, consistency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "rep(1:3, length.out = 10)", linters = rep_len_linter() ) # okay lint( text = "rep_len(1:3, 10)", linters = rep_len_linter() ) lint( text = "rep(1:3, each = 2L, length.out = 10L)", linters = rep_len_linter() )
# will produce lints lint( text = "rep(1:3, length.out = 10)", linters = rep_len_linter() ) # okay lint( text = "rep_len(1:3, 10)", linters = rep_len_linter() ) lint( text = "rep(1:3, each = 2L, length.out = 10L)", linters = rep_len_linter() )
Check that while (TRUE)
is not used for infinite loops.
repeat_linter()
repeat_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "while (TRUE) { }", linters = repeat_linter() ) # okay lint( text = "repeat { }", linters = repeat_linter() )
# will produce lints lint( text = "while (TRUE) { }", linters = repeat_linter() ) # okay lint( text = "repeat { }", linters = repeat_linter() )
This linter checks functions' return()
expressions.
return_linter( return_style = c("implicit", "explicit"), allow_implicit_else = TRUE, return_functions = NULL, except = NULL, except_regex = NULL )
return_linter( return_style = c("implicit", "explicit"), allow_implicit_else = TRUE, return_functions = NULL, except = NULL, except_regex = NULL )
return_style |
Character string naming the return style. |
allow_implicit_else |
Logical, default |
return_functions |
Character vector of functions that are accepted as terminal calls
when |
except , except_regex
|
Character vector of functions that are not checked when
|
linters for a complete list of linters available in lintr.
# will produce lints code <- "function(x) {\n return(x + 1)\n}" writeLines(code) lint( text = code, linters = return_linter() ) code <- "function(x) {\n x + 1\n}" writeLines(code) lint( text = code, linters = return_linter(return_style = "explicit") ) code <- "function(x) {\n if (x > 0) 2\n}" writeLines(code) lint( text = code, linters = return_linter(allow_implicit_else = FALSE) ) # okay code <- "function(x) {\n x + 1\n}" writeLines(code) lint( text = code, linters = return_linter() ) code <- "function(x) {\n return(x + 1)\n}" writeLines(code) lint( text = code, linters = return_linter(return_style = "explicit") ) code <- "function(x) {\n if (x > 0) 2 else NULL\n}" writeLines(code) lint( text = code, linters = return_linter(allow_implicit_else = FALSE) )
# will produce lints code <- "function(x) {\n return(x + 1)\n}" writeLines(code) lint( text = code, linters = return_linter() ) code <- "function(x) {\n x + 1\n}" writeLines(code) lint( text = code, linters = return_linter(return_style = "explicit") ) code <- "function(x) {\n if (x > 0) 2\n}" writeLines(code) lint( text = code, linters = return_linter(allow_implicit_else = FALSE) ) # okay code <- "function(x) {\n x + 1\n}" writeLines(code) lint( text = code, linters = return_linter() ) code <- "function(x) {\n return(x + 1)\n}" writeLines(code) lint( text = code, linters = return_linter(return_style = "explicit") ) code <- "function(x) {\n if (x > 0) 2 else NULL\n}" writeLines(code) lint( text = code, linters = return_linter(allow_implicit_else = FALSE) )
Linters highlighting code robustness issues, such as possibly wrong edge case behavior.
The following linters are tagged with 'robustness':
linters for a complete list of linters available in lintr.
It is preferable to register routines for efficiency and safety.
routine_registration_linter()
routine_registration_linter()
best_practices, efficiency, robustness
linters for a complete list of linters available in lintr.
https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Registering-native-routines
# will produce lints lint( text = '.Call("cpp_routine", PACKAGE = "mypkg")', linters = routine_registration_linter() ) lint( text = '.Fortran("f_routine", PACKAGE = "mypkg")', linters = routine_registration_linter() ) # okay lint( text = ".Call(cpp_routine)", linters = routine_registration_linter() ) lint( text = ".Fortran(f_routine)", linters = routine_registration_linter() )
# will produce lints lint( text = '.Call("cpp_routine", PACKAGE = "mypkg")', linters = routine_registration_linter() ) lint( text = '.Fortran("f_routine", PACKAGE = "mypkg")', linters = routine_registration_linter() ) # okay lint( text = ".Call(cpp_routine)", linters = routine_registration_linter() ) lint( text = ".Fortran(f_routine)", linters = routine_registration_linter() )
sample.int()
is preferable to sample()
for the case of sampling numbers
between 1 and n
. sample
calls sample.int()
"under the hood".
sample_int_linter()
sample_int_linter()
efficiency, readability, robustness
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "sample(1:10, 2)", linters = sample_int_linter() ) lint( text = "sample(seq(4), 2)", linters = sample_int_linter() ) lint( text = "sample(seq_len(8), 2)", linters = sample_int_linter() ) # okay lint( text = "sample(seq(1, 5, by = 2), 2)", linters = sample_int_linter() ) lint( text = "sample(letters, 2)", linters = sample_int_linter() )
# will produce lints lint( text = "sample(1:10, 2)", linters = sample_int_linter() ) lint( text = "sample(seq(4), 2)", linters = sample_int_linter() ) lint( text = "sample(seq_len(8), 2)", linters = sample_int_linter() ) # okay lint( text = "sample(seq(1, 5, by = 2), 2)", linters = sample_int_linter() ) lint( text = "sample(letters, 2)", linters = sample_int_linter() )
Generate a report of the linting results using the SARIF format.
sarif_output(lints, filename = "lintr_results.sarif")
sarif_output(lints, filename = "lintr_results.sarif")
lints |
the linting results. |
filename |
the name of the output report |
vector %in% set
is appropriate for matching a vector to a set, but if
that set has size 1, ==
is more appropriate.
scalar_in_linter(in_operators = NULL)
scalar_in_linter(in_operators = NULL)
in_operators |
Character vector of additional infix operators that behave like the |
scalar %in% vector
is OK, because the alternative (any(vector == scalar)
)
is more circuitous & potentially less clear.
best_practices, configurable, consistency, efficiency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x %in% 1L", linters = scalar_in_linter() ) lint( text = "x %chin% 'a'", linters = scalar_in_linter(in_operators = "%chin%") ) # okay lint( text = "x %in% 1:10", linters = scalar_in_linter() )
# will produce lints lint( text = "x %in% 1L", linters = scalar_in_linter() ) lint( text = "x %chin% 'a'", linters = scalar_in_linter(in_operators = "%chin%") ) # okay lint( text = "x %in% 1:10", linters = scalar_in_linter() )
Check that no semicolons terminate expressions.
semicolon_linter(allow_compound = FALSE, allow_trailing = FALSE)
semicolon_linter(allow_compound = FALSE, allow_trailing = FALSE)
allow_compound |
Logical, default |
allow_trailing |
Logical, default |
configurable, default, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "a <- 1;", linters = semicolon_linter() ) lint( text = "a <- 1; b <- 1", linters = semicolon_linter() ) lint( text = "function() { a <- 1; b <- 1 }", linters = semicolon_linter() ) # okay lint( text = "a <- 1", linters = semicolon_linter() ) lint( text = "a <- 1;", linters = semicolon_linter(allow_trailing = TRUE) ) code_lines <- "a <- 1\nb <- 1" writeLines(code_lines) lint( text = code_lines, linters = semicolon_linter() ) lint( text = "a <- 1; b <- 1", linters = semicolon_linter(allow_compound = TRUE) ) code_lines <- "function() { \n a <- 1\n b <- 1\n}" writeLines(code_lines) lint( text = code_lines, linters = semicolon_linter() )
# will produce lints lint( text = "a <- 1;", linters = semicolon_linter() ) lint( text = "a <- 1; b <- 1", linters = semicolon_linter() ) lint( text = "function() { a <- 1; b <- 1 }", linters = semicolon_linter() ) # okay lint( text = "a <- 1", linters = semicolon_linter() ) lint( text = "a <- 1;", linters = semicolon_linter(allow_trailing = TRUE) ) code_lines <- "a <- 1\nb <- 1" writeLines(code_lines) lint( text = code_lines, linters = semicolon_linter() ) lint( text = "a <- 1; b <- 1", linters = semicolon_linter(allow_compound = TRUE) ) code_lines <- "function() { \n a <- 1\n b <- 1\n}" writeLines(code_lines) lint( text = code_lines, linters = semicolon_linter() )
This linter checks for 1:length(...)
, 1:nrow(...)
, 1:ncol(...)
,
1:NROW(...)
and 1:NCOL(...)
expressions in base-R, or their usage in
conjunction with seq()
(e.g., seq(length(...))
, seq(nrow(...))
, etc.).
seq_linter()
seq_linter()
Additionally, it checks for 1:n()
(from {dplyr}
) and 1:.N
(from {data.table}
).
These often cause bugs when the right-hand side is zero.
Instead, it is safer to use base::seq_len()
(to create a sequence of a specified length) or
base::seq_along()
(to create a sequence along an object).
best_practices, consistency, default, efficiency, robustness
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "seq(length(x))", linters = seq_linter() ) lint( text = "1:nrow(x)", linters = seq_linter() ) lint( text = "dplyr::mutate(x, .id = 1:n())", linters = seq_linter() ) # okay lint( text = "seq_along(x)", linters = seq_linter() ) lint( text = "seq_len(nrow(x))", linters = seq_linter() ) lint( text = "dplyr::mutate(x, .id = seq_len(n()))", linters = seq_linter() )
# will produce lints lint( text = "seq(length(x))", linters = seq_linter() ) lint( text = "1:nrow(x)", linters = seq_linter() ) lint( text = "dplyr::mutate(x, .id = 1:n())", linters = seq_linter() ) # okay lint( text = "seq_along(x)", linters = seq_linter() ) lint( text = "seq_len(nrow(x))", linters = seq_linter() ) lint( text = "dplyr::mutate(x, .id = seq_len(n()))", linters = seq_linter() )
This linter checks for some common mistakes when using order()
or sort()
.
sort_linter()
sort_linter()
First, it requires usage of sort()
over .[order(.)]
.
sort()
is the dedicated option to sort a list or vector. It is more legible
and around twice as fast as .[order(.)]
, with the gap in performance
growing with the vector size.
Second, it requires usage of is.unsorted()
over equivalents using sort()
.
The base function is.unsorted()
exists to test the sortedness of a vector.
Prefer it to inefficient and less-readable equivalents like
x != sort(x)
. The same goes for checking x == sort(x)
– use
!is.unsorted(x)
instead.
Moreover, use of x == sort(x)
can be risky because sort()
drops missing
elements by default, meaning ==
might end up trying to compare vectors
of differing lengths.
best_practices, efficiency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x[order(x)]", linters = sort_linter() ) lint( text = "x[order(x, decreasing = TRUE)]", linters = sort_linter() ) lint( text = "sort(x) == x", linters = sort_linter() ) # okay lint( text = "x[sample(order(x))]", linters = sort_linter() ) lint( text = "y[order(x)]", linters = sort_linter() ) lint( text = "sort(x, decreasing = TRUE) == x", linters = sort_linter() ) # If you are sorting several objects based on the order of one of them, such # as: x <- sample(1:26) y <- letters newx <- x[order(x)] newy <- y[order(x)] # This will be flagged by the linter. However, in this very specific case, # it would be clearer and more efficient to run order() once and assign it # to an object, rather than mix and match order() and sort() index <- order(x) newx <- x[index] newy <- y[index]
# will produce lints lint( text = "x[order(x)]", linters = sort_linter() ) lint( text = "x[order(x, decreasing = TRUE)]", linters = sort_linter() ) lint( text = "sort(x) == x", linters = sort_linter() ) # okay lint( text = "x[sample(order(x))]", linters = sort_linter() ) lint( text = "y[order(x)]", linters = sort_linter() ) lint( text = "sort(x, decreasing = TRUE) == x", linters = sort_linter() ) # If you are sorting several objects based on the order of one of them, such # as: x <- sample(1:26) y <- letters newx <- x[order(x)] newy <- y[order(x)] # This will be flagged by the linter. However, in this very specific case, # it would be clearer and more efficient to run order() once and assign it # to an object, rather than mix and match order() and sort() index <- order(x) newx <- x[index] newy <- y[index]
Check that parentheses and square brackets do not have spaces directly inside them, i.e., directly following an opening delimiter or directly preceding a closing delimiter.
spaces_inside_linter()
spaces_inside_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "c( TRUE, FALSE )", linters = spaces_inside_linter() ) lint( text = "x[ 1L ]", linters = spaces_inside_linter() ) # okay lint( text = "c(TRUE, FALSE)", linters = spaces_inside_linter() ) lint( text = "x[1L]", linters = spaces_inside_linter() )
# will produce lints lint( text = "c( TRUE, FALSE )", linters = spaces_inside_linter() ) lint( text = "x[ 1L ]", linters = spaces_inside_linter() ) # okay lint( text = "c(TRUE, FALSE)", linters = spaces_inside_linter() ) lint( text = "x[1L]", linters = spaces_inside_linter() )
Check that all left parentheses have a space before them unless they are in a function call.
spaces_left_parentheses_linter()
spaces_left_parentheses_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "if(TRUE) x else y", linters = spaces_left_parentheses_linter() ) # okay lint( text = "if (TRUE) x else y", linters = spaces_left_parentheses_linter() )
# will produce lints lint( text = "if(TRUE) x else y", linters = spaces_left_parentheses_linter() ) # okay lint( text = "if (TRUE) x else y", linters = spaces_left_parentheses_linter() )
sprintf()
callsCheck for an inconsistent number of arguments or arguments with incompatible types (for literal arguments) in
sprintf()
calls.
sprintf_linter()
sprintf_linter()
gettextf()
calls are also included, since gettextf()
is a thin wrapper around sprintf()
.
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'sprintf("hello %s %s %d", x, y)', linters = sprintf_linter() ) # okay lint( text = 'sprintf("hello %s %s %d", x, y, z)', linters = sprintf_linter() ) lint( text = 'sprintf("hello %s %s %d", x, y, ...)', linters = sprintf_linter() )
# will produce lints lint( text = 'sprintf("hello %s %s %d", x, y)', linters = sprintf_linter() ) # okay lint( text = 'sprintf("hello %s %s %d", x, y, z)', linters = sprintf_linter() ) lint( text = 'sprintf("hello %s %s %d", x, y, ...)', linters = sprintf_linter() )
stopifnot(A)
actually checks all(A)
"under the hood" if A
is a vector,
and produces a better error message than stopifnot(all(A))
does.
stopifnot_all_linter()
stopifnot_all_linter()
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "stopifnot(all(x > 0))", linters = stopifnot_all_linter() ) lint( text = "stopifnot(y > 3, all(x < 0))", linters = stopifnot_all_linter() ) # okay lint( text = "stopifnot(is.null(x) || all(x > 0))", linters = stopifnot_all_linter() ) lint( text = "assert_that(all(x > 0))", linters = stopifnot_all_linter() )
# will produce lints lint( text = "stopifnot(all(x > 0))", linters = stopifnot_all_linter() ) lint( text = "stopifnot(y > 3, all(x < 0))", linters = stopifnot_all_linter() ) # okay lint( text = "stopifnot(is.null(x) || all(x > 0))", linters = stopifnot_all_linter() ) lint( text = "assert_that(all(x > 0))", linters = stopifnot_all_linter() )
startsWith()
and endsWith()
over grepl()
/substr()
versionsstartsWith()
is used to detect fixed initial substrings; it is more
readable and more efficient than equivalents using grepl()
or substr()
.
c.f. startsWith(x, "abc")
, grepl("^abc", x)
,
substr(x, 1L, 3L) == "abc"
.
string_boundary_linter(allow_grepl = FALSE)
string_boundary_linter(allow_grepl = FALSE)
allow_grepl |
Logical, default |
Ditto for using endsWith()
to detect fixed terminal substrings.
Note that there is a difference in behavior between how grepl()
and startsWith()
(and endsWith()
) handle missing values. In particular, for grepl()
, NA
inputs
are considered FALSE
, while for startsWith()
, NA
inputs have NA
outputs.
That means the strict equivalent of grepl("^abc", x)
is
!is.na(x) & startsWith(x, "abc")
.
We lint grepl()
usages by default because the !is.na()
version is more explicit
with respect to NA
handling – though documented, the way grepl()
handles
missing inputs may be surprising to some users.
configurable, efficiency, readability, regex
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'grepl("^a", x)', linters = string_boundary_linter() ) lint( text = 'grepl("z$", x)', linters = string_boundary_linter() ) # okay lint( text = 'startsWith(x, "a")', linters = string_boundary_linter() ) lint( text = 'endsWith(x, "z")', linters = string_boundary_linter() ) # If missing values are present, the suggested alternative wouldn't be strictly # equivalent, so this linter can also be turned off in such cases. lint( text = 'grepl("z$", x)', linters = string_boundary_linter(allow_grepl = TRUE) )
# will produce lints lint( text = 'grepl("^a", x)', linters = string_boundary_linter() ) lint( text = 'grepl("z$", x)', linters = string_boundary_linter() ) # okay lint( text = 'startsWith(x, "a")', linters = string_boundary_linter() ) lint( text = 'endsWith(x, "z")', linters = string_boundary_linter() ) # If missing values are present, the suggested alternative wouldn't be strictly # equivalent, so this linter can also be turned off in such cases. lint( text = 'grepl("z$", x)', linters = string_boundary_linter(allow_grepl = TRUE) )
stringsAsFactors
should be supplied explicitlyDesigned for code bases written for versions of R before 4.0 seeking to upgrade to R >= 4.0, where
one of the biggest pain points will surely be the flipping of the
default value of stringsAsFactors
from TRUE
to FALSE
.
strings_as_factors_linter()
strings_as_factors_linter()
It's not always possible to tell statically whether the change will break
existing code because R is dynamically typed – e.g. in data.frame(x)
if x
is a string, this code will be affected, but if x
is a number,
this code will be unaffected. However, in data.frame(x = "a")
, the
output will unambiguously be affected. We can instead supply
stringsAsFactors = TRUE
, which will make this code backwards-compatible.
See https://developer.r-project.org/Blog/public/2020/02/16/stringsasfactors/.
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'data.frame(x = "a")', linters = strings_as_factors_linter() ) # okay lint( text = 'data.frame(x = "a", stringsAsFactors = TRUE)', linters = strings_as_factors_linter() ) lint( text = 'data.frame(x = "a", stringsAsFactors = FALSE)', linters = strings_as_factors_linter() ) lint( text = "data.frame(x = 1.2)", linters = strings_as_factors_linter() )
# will produce lints lint( text = 'data.frame(x = "a")', linters = strings_as_factors_linter() ) # okay lint( text = 'data.frame(x = "a", stringsAsFactors = TRUE)', linters = strings_as_factors_linter() ) lint( text = 'data.frame(x = "a", stringsAsFactors = FALSE)', linters = strings_as_factors_linter() ) lint( text = "data.frame(x = 1.2)", linters = strings_as_factors_linter() )
Linters highlighting code style issues.
The following linters are tagged with 'style':
linters for a complete list of linters available in lintr.
file.path()
with system.file()
system.file()
has a ...
argument which, internally, is passed to
file.path()
, so including it in user code is repetitive.
system_file_linter()
system_file_linter()
best_practices, consistency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = 'system.file(file.path("path", "to", "data"), package = "foo")', linters = system_file_linter() ) lint( text = 'file.path(system.file(package = "foo"), "path", "to", "data")', linters = system_file_linter() ) # okay lint( text = 'system.file("path", "to", "data", package = "foo")', linters = system_file_linter() )
# will produce lints lint( text = 'system.file(file.path("path", "to", "data"), package = "foo")', linters = system_file_linter() ) lint( text = 'file.path(system.file(package = "foo"), "path", "to", "data")', linters = system_file_linter() ) # okay lint( text = 'system.file("path", "to", "data", package = "foo")', linters = system_file_linter() )
T
and F
symbol linterAlthough they can be synonyms, avoid the symbols T
and F
, and use TRUE
and FALSE
, respectively, instead.
T
and F
are not reserved keywords and can be assigned to any other values.
T_and_F_symbol_linter()
T_and_F_symbol_linter()
best_practices, consistency, default, readability, robustness, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x <- T; y <- F", linters = T_and_F_symbol_linter() ) lint( text = "T = 1.2; F = 2.4", linters = T_and_F_symbol_linter() ) # okay lint( text = "x <- c(TRUE, FALSE)", linters = T_and_F_symbol_linter() ) lint( text = "t = 1.2; f = 2.4", linters = T_and_F_symbol_linter() )
# will produce lints lint( text = "x <- T; y <- F", linters = T_and_F_symbol_linter() ) lint( text = "T = 1.2; F = 2.4", linters = T_and_F_symbol_linter() ) # okay lint( text = "x <- c(TRUE, FALSE)", linters = T_and_F_symbol_linter() ) lint( text = "t = 1.2; f = 2.4", linters = T_and_F_symbol_linter() )
Functions that end in close(x)
are almost always better written by using
on.exit(close(x))
close to where x
is defined and/or opened.
terminal_close_linter()
terminal_close_linter()
linters for a complete list of linters available in lintr.
# will produce lints code <- paste( "f <- function(fl) {", " conn <- file(fl, open = 'r')", " readLines(conn)", " close(conn)", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = terminal_close_linter() ) # okay code <- paste( "f <- function(fl) {", " conn <- file(fl, open = 'r')", " on.exit(close(conn))", " readLines(conn)", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = terminal_close_linter() )
# will produce lints code <- paste( "f <- function(fl) {", " conn <- file(fl, open = 'r')", " readLines(conn)", " close(conn)", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = terminal_close_linter() ) # okay code <- paste( "f <- function(fl) {", " conn <- file(fl, open = 'r')", " on.exit(close(conn))", " readLines(conn)", "}", sep = "\n" ) writeLines(code) lint( text = code, linters = terminal_close_linter() )
Linters based on guidelines described in the 'Tidy design principles' book.
The following linters are tagged with 'tidy_design':
linters for a complete list of linters available in lintr.
Check that the source contains no TODO comments (case-insensitive).
todo_comment_linter(todo = c("todo", "fixme"), except_regex = NULL)
todo_comment_linter(todo = c("todo", "fixme"), except_regex = NULL)
todo |
Vector of case-insensitive strings that identify TODO comments. |
except_regex |
Vector of case-sensitive regular expressions that identify valid TODO comments. |
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x + y # TOODOO", linters = todo_comment_linter(todo = "toodoo") ) lint( text = "pi <- 1.0 # FIIXMEE", linters = todo_comment_linter(todo = "fiixmee") ) lint( text = "x <- TRUE # TOODOO(#1234): Fix this hack.", linters = todo_comment_linter() ) # okay lint( text = "x + y # my informative comment", linters = todo_comment_linter() ) lint( text = "pi <- 3.14", linters = todo_comment_linter() ) lint( text = "x <- TRUE", linters = todo_comment_linter() ) lint( text = "x <- TRUE # TODO(#1234): Fix this hack.", linters = todo_comment_linter(except_regex = "TODO\\(#[0-9]+\\):") )
# will produce lints lint( text = "x + y # TOODOO", linters = todo_comment_linter(todo = "toodoo") ) lint( text = "pi <- 1.0 # FIIXMEE", linters = todo_comment_linter(todo = "fiixmee") ) lint( text = "x <- TRUE # TOODOO(#1234): Fix this hack.", linters = todo_comment_linter() ) # okay lint( text = "x + y # my informative comment", linters = todo_comment_linter() ) lint( text = "pi <- 3.14", linters = todo_comment_linter() ) lint( text = "x <- TRUE", linters = todo_comment_linter() ) lint( text = "x <- TRUE # TODO(#1234): Fix this hack.", linters = todo_comment_linter(except_regex = "TODO\\(#[0-9]+\\):") )
Check that there are no trailing blank lines in source code.
trailing_blank_lines_linter()
trailing_blank_lines_linter()
linters for a complete list of linters available in lintr.
# will produce lints f <- tempfile() cat("x <- 1\n\n", file = f) writeLines(readChar(f, file.size(f))) lint( filename = f, linters = trailing_blank_lines_linter() ) unlink(f) # okay cat("x <- 1\n", file = f) writeLines(readChar(f, file.size(f))) lint( filename = f, linters = trailing_blank_lines_linter() ) unlink(f)
# will produce lints f <- tempfile() cat("x <- 1\n\n", file = f) writeLines(readChar(f, file.size(f))) lint( filename = f, linters = trailing_blank_lines_linter() ) unlink(f) # okay cat("x <- 1\n", file = f) writeLines(readChar(f, file.size(f))) lint( filename = f, linters = trailing_blank_lines_linter() ) unlink(f)
Check that there are no space characters at the end of source lines.
trailing_whitespace_linter(allow_empty_lines = FALSE, allow_in_strings = TRUE)
trailing_whitespace_linter(allow_empty_lines = FALSE, allow_in_strings = TRUE)
allow_empty_lines |
Suppress lints for lines that contain only whitespace. |
allow_in_strings |
Suppress lints for trailing whitespace in string constants. |
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x <- 1.2 ", linters = trailing_whitespace_linter() ) code_lines <- "a <- TRUE\n \nb <- FALSE" writeLines(code_lines) lint( text = code_lines, linters = trailing_whitespace_linter() ) # okay lint( text = "x <- 1.2", linters = trailing_whitespace_linter() ) lint( text = "x <- 1.2 # comment about this assignment", linters = trailing_whitespace_linter() ) code_lines <- "a <- TRUE\n \nb <- FALSE" writeLines(code_lines) lint( text = code_lines, linters = trailing_whitespace_linter(allow_empty_lines = TRUE) )
# will produce lints lint( text = "x <- 1.2 ", linters = trailing_whitespace_linter() ) code_lines <- "a <- TRUE\n \nb <- FALSE" writeLines(code_lines) lint( text = code_lines, linters = trailing_whitespace_linter() ) # okay lint( text = "x <- 1.2", linters = trailing_whitespace_linter() ) lint( text = "x <- 1.2 # comment about this assignment", linters = trailing_whitespace_linter() ) code_lines <- "a <- TRUE\n \nb <- FALSE" writeLines(code_lines) lint( text = code_lines, linters = trailing_whitespace_linter(allow_empty_lines = TRUE) )
Report the use of undesirable functions and suggest an alternative.
undesirable_function_linter( fun = default_undesirable_functions, symbol_is_undesirable = TRUE )
undesirable_function_linter( fun = default_undesirable_functions, symbol_is_undesirable = TRUE )
fun |
Named character vector. |
symbol_is_undesirable |
Whether to consider the use of an undesirable function name as a symbol undesirable or not. |
best_practices, configurable, robustness, style
linters for a complete list of linters available in lintr.
# defaults for which functions are considered undesirable names(default_undesirable_functions) # will produce lints lint( text = "sapply(x, mean)", linters = undesirable_function_linter() ) lint( text = "log10(x)", linters = undesirable_function_linter(fun = c("log10" = NA)) ) lint( text = "log10(x)", linters = undesirable_function_linter(fun = c("log10" = "use log()")) ) lint( text = 'dir <- "path/to/a/directory"', linters = undesirable_function_linter(fun = c("dir" = NA)) ) # okay lint( text = "vapply(x, mean, FUN.VALUE = numeric(1))", linters = undesirable_function_linter() ) lint( text = "log(x, base = 10)", linters = undesirable_function_linter(fun = c("log10" = "use log()")) ) lint( text = 'dir <- "path/to/a/directory"', linters = undesirable_function_linter(fun = c("dir" = NA), symbol_is_undesirable = FALSE) )
# defaults for which functions are considered undesirable names(default_undesirable_functions) # will produce lints lint( text = "sapply(x, mean)", linters = undesirable_function_linter() ) lint( text = "log10(x)", linters = undesirable_function_linter(fun = c("log10" = NA)) ) lint( text = "log10(x)", linters = undesirable_function_linter(fun = c("log10" = "use log()")) ) lint( text = 'dir <- "path/to/a/directory"', linters = undesirable_function_linter(fun = c("dir" = NA)) ) # okay lint( text = "vapply(x, mean, FUN.VALUE = numeric(1))", linters = undesirable_function_linter() ) lint( text = "log(x, base = 10)", linters = undesirable_function_linter(fun = c("log10" = "use log()")) ) lint( text = 'dir <- "path/to/a/directory"', linters = undesirable_function_linter(fun = c("dir" = NA), symbol_is_undesirable = FALSE) )
Report the use of undesirable operators, e.g. :::
or
<<-
and suggest an alternative.
undesirable_operator_linter(op = default_undesirable_operators)
undesirable_operator_linter(op = default_undesirable_operators)
op |
Named character vector. |
best_practices, configurable, robustness, style
linters for a complete list of linters available in lintr.
# defaults for which functions are considered undesirable names(default_undesirable_operators) # will produce lints lint( text = "a <<- log(10)", linters = undesirable_operator_linter() ) lint( text = "mtcars$wt", linters = undesirable_operator_linter(op = c("$" = "As an alternative, use the `[[` accessor.")) ) # okay lint( text = "a <- log(10)", linters = undesirable_operator_linter() ) lint( text = 'mtcars[["wt"]]', linters = undesirable_operator_linter(op = c("$" = NA)) ) lint( text = 'mtcars[["wt"]]', linters = undesirable_operator_linter(op = c("$" = "As an alternative, use the `[[` accessor.")) )
# defaults for which functions are considered undesirable names(default_undesirable_operators) # will produce lints lint( text = "a <<- log(10)", linters = undesirable_operator_linter() ) lint( text = "mtcars$wt", linters = undesirable_operator_linter(op = c("$" = "As an alternative, use the `[[` accessor.")) ) # okay lint( text = "a <- log(10)", linters = undesirable_operator_linter() ) lint( text = 'mtcars[["wt"]]', linters = undesirable_operator_linter(op = c("$" = NA)) ) lint( text = 'mtcars[["wt"]]', linters = undesirable_operator_linter(op = c("$" = "As an alternative, use the `[[` accessor.")) )
Check that the c()
function is not used without arguments nor with a single constant.
unnecessary_concatenation_linter(allow_single_expression = TRUE)
unnecessary_concatenation_linter(allow_single_expression = TRUE)
allow_single_expression |
Logical, default |
configurable, efficiency, readability, style
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x <- c()", linters = unnecessary_concatenation_linter() ) lint( text = "x <- c(TRUE)", linters = unnecessary_concatenation_linter() ) lint( text = "x <- c(1.5 + 2.5)", linters = unnecessary_concatenation_linter(allow_single_expression = FALSE) ) # okay lint( text = "x <- NULL", linters = unnecessary_concatenation_linter() ) # In case the intent here was to seed a vector of known size lint( text = "x <- integer(4L)", linters = unnecessary_concatenation_linter() ) lint( text = "x <- TRUE", linters = unnecessary_concatenation_linter() ) lint( text = "x <- c(1.5 + 2.5)", linters = unnecessary_concatenation_linter(allow_single_expression = TRUE) )
# will produce lints lint( text = "x <- c()", linters = unnecessary_concatenation_linter() ) lint( text = "x <- c(TRUE)", linters = unnecessary_concatenation_linter() ) lint( text = "x <- c(1.5 + 2.5)", linters = unnecessary_concatenation_linter(allow_single_expression = FALSE) ) # okay lint( text = "x <- NULL", linters = unnecessary_concatenation_linter() ) # In case the intent here was to seed a vector of known size lint( text = "x <- integer(4L)", linters = unnecessary_concatenation_linter() ) lint( text = "x <- TRUE", linters = unnecessary_concatenation_linter() ) lint( text = "x <- c(1.5 + 2.5)", linters = unnecessary_concatenation_linter(allow_single_expression = TRUE) )
Using an anonymous function in, e.g., lapply()
is not always necessary,
e.g. lapply(DF, sum)
is the same as lapply(DF, function(x) sum(x))
and
the former is more readable.
unnecessary_lambda_linter(allow_comparison = FALSE)
unnecessary_lambda_linter(allow_comparison = FALSE)
allow_comparison |
Logical, default |
Cases like lapply(x, \(xi) grep("ptn", xi))
are excluded because, though
the anonymous function can be avoided, doing so is not always more
readable.
best_practices, configurable, efficiency, readability
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "lapply(list(1:3, 2:4), function(xi) sum(xi))", linters = unnecessary_lambda_linter() ) lint( text = "sapply(x, function(xi) xi == 2)", linters = unnecessary_lambda_linter() ) lint( text = "sapply(x, function(xi) sum(xi) > 0)", linters = unnecessary_lambda_linter() ) # okay lint( text = "lapply(list(1:3, 2:4), sum)", linters = unnecessary_lambda_linter() ) lint( text = 'lapply(x, function(xi) grep("ptn", xi))', linters = unnecessary_lambda_linter() ) lint( text = "lapply(x, function(xi) data.frame(col = xi))", linters = unnecessary_lambda_linter() ) lint( text = "sapply(x, function(xi) xi == 2)", linters = unnecessary_lambda_linter(allow_comparison = TRUE) ) lint( text = "sapply(x, function(xi) sum(xi) > 0)", linters = unnecessary_lambda_linter(allow_comparison = TRUE) ) lint( text = "sapply(x, function(xi) sum(abs(xi)) > 10)", linters = unnecessary_lambda_linter() ) lint( text = "sapply(x, sum) > 0", linters = unnecessary_lambda_linter() )
# will produce lints lint( text = "lapply(list(1:3, 2:4), function(xi) sum(xi))", linters = unnecessary_lambda_linter() ) lint( text = "sapply(x, function(xi) xi == 2)", linters = unnecessary_lambda_linter() ) lint( text = "sapply(x, function(xi) sum(xi) > 0)", linters = unnecessary_lambda_linter() ) # okay lint( text = "lapply(list(1:3, 2:4), sum)", linters = unnecessary_lambda_linter() ) lint( text = 'lapply(x, function(xi) grep("ptn", xi))', linters = unnecessary_lambda_linter() ) lint( text = "lapply(x, function(xi) data.frame(col = xi))", linters = unnecessary_lambda_linter() ) lint( text = "sapply(x, function(xi) xi == 2)", linters = unnecessary_lambda_linter(allow_comparison = TRUE) ) lint( text = "sapply(x, function(xi) sum(xi) > 0)", linters = unnecessary_lambda_linter(allow_comparison = TRUE) ) lint( text = "sapply(x, function(xi) sum(abs(xi)) > 10)", linters = unnecessary_lambda_linter() ) lint( text = "sapply(x, sum) > 0", linters = unnecessary_lambda_linter() )
Excessive nesting harms readability. Use helper functions or early returns to reduce nesting wherever possible.
unnecessary_nesting_linter( allow_assignment = TRUE, allow_functions = c("switch", "try", "tryCatch", "withCallingHandlers", "quote", "expression", "bquote", "substitute", "with_parameters_test_that", "reactive", "observe", "observeEvent", "renderCachedPlot", "renderDataTable", "renderImage", "renderPlot", "renderPrint", "renderTable", "renderText", "renderUI") )
unnecessary_nesting_linter( allow_assignment = TRUE, allow_functions = c("switch", "try", "tryCatch", "withCallingHandlers", "quote", "expression", "bquote", "substitute", "with_parameters_test_that", "reactive", "observe", "observeEvent", "renderCachedPlot", "renderDataTable", "renderImage", "renderPlot", "renderPrint", "renderTable", "renderText", "renderUI") )
allow_assignment |
Logical, default |
allow_functions |
Character vector of functions which always allow
one-child braced expressions. |
best_practices, configurable, consistency, readability
cyclocomp_linter()
for another linter that penalizes overly complex code.
linters for a complete list of linters available in lintr.
# will produce lints code <- "if (A) {\n stop('A is bad!')\n} else {\n do_good()\n}" writeLines(code) lint( text = code, linters = unnecessary_nesting_linter() ) code <- "tryCatch(\n {\n foo()\n },\n error = identity\n)" writeLines(code) lint( text = code, linters = unnecessary_nesting_linter() ) code <- "expect_warning(\n {\n x <- foo()\n },\n 'warned'\n)" writeLines(code) lint( text = code, linters = unnecessary_nesting_linter(allow_assignment = FALSE) ) writeLines("if (x) { \n if (y) { \n return(1L) \n } \n}") lint( text = "if (x) { \n if (y) { \n return(1L) \n } \n}", linters = unnecessary_nesting_linter() ) lint( text = "my_quote({x})", linters = unnecessary_nesting_linter() ) # okay code <- "if (A) {\n stop('A is bad because a.')\n} else {\n stop('!A is bad too.')\n}" writeLines(code) lint( text = code, linters = unnecessary_nesting_linter() ) code <- "capture.output({\n foo()\n})" writeLines(code) lint( text = code, linters = unnecessary_nesting_linter() ) code <- "expect_warning(\n {\n x <- foo()\n },\n 'warned'\n)" writeLines(code) lint( text = code, linters = unnecessary_nesting_linter() ) writeLines("if (x && y) { \n return(1L) \n}") lint( text = "if (x && y) { \n return(1L) \n}", linters = unnecessary_nesting_linter() ) writeLines("if (x) { \n y <- x + 1L\n if (y) { \n return(1L) \n } \n}") lint( text = "if (x) { \n y <- x + 1L\n if (y) { \n return(1L) \n } \n}", linters = unnecessary_nesting_linter() ) lint( text = "my_quote({x})", linters = unnecessary_nesting_linter(allow_functions = "my_quote") )
# will produce lints code <- "if (A) {\n stop('A is bad!')\n} else {\n do_good()\n}" writeLines(code) lint( text = code, linters = unnecessary_nesting_linter() ) code <- "tryCatch(\n {\n foo()\n },\n error = identity\n)" writeLines(code) lint( text = code, linters = unnecessary_nesting_linter() ) code <- "expect_warning(\n {\n x <- foo()\n },\n 'warned'\n)" writeLines(code) lint( text = code, linters = unnecessary_nesting_linter(allow_assignment = FALSE) ) writeLines("if (x) { \n if (y) { \n return(1L) \n } \n}") lint( text = "if (x) { \n if (y) { \n return(1L) \n } \n}", linters = unnecessary_nesting_linter() ) lint( text = "my_quote({x})", linters = unnecessary_nesting_linter() ) # okay code <- "if (A) {\n stop('A is bad because a.')\n} else {\n stop('!A is bad too.')\n}" writeLines(code) lint( text = code, linters = unnecessary_nesting_linter() ) code <- "capture.output({\n foo()\n})" writeLines(code) lint( text = code, linters = unnecessary_nesting_linter() ) code <- "expect_warning(\n {\n x <- foo()\n },\n 'warned'\n)" writeLines(code) lint( text = code, linters = unnecessary_nesting_linter() ) writeLines("if (x && y) { \n return(1L) \n}") lint( text = "if (x && y) { \n return(1L) \n}", linters = unnecessary_nesting_linter() ) writeLines("if (x) { \n y <- x + 1L\n if (y) { \n return(1L) \n } \n}") lint( text = "if (x) { \n y <- x + 1L\n if (y) { \n return(1L) \n } \n}", linters = unnecessary_nesting_linter() ) lint( text = "my_quote({x})", linters = unnecessary_nesting_linter(allow_functions = "my_quote") )
The argument placeholder .
in magrittr pipelines is unnecessary if
passed as the first positional argument; using it can cause confusion
and impacts readability.
unnecessary_placeholder_linter()
unnecessary_placeholder_linter()
This is true for forward (%>%
), assignment (%<>%
), and tee (%T>%
) operators.
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "x %>% sum(., na.rm = TRUE)", linters = unnecessary_placeholder_linter() ) # okay lint( text = "x %>% sum(na.rm = TRUE)", linters = unnecessary_placeholder_linter() ) lint( text = "x %>% lm(data = ., y ~ z)", linters = unnecessary_placeholder_linter() ) lint( text = "x %>% outer(., .)", linters = unnecessary_placeholder_linter() )
# will produce lints lint( text = "x %>% sum(., na.rm = TRUE)", linters = unnecessary_placeholder_linter() ) # okay lint( text = "x %>% sum(na.rm = TRUE)", linters = unnecessary_placeholder_linter() ) lint( text = "x %>% lm(data = ., y ~ z)", linters = unnecessary_placeholder_linter() ) lint( text = "x %>% outer(., .)", linters = unnecessary_placeholder_linter() )
Code after e.g. a return()
or stop()
or in deterministically false conditional loops like if (FALSE)
can't be reached;
typically this is vestigial code left after refactoring or sandboxing code, which
is fine for exploration, but shouldn't ultimately be checked in. Comments
meant for posterity should be placed before the final return()
.
unreachable_code_linter( allow_comment_regex = getOption("covr.exclude_end", "# nocov end") )
unreachable_code_linter( allow_comment_regex = getOption("covr.exclude_end", "# nocov end") )
allow_comment_regex |
Character vector of regular expressions which identify
comments to exclude when finding unreachable terminal comments. By default, this
includes the default "skip region" end marker for |
best_practices, configurable, readability
linters for a complete list of linters available in lintr.
# will produce lints code_lines <- "f <- function() {\n return(1 + 1)\n 2 + 2\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) code_lines <- "if (FALSE) {\n 2 + 2\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) code_lines <- "while (FALSE) {\n 2 + 2\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) code_lines <- "f <- function() {\n return(1)\n # end skip\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) # okay code_lines <- "f <- function() {\n return(1 + 1)\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) code_lines <- "if (foo) {\n 2 + 2\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) code_lines <- "while (foo) {\n 2 + 2\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) code_lines <- "f <- function() {\n return(1)\n # end skip\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter(allow_comment_regex = "# end skip") )
# will produce lints code_lines <- "f <- function() {\n return(1 + 1)\n 2 + 2\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) code_lines <- "if (FALSE) {\n 2 + 2\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) code_lines <- "while (FALSE) {\n 2 + 2\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) code_lines <- "f <- function() {\n return(1)\n # end skip\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) # okay code_lines <- "f <- function() {\n return(1 + 1)\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) code_lines <- "if (foo) {\n 2 + 2\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) code_lines <- "while (foo) {\n 2 + 2\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter() ) code_lines <- "f <- function() {\n return(1)\n # end skip\n}" writeLines(code_lines) lint( text = code_lines, linters = unreachable_code_linter(allow_comment_regex = "# end skip") )
Check that imported packages are actually used
unused_import_linter( allow_ns_usage = FALSE, except_packages = c("bit64", "data.table", "tidyverse"), interpret_glue = TRUE )
unused_import_linter( allow_ns_usage = FALSE, except_packages = c("bit64", "data.table", "tidyverse"), interpret_glue = TRUE )
allow_ns_usage |
Suppress lints for packages only used via namespace.
This is |
except_packages |
Character vector of packages that are ignored. These are usually attached for their side effects. |
interpret_glue |
If |
best_practices, common_mistakes, configurable, executing
linters for a complete list of linters available in lintr.
# will produce lints code_lines <- "library(dplyr)\n1 + 1" writeLines(code_lines) lint( text = code_lines, linters = unused_import_linter() ) code_lines <- "library(dplyr)\ndplyr::tibble(a = 1)" writeLines(code_lines) lint( text = code_lines, linters = unused_import_linter() ) # okay code_lines <- "library(dplyr)\ntibble(a = 1)" writeLines(code_lines) lint( text = code_lines, linters = unused_import_linter() ) code_lines <- "library(dplyr)\ndplyr::tibble(a = 1)" writeLines(code_lines) lint( text = code_lines, linters = unused_import_linter(allow_ns_usage = TRUE) )
# will produce lints code_lines <- "library(dplyr)\n1 + 1" writeLines(code_lines) lint( text = code_lines, linters = unused_import_linter() ) code_lines <- "library(dplyr)\ndplyr::tibble(a = 1)" writeLines(code_lines) lint( text = code_lines, linters = unused_import_linter() ) # okay code_lines <- "library(dplyr)\ntibble(a = 1)" writeLines(code_lines) lint( text = code_lines, linters = unused_import_linter() ) code_lines <- "library(dplyr)\ndplyr::tibble(a = 1)" writeLines(code_lines) lint( text = code_lines, linters = unused_import_linter(allow_ns_usage = TRUE) )
Create a minimal lintr config file as a starting point for customization
use_lintr(path = ".", type = c("tidyverse", "full"))
use_lintr(path = ".", type = c("tidyverse", "full"))
path |
Path to project root, where a |
type |
What kind of configuration to create?
|
Path to the generated configuration, invisibly.
vignette("lintr")
for detailed introduction to using and configuring lintr.
if (FALSE) { # use the default set of linters lintr::use_lintr() # or try all linters lintr::use_lintr(type = "full") # then lintr::lint_dir() }
if (FALSE) { # use the default set of linters lintr::use_lintr() # or try all linters lintr::use_lintr(type = "full") # then lintr::lint_dir() }
Usage of &
in conditional statements is error-prone and inefficient.
condition
in if (condition) expr
must always be of length 1, in which
case &&
is to be preferred. Ditto for |
vs. ||
.
vector_logic_linter()
vector_logic_linter()
This linter covers inputs to if()
and while()
conditions and to
testthat::expect_true()
and testthat::expect_false()
.
Note that because &
and |
are generics, it is possible that
&&
/ ||
are not perfect substitutes because &
is doing
method dispatch in an incompatible way.
Moreover, be wary of code that may have side effects, most commonly
assignments. Consider if ((a <- foo(x)) | (b <- bar(y))) { ... }
vs. if ((a <- foo(x)) || (b <- bar(y))) { ... }
. Because ||
exits
early, if a
is TRUE
, the second condition will never be evaluated
and b
will not be assigned. Such usage is not allowed by the Tidyverse
style guide, and the code can easily be refactored by pulling the
assignment outside the condition, so using ||
is still preferable.
best_practices, common_mistakes, default, efficiency
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "if (TRUE & FALSE) 1", linters = vector_logic_linter() ) lint( text = "if (TRUE && (TRUE | FALSE)) 4", linters = vector_logic_linter() ) lint( text = "filter(x, A && B)", linters = vector_logic_linter() ) # okay lint( text = "if (TRUE && FALSE) 1", linters = vector_logic_linter() ) lint( text = "if (TRUE && (TRUE || FALSE)) 4", linters = vector_logic_linter() ) lint( text = "filter(x, A & B)", linters = vector_logic_linter() )
# will produce lints lint( text = "if (TRUE & FALSE) 1", linters = vector_logic_linter() ) lint( text = "if (TRUE && (TRUE | FALSE)) 4", linters = vector_logic_linter() ) lint( text = "filter(x, A && B)", linters = vector_logic_linter() ) # okay lint( text = "if (TRUE && FALSE) 1", linters = vector_logic_linter() ) lint( text = "if (TRUE && (TRUE || FALSE)) 4", linters = vector_logic_linter() ) lint( text = "filter(x, A & B)", linters = vector_logic_linter() )
which(grepl(pattern, x))
is the same as grep(pattern, x)
, but harder
to read and requires two passes over the vector.
which_grepl_linter()
which_grepl_linter()
consistency, efficiency, readability, regex
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "which(grepl('^a', x))", linters = which_grepl_linter() ) # okay lint( text = "which(grepl('^a', x) | grepl('^b', x))", linters = which_grepl_linter() )
# will produce lints lint( text = "which(grepl('^a', x))", linters = which_grepl_linter() ) # okay lint( text = "which(grepl('^a', x) | grepl('^b', x))", linters = which_grepl_linter() )
Check that the correct character is used for indentation.
whitespace_linter()
whitespace_linter()
Currently, only supports linting in the presence of tabs.
Much ink has been spilled on this topic, and we encourage you to check out references for more information.
https://www.jwz.org/doc/tabs-vs-spaces.html
https://blog.codinghorror.com/death-to-the-space-infidels/
linters for a complete list of linters available in lintr.
# will produce lints lint( text = "\tx", linters = whitespace_linter() ) # okay lint( text = " x", linters = whitespace_linter() )
# will produce lints lint( text = "\tx", linters = whitespace_linter() ) # okay lint( text = " x", linters = whitespace_linter() )
Convenience function for converting nodes matched by XPath-based
linter logic into a Lint()
object to return.
xml_nodes_to_lints( xml, source_expression, lint_message, type = c("style", "warning", "error"), column_number_xpath = range_start_xpath, range_start_xpath = "number(./@col1)", range_end_xpath = "number(./@col2)" )
xml_nodes_to_lints( xml, source_expression, lint_message, type = c("style", "warning", "error"), column_number_xpath = range_start_xpath, range_start_xpath = "number(./@col1)", range_end_xpath = "number(./@col2)" )
xml |
An |
source_expression |
A source expression object, e.g. as
returned typically by |
lint_message |
The message to be included as the |
type |
type of lint. |
column_number_xpath |
XPath expression to return the column number location of the lint.
Defaults to the start of the range matched by |
range_start_xpath |
XPath expression to return the range start location of the lint.
Defaults to the start of the expression matched by |
range_end_xpath |
XPath expression to return the range end location of the lint.
Defaults to the end of the expression matched by |
The location XPaths, column_number_xpath
, range_start_xpath
and range_end_xpath
are evaluated using
xml2::xml_find_num()
and will usually be of the form "number(./relative/xpath)"
.
Note that the location line number cannot be changed and lints spanning multiple lines will ignore range_end_xpath
.
column_number_xpath
and range_start_xpath
are assumed to always refer to locations on the starting line of the
xml
node.
For xml_node
s, a lint
. For xml_nodeset
s, lints
(a list of lint
s).
Often, it is more helpful to tailor the message
of a lint to record
which function was matched by the lint logic. This function encapsulates
the logic to pull out the matched call in common situations.
xp_call_name(expr, depth = 1L, condition = NULL)
xp_call_name(expr, depth = 1L, condition = NULL)
expr |
An |
depth |
Integer, default |
condition |
An additional (XPath condition on the |
xml_from_code <- function(str) { xml2::read_xml(xmlparsedata::xml_parse_data(parse(text = str, keep.source = TRUE))) } xml <- xml_from_code("sum(1:10)") xp_call_name(xml, depth = 2L) xp_call_name(xml2::xml_find_first(xml, "expr")) xml <- xml_from_code(c("sum(1:10)", "sd(1:10)")) xp_call_name(xml, depth = 2L, condition = "text() = 'sum'")
xml_from_code <- function(str) { xml2::read_xml(xmlparsedata::xml_parse_data(parse(text = str, keep.source = TRUE))) } xml <- xml_from_code("sum(1:10)") xp_call_name(xml, depth = 2L) xp_call_name(xml2::xml_find_first(xml, "expr")) xml <- xml_from_code(c("sum(1:10)", "sd(1:10)")) xp_call_name(xml, depth = 2L, condition = "text() = 'sum'")
Yoda tests use (expected, actual)
instead of the more common (actual, expected)
.
This is not always possible to detect statically; this linter focuses on
the simple case of testing an expression against a literal value, e.g.
(1L, foo(x))
should be (foo(x), 1L)
.
yoda_test_linter()
yoda_test_linter()
best_practices, package_development, pkg_testthat, readability
linters for a complete list of linters available in lintr. https://en.wikipedia.org/wiki/Yoda_conditions
# will produce lints lint( text = "expect_equal(2, x)", linters = yoda_test_linter() ) lint( text = 'expect_identical("a", x)', linters = yoda_test_linter() ) # okay lint( text = "expect_equal(x, 2)", linters = yoda_test_linter() ) lint( text = 'expect_identical(x, "a")', linters = yoda_test_linter() )
# will produce lints lint( text = "expect_equal(2, x)", linters = yoda_test_linter() ) lint( text = 'expect_identical("a", x)', linters = yoda_test_linter() ) # okay lint( text = "expect_equal(x, 2)", linters = yoda_test_linter() ) lint( text = 'expect_identical(x, "a")', linters = yoda_test_linter() )