Changelog
Source:NEWS.md
lintr (development version)
Deprecations & breaking changes
- Various things marked deprecated since {lintr} 3.0.0 have been fully deprecated. They will be completely removed in the subsequent release.
-
source_file=
argument toids_with_token()
andwith_id()
. - Passing linters by name or as non-
"linter"
-classed functions. -
linter=
argument ofLint()
. -
with_defaults()
. - Linters
closed_curly_linter()
,open_curly_linter()
,paren_brace_linter()
, andsemicolon_terminator_linter()
. - Helper
with_defaults()
.
-
-
all_linters()
has signatureall_linters(..., packages)
rather thanall_linters(packages, ...)
(#2332, @MichaelChirico). This forcespackages=
to be supplied by name and will break users who rely on supplyingpackages=
positionally, of which we found none searching GitHub. - Adjusted various lint messages for consistency in readability (#1330, @MichaelChirico). In general, we favor lint messages to be phrased like “Action, reason” to but the “what” piece of the message front-and-center. This may be a breaking change for code that tests the specific phrasing of lints.
-
extraction_operator_linter()
is deprecated. Although switching from$
to[[
has some robustness benefits for package code, it can lead to non-idiomatic code in many contexts (e.g. R6 classes, Shiny applications, etc.) (#2409, @IndrajeetPatil). To enable the detection of the$
operator for extraction through partial matching, useoptions(warnPartialMatchDollar = TRUE)
. -
unnecessary_nested_if_linter()
is deprecated and subsumed into the new/more generalunnecessary_nesting_linter()
. - Drop support for posting GitHub comments from inside GitHub comment bot, Travis, Wercker, and Jenkins CI tools (spurred by #2148, @MichaelChirico). We rely on GitHub Actions for linting in CI, and don’t see any active users relying on these alternatives. We welcome and encourage community contributions to get support for different CI system going again.
-
cyclocomp_linter()
is no longer part of the default linters (#2555, @IndrajeetPatil) because the tidyverse style guide doesn’t contain any guidelines on meeting certain complexity requirements. Note that users withcyclocomp_linter()
in their configs may now need to install {cyclocomp} intentionally, in particular in CI/CD pipelines. -
scalar_in_linter
is now configurable to allow other%in%
like operators to be linted. The data.table operator%chin%
is no longer linted by default; usein_operators = "%chin%"
to continue linting it. (@F-Noelle) -
lint()
and friends now normalize paths to forward slashes on Windows (@olivroy, #2613). -
undesirable_function_linter()
,undesirable_operator_linter()
, andlist_comparison_linter()
were removed from the tagefficiency
(@IndrajeetPatil, #2655). If you uselinters_with_tags("efficiency")
to include these linters, you’ll need to adjust your config to keep linting your code against them. We did not find any such users on GitHub.
Bug fixes
-
expect_identical_linter()
also skipsexpect_equal()
comparison to negative non-integers like-1.034
(#2411, @Bisaloo). This is a parity fix since positive reals have always been skipped because “high-precision” comparisons are typically done to get tests withintolerance
, soexpect_identical()
is not a great substitution. -
object_name_linter()
no longer errors when user-suppliedregexes=
have capture groups (#2188, @MichaelChirico). -
.lintr
config validation correctly accepts regular expressions which only compile underperl = TRUE
(#2375, @MichaelChirico). These have always been valid (sincerex::re_matches()
, which powers the lint exclusion logic, also uses this setting), but the new up-front validation in v3.1.1 incorrectly usedperl = FALSE
. -
.lintr
configs set by optionlintr.linter_file
or environment variableR_LINTR_LINTER_FILE
can point to subdirectories (#2512, @MichaelChirico). -
indentation_linter()
returnsranges[1L]==1L
when the offending line has 0 spaces (#2550, @MichaelChirico). -
literal_coercion_linter()
doesn’t surface a warning about NAs during coercion for code likeas.integer("a")
(#2566, @MichaelChirico). -
commented_code_linter()
can detect commented code that ends with a pipe (#2671, @jcken95)
Changes to default linters
- New default linter
return_linter()
for the style guide rule that terminal returns should be left implicit (#1100, #2343, #2354, and #2356, @MEO265 and @MichaelChirico).
New and improved features
- More helpful errors for invalid configs (#2253, @MichaelChirico).
-
library_call_linter()
is extended- to encourage all packages to be attached with
library(symbol)
, notlibrary("symbol", character.only = TRUE)
or “vectorized” approaches looping over package names (part of #884, @MichaelChirico). - to discourage many consecutive calls to
suppressMessages()
orsuppressPackageStartupMessages()
(part of #884, @MichaelChirico).
- to encourage all packages to be attached with
-
return_linter()
also has arguments for fine-tuning which functions get linted:-
return_style
("implicit"
by default) which checks that all functions confirm to the specified return style of"implicit"
or"explicit"
(#2271 and part of #884, @MichaelChirico, @AshesITR and @MEO265). -
allow_implicit_else
(defaultTRUE
) which, whenFALSE
, checks that all terminalif
statements are paired with a correspondingelse
statement (part of #884, @MichaelChirico). -
return_functions
to customize which functions are equivalent toreturn()
as “exit” clauses, e.g.rlang::abort()
can be considered in addition to the default functions likestop()
andq()
from base (#2271 and part of #884, @MichaelChirico and @MEO265). -
except
to customize which functions are ignored entirely (i.e., whether they have a return of the specified style is not checked; #2271 and part of #884, @MichaelChirico and @MEO265). Namespace hooks like.onAttach()
and.onLoad()
are always ignored. -
except_regex
, the same purpose asexcept=
, but filters functions by pattern. This is motivated by {RUnit}, where test suites are based on unit test functions matched by pattern, e.g.^Test
, and where explicit return may be awkward (#2335, @MichaelChirico).
-
-
unnecessary_lambda_linter
is extended to encourage vectorized comparisons where possible, e.g.sapply(x, sum) > 0
instead ofsapply(x, function(x) sum(x) > 0)
(part of #884, @MichaelChirico). Toggle this behavior with argumentallow_comparison
. -
backport_linter()
is slightly faster by moving expensive computations outside the linting function (#2339, #2348, @AshesITR and @MichaelChirico). -
Linter()
has a new argumentlinter_level
(defaultNA
). This is used bylint()
to more efficiently check for expression levels than the idiomif (!is_lint_level(...)) { return(list()) }
(#2351, @AshesITR). -
string_boundary_linter()
recognizes regular expression calls likegrepl("^abc$", x)
that can be replaced by using==
instead (#1613, @MichaelChirico). -
unreachable_code_linter()
has an argumentallow_comment_regex
for customizing which “terminal” comments to exclude (#2327, @MichaelChirico).# nolint end
comments are always excluded, as are {covr} exclusions (e.g.# nocov end
) by default. -
format()
andprint()
methods forlint
andlints
classes get a new optionwidth
to control the printing width of lint messages (#1884, @MichaelChirico). The default is controlled by a new optionlintr.format_width
; if unset, no wrapping occurs (matching earlier behavior). -
implicit_assignment_linter()
gets a custom message for the case of using(
to induce printing like(x <- foo())
; use an explicit call toprint()
for clarity (#2257, @MichaelChirico). - New function node caching for big efficiency gains to most linters (e.g. overall
lint_package()
improvement of 14-27% and core linting improvement up to 30%; #2357, @AshesITR). Most linters are written around function usage, and XPath performance searching for many functions is poor. The newxml_find_function_calls()
entry in theget_source_expressions()
output caches all function call nodes instead. See the vignette on creating linters for more details on how to use it. -
todo_comment_linter()
has a new argumentexcept_regex
for setting valid TODO comments, e.g. for forcing TODO comments to be linked to GitHub issues likeTODO(#154)
(#2047, @MichaelChirico). -
vector_logic_linter()
is extended to recognize incorrect usage of scalar operators&&
and||
inside subsetting expressions likedplyr::filter(x, A && B)
(#2166, @MichaelChirico). -
any_is_na_linter()
is extended to catch the unusual usageNA %in% x
(#2113, @MichaelChirico). -
make_linter_from_xpath()
errors up front whenlint_message
is missing (instead of delaying this error until the linter is used, #2541, @MichaelChirico). -
paste_linter()
is extended to recommend usingpaste()
instead ofpaste0()
for simply aggregating a character vector withcollapse=
, i.e., whensep=
is irrelevant (#1108, @MichaelChirico). -
expect_no_lint()
was added as new function to cover the typical use case of expecting no lint message, akin to the recent {testthat} functions likeexpect_no_warning()
(#2580, @F-Noelle). -
lint()
and friends emit a message if no lints are found (#2643, @IndrajeetPatil). - lintr now has a hex sticker (https://github.com/rstudio/hex-stickers/pull/110). Thank you, @gregswinehart!
New linters
-
condition_call_linter()
for ensuring consistent use ofcall.
inwarning()
andstop()
. The defaultcall. = FALSE
follows the tidyverse guidance of not displaying the call (#2226, @Bisaloo) -
sample_int_linter()
for encouragingsample.int(n, ...)
over equivalents likesample(1:n, ...)
(part of #884, @MichaelChirico). -
stopifnot_all_linter()
discourages tests withall()
likestopifnot(all(x > 0))
;stopifnot()
runsall()
itself, and uses a better error message (part of #884, @MichaelChirico). -
comparison_negation_linter()
for discouraging negated comparisons when a direct negation is preferable, e.g.!(x == y)
could bex != y
(part of #884, @MichaelChirico). -
nzchar_linter()
for encouragingnzchar()
to test for empty strings, e.g.nchar(x) > 0
can benzchar(x)
(part of #884, @MichaelChirico). -
terminal_close_linter()
for discouraging usingclose()
to end functions (part of #884, @MichaelChirico). Such usages are not robust to errors, whereclose()
will not be run as intended. Putclose()
in anon.exit()
hook, or use {withr} to manage connections with proper cleanup. -
rep_len_linter()
for encouraging use ofrep_len()
directly instead ofrep(x, length.out = n)
(part of #884, @MichaelChirico). -
which_grepl_linter()
for discouragingwhich(grepl(ptn, x))
in favor of directly usinggrep(ptn, x)
(part of #884, @MichaelChirico). -
list_comparison_linter()
for discouraging comparisons on the output oflapply()
, e.g.lapply(x, sum) > 10
(part of #884, @MichaelChirico). -
print_linter()
for discouraging usage ofprint()
on string literals likeprint("Reached here")
orprint(paste("Found", nrow(DF), "rows."))
(#1894, @MichaelChirico). -
unnecessary_nesting_linter()
for discouraging overly-nested code where an early return or eliminated sub-expression (inside ‘{’) is preferable (#2317, #2334 and part of #884, @MichaelChirico). -
consecutive_mutate_linter()
for encouraging consecutive calls todplyr::mutate()
to be combined (part of #884, @MichaelChirico). -
if_switch_linter()
for encouragingswitch()
over repeatedif
/else
tests (#2322 and part of #884, @MichaelChirico). -
nested_pipe_linter()
for discouraging pipes within pipes, e.g.df1 %>% inner_join(df2 %>% select(a, b))
(part of #884, @MichaelChirico). -
nrow_subset_linter()
for discouraging usage likenrow(subset(x, conditions))
in favor of something likewith(x, sum(conditions))
which doesn’t require a full subset ofx
(#2313, #2314 and part of #884, @MichaelChirico). -
pipe_return_linter()
for discouraging usage ofreturn()
inside a {magrittr} pipeline (part of #884, @MichaelChirico). -
one_call_pipe_linter()
for discouraging one-step pipelines likex |> as.character()
(#2330 and part of #884, @MichaelChirico). -
object_overwrite_linter()
for discouraging re-use of upstream package exports as local variables (#2344, #2346 and part of #884, @MichaelChirico and @AshesITR).
Lint accuracy fixes: removing false positives
-
object_name_linter()
andobject_length_linter()
ignore {rlang} name injection likex |> mutate("{new_name}" := foo(col))
(#1926, @MichaelChirico). No checking is applied in such cases. {data.table} in-place assignments likeDT[, "sPoNGeBob" := "friend"]
are still eligible for lints. -
object_usage_linter()
finds global variables assigned with=
or->
, which avoids some issues around “undefined global variables” in scripts (#2654, @MichaelChirico).
Notes
- All user-facing messages (including progress bars) are now prepared using the cli package (#2418 and #2641, @IndrajeetPatil). All messages have been reviewed and updated to be more informative and consistent.
- File locations in lints and error messages contain clickable hyperlinks to improve code navigation (#2645, #2588, @olivroy).
- {lintr} now depends on R version 4.0.0. It already does so implicitly due to recursive upstream dependencies requiring this version; we’ve simply made that dependency explicit and up-front (#2569, @MichaelChirico).
- Some code with parameters accepting regular expressions is less strict about whether there are capture groups (#2678, @MichaelChirico). In particular, this affects
unreachable_code_linter(allow_comment_regex=)
andexpect_lint(checks=)
.
lintr 3.1.2
CRAN release: 2024-03-25
New and improved features
Lint accuracy fixes: removing false positives
-
unreachable_code_linter()
ignores reachable code in inline functions likefunction(x) if (x > 2) stop() else x
(#2259, @MEO265). -
unnecessary_lambda_linter()
- ignores extractions with explicit returns like
lapply(l, function(x) foo(x)$bar)
(#2258, @MichaelChirico). - ignores calls on the RHS of operators like
lapply(l, function(x) "a" %in% names(x))
(#2310, @MichaelChirico).
- ignores extractions with explicit returns like
-
vector_logic_linter()
recognizes some cases where bitwise&
/|
are used correctly (#1453, @MichaelChirico). -
expect_comparison_linter()
ignores faulty usage likeexpect_true(x, y > z)
(#2083, @MichaelChirico). Note thaty > z
is being passed to theinfo=
argument, so this is likely a mistake. -
consecutive_assertion_linter()
ignores cases where a second assertion follows an intervening assignment with=
(#2444, @MichaelChirico).
Lint accuracy fixes: removing false negatives
-
missing_argument_linter()
catches all missing arguments in calls with several, e.g.foo(,,)
gives 3 lints instead of 2 (#2399, @MichaelChirico). -
duplicate_argument_linter()
no longer misses cases with duplicate arguments where a comment comes between the argument name and=
(#2402, @MichaelChirico).
Notes
- Fixed a test assuming a specific parser error message that recently changed in r-devel (#2527, @IndrajeetPatil).
- @MichaelChirico has taken over CRAN maintainer duties for the package. Many thanks to @jimhester for more than 10 years and 15 releases wearing that hat!!
lintr 3.1.1
CRAN release: 2023-11-07
Breaking changes
-
infix_spaces_linter()
distinguishes<-
,:=
,<<-
and->
,->>
, i.e.infix_spaces_linter(exclude_operators = "->")
will no longer exclude->>
(#2115, @MichaelChirico). This change is breaking for users relying on manually-suppliedexclude_operators
containing"<-"
to also exclude:=
and<<-
. The fix is to manually supply":="
and"<<-"
as well. We don’t expect this change to affect many users, the fix is simple, and the new behavior is much more transparent, so we are including this breakage in a minor release. - Removed
find_line()
andfind_column()
entries fromget_source_expressions()
expression-level objects. These have been marked deprecated since version 3.0.0. No users were found on GitHub. - There is experimental support for writing config in plain R scripts (as opposed to DCF files; #1210, @MichaelChirico). The script is run in a new environment and variables matching settings (
?default_settings
) are copied over. In particular, this removes the need to write R code in a DCF-friendly way, and allows normal R syntax highlighting in the saved file. We may eventually deprecate the DCF approach in favor of this one; user feedback is welcome on strong preferences for either approach, or for a different approach like YAML. Generally you should be able to convert your existing.lintr
file to an equivalent R config by replacing the:
key-value separators with assignments (<-
). By default, such a config is searched for in a file named.lintr.R
. This is a mildly breaking change if you happened to be keeping a file.lintr.R
around since that file is given precedence over.lintr
.- We also validate config files up-front make it clearer when invalid configs are present (#2195, @MichaelChirico). There is a warning for “invalid” settings, i.e., settings not part of
?default_settings
. We think this is more likely to affect users declaring settings in R, since any variable defined in the config that’s not a setting must be removed to make it clearer which variables are settings vs. ancillary.
- We also validate config files up-front make it clearer when invalid configs are present (#2195, @MichaelChirico). There is a warning for “invalid” settings, i.e., settings not part of
Bug fixes
-
sprintf_linter()
doesn’t error in cases where whitespace in...
arguments is significant, e.g.sprintf("%s", if (A) "" else y)
, which won’t parse if whitespace is removed (#2131, @MichaelChirico).
Changes to default linters
-
assignment_linter()
lints the {magrittr} assignment pipe%<>%
(#2008, @MichaelChirico). This can be deactivated by setting the new argumentallow_pipe_assign
toTRUE
. -
object_usage_linter()
:- assumes
glue()
isglue::glue()
wheninterpret_glue=TRUE
(#2032, @MichaelChirico). - finds function usages, including infix usage, inside
glue()
calls to avoid false positives for “unused objects” (#2029 and #2069, @MichaelChirico).
- assumes
-
object_name_linter()
no longer attempts to lint strings in function calls on the LHS of assignments (#1466, @MichaelChirico). -
infix_spaces_linter()
allows finer control for linting=
in different scenarios using parse tagsEQ_ASSIGN
,EQ_SUB
, andEQ_FORMALS
(#1977, @MichaelChirico). -
equals_na_linter()
checks forx %in% NA
, which is a more convoluted form ofis.na(x)
(#2088, @MichaelChirico).
New and improved features
- New exclusion sentinel
# nolint next
to signify the next line should skip linting (#1791, @MichaelChirico). The usual rules apply for excluding specific linters, e.g.# nolint next: assignment_linter.
. The exact string used to match a subsequent-line exclusion is controlled by theexclude_next
config entry or R option"lintr.exclude_next"
. - New
xp_call_name()
helper to facilitate writing custom linters (#2023, @MichaelChirico). This helper converts a matched XPath to the R function to which it corresponds. This is useful for including the “offending” function in the lint’s message. - New
make_linter_from_xpath()
to facilitate making simple linters directly from a single XPath (#2064, @MichaelChirico). This is especially helpful for making on-the-fly/exploratory linters, but also extends to any case where the linter can be fully defined from a static lint message and single XPath. - Toggle lint progress indicators with argument
show_progress
tolint_dir()
andlint_package()
(#972, @MichaelChirico). The default is still to show progress ininteractive()
sessions. Progress is also now shown with a “proper” progress bar (utils::txtProgressBar()
), which in particular solves the issue of progress.
spilling well past the width of the screen in large directories. -
lint()
,lint_dir()
, andlint_package()
fail more gracefully when the user mis-spells an argument name (#2134, @MichaelChirico). - Quarto files (.qmd) are included by
lint_dir()
by default (#2150, @dave-lovell).
New linters
-
library_call_linter()
can detect if all library/require calls are not at the top of your script (#2027, #2043, #2163, and #2170, @nicholas-masel and @MichaelChirico). -
keyword_quote_linter()
for finding unnecessary or discouraged quoting of symbols in assignment, function arguments, or extraction (part of #884, @MichaelChirico). Quoting is unnecessary when the target is a valid R name, e.g.c("a" = 1)
can bec(a = 1)
. The same goes to assignment ("a" <- 1
) and extraction (x$"a"
). Where quoting is necessary, the linter encourages doing so with backticks (e.g.x$`a b`
instead ofx$"a b"
). -
length_levels_linter()
for using the specific functionnlevels()
instead of checkinglength(levels(x))
(part of #884, @MichaelChirico). -
scalar_in_linter()
for discouraging%in%
when the right-hand side is a scalar, e.g.x %in% 1
(part of #884, @MichaelChirico). -
if_not_else_linter()
for encouragingif
statements to be structured asif (A) x else y
instead ofif (!A) y else x
(part of #884, @MichaelChirico). -
repeat_linter()
for encouragingrepeat
for infinite loops instead ofwhile (TRUE)
(#2106, @MEO265). -
length_test_linter()
detects the common mistakelength(x == 0)
which is meant to belength(x) == 0
(#1991, @MichaelChirico).
Extensions to existing linters
-
fixed_regex_linter()
gains an optionallow_unescaped
(defaultFALSE
) to toggle linting regexes not requiring any escapes or character classes (#1689, @MichaelChirico). Thusfixed_regex_linter(allow_unescaped = TRUE)
would lint ongrepl("[$]", x)
but not ongrepl("a", x)
since the latter does not use any regex special characters. -
line_length_linter()
helpfully includes the line length in the lint message (#2057, @MichaelChirico). -
conjunct_test_linter()
also lints usage likedplyr::filter(x, A & B)
in favor of usingdplyr::filter(x, A, B)
(part of #884; #2110 and #2078, @salim-b and @MichaelChirico). Optionallow_filter
toggles when this applies.allow_filter = "always"
drops such lints entirely, while"not_dplyr"
only lints calls explicitly qualified asdplyr::filter()
. The default,"never"
, assumes all unqualified calls tofilter()
aredplyr::filter()
. -
sort_linter()
checks for code likex == sort(x)
which is better served by using the functionis.unsorted()
(part of #884, @MichaelChirico). -
paste_linter()
gains detection for file paths that are better constructed withfile.path()
, e.g.paste0(dir, "/", file)
would be better asfile.path(dir, file)
(part of #884, #2082, @MichaelChirico). What exactly gets linted here can be fine-tuned with theallow_file_path
option ("double_slash"
by default, with alternatives"never"
and"always"
). When"always"
, these rules are ignored. When"double_slash"
, paths appearing to construct a URL that have consecutive forward slashes (/
) are skipped. When"never"
, even URLs should be constructed withfile.path()
. -
seq_linter()
recommendsrev()
in the lint message for lints likenrow(x):1
(#1542, @MichaelChirico). -
function_argument_linter()
detects usage ofmissing()
for the linted argument (#1546, @MichaelChirico). The simplest fix forfunction_argument_linter()
lints is typically to set that argument toNULL
by default, in which case it’s usually preferable to update function logic checkingmissing()
to checkis.null()
instead. -
commas_linter()
gains an optionallow_trailing
(defaultFALSE
) to allow trailing commas while indexing. (#2104, @MEO265) -
unreachable_code_linter()
-
implicit_assignment_linter()
gains an argumentallow_lazy
(defaultFALSE
) that allows optionally skipping lazy assignments likeA && (B <- foo(A))
(#2016, @MichaelChirico). -
unused_import_linter()
gains an argumentinterpret_glue
(defaultTRUE
) paralleling that inobject_usage_linter()
to toggle whetherglue::glue()
expressions should be inspected for exported object usage (#2042, @MichaelChirico). -
default_undesirable_functions
is updated to also includeSys.unsetenv()
andstructure()
(#2192 and #2228, @IndrajeetPatil and @MichaelChirico). - Linters with logic around the magrittr pipe
%>%
consistently apply it to the other pipes%!>%
,%T>%
,%<>%
(and possibly%$%
) where appropriate (#2008, @MichaelChirico). - Linters with logic around function declarations consistently include the R 4.0.0 shorthand
\()
(#2190, @MichaelChirico).
Lint accuracy fixes: removing false positives
-
fixed_regex_linter()
- Is pipe-aware, in particular removing false positives around piping into {stringr} functions like
x |> str_replace(fixed("a"), "b")
(#1811, @MichaelChirico). - Ignores non-string inputs to
pattern=
as a keyword argument (#2159, @MichaelChirico).
- Is pipe-aware, in particular removing false positives around piping into {stringr} functions like
- Several linters avoiding false positives in
$
extractions get the same exceptions for@
extractions, e.g.S4@T
will no longer throw aT_and_F_symbol_linter()
hit (#2039, @MichaelChirico). -
sprintf_linter()
is pipe-aware, so thatx %>% sprintf(fmt = "%s")
no longer lints (#1943, @MichaelChirico). -
condition_message_linter()
ignores usages of extracted calls likeenv$stop(paste(a, b))
(#1455, @MichaelChirico). -
inner_combine_linter()
no longer throws on length-1 calls toc()
likec(exp(2))
orc(log(3))
(#2017, @MichaelChirico). Such usage is discouraged byunnecessary_concatenation_linter()
, butinner_combine_linter()
per se does not apply. -
sort_linter()
only lints onorder()
of a single vector, excluding e.g.x[order(x, y)]
andx[order(y, x)]
(#2156, @MichaelChirico). -
redundant_ifelse_linter()
is aware ofdplyr::if_else()
’smissing=
argument, so thatif_else(A, TRUE, FALSE, missing = FALSE)
doesn’t lint, butif_else(A, TRUE, FALSE, NA)
does (#1941, @MichaelChirico). Note thatdplyr::coalesce()
ortidyr::replace_na()
may still be preferable.
Lint accuracy fixes: removing false negatives
-
unreachable_code_linter()
finds unreachable code even in the presence of a comment or semicolon afterreturn()
orstop()
(#2127, @MEO265). -
implicit_assignment_linter()
- finds assignments in call arguments besides the first one (#2136, @MichaelChirico).
- finds assignments in parenthetical expressions like
if (A && (B <- foo(A))) { }
(#2138, @MichaelChirico).
-
unnecessary_lambda_linter()
checks for cases using explicit returns, e.g.lapply(x, \(xi) return(sum(xi)))
(#1567, @MichaelChirico).- thanks to @Bisaloo and @strengejacke for detecting a regression in the original fix (#2231, #2247).
lintr 3.1.0
CRAN release: 2023-07-19
Deprecations & Breaking Changes
-
.lintr
files can now be kept in the directory.github/linters
for better compatibility with Super-Linter. Note that this may be a breaking change if you already have a config in.github/linters
inside a subdirectory as well as in your R project’s root, since the former will now be discovered first where it was ignored before. Please seevignette("lintr")
for details on how configs are discovered (#1746, @tonyk7440 and @klmr). -
single_quotes_linter()
is deprecated in favor of the more generalizablequotes_linter()
(#1729, @MichaelChirico). -
unneeded_concatentation_linter()
is deprecated in favor ofunnecessary_concatenation_linter()
for naming consistency (#1707, @IndrajeetPatil). -
consecutive_stopifnot_linter()
is deprecated in favor of the more general (see below)consecutive_assertion_linter()
(#1604, @MichaelChirico). -
no_tab_linter()
is deprecated in favor ofwhitespace_linter()
for naming consistency and future generalization (#1954, @MichaelChirico). -
available_linters()
prioritizestags
overexclude_tags
in the case of overlap, i.e., tags listed in both arguments are included, not excluded. We don’t expect many people to be affected by this, and the old behavior was not made explicit in the documentation, but make note of it here since it required changing a test in lintr’s own suite wherelinters_with_tags()
implicitly assumed this behavior. -
lint()
,lint_dir()
, andlint_package()
no longer accept certain arguments (cache=
forlint()
,relative_path=
for the latter two) positionally. Thewarning()
since 3.0.0 has been upgraded to an error.
Bug fixes
linters_with_tags()
now includes the previously missing spaces around “and” when listing missing linters advertised byavailable_linters()
. This error message may appear e.g. when you update lintr to a version with new linters but don’t restart your R session (#1946, @Bisaloo)fixed_regex_linter()
is more robust to errors stemming from unrecognized escapes (#1545, #1845, @IndrajeetPatil).get_source_expressions()
can handle Sweave/Rmarkdown documents with reference chunks like<<ref_file>>
(#779, @MichaelChirico). Note that these are simply skipped, rather than attempting to retrieve the reference and also lint it.assignment_linter()
no longer lints assignments in braces that include comments whenallow_trailing = FALSE
(#1701, @ashbaldry)-
- No longer silently ignores usage warnings that don’t contain a quoted name (#1714, @AshesITR)
- No longer fails on code with comments inside a multi-line call to
glue::glue()
(#1919, @MichaelChirico)
namespace_linter()
correctly recognizes backticked operators to be exported from respective namespaces (likerlang::`%||%`
) (#1752, @IndrajeetPatil)lint_package()
correctly finds a package from within a subdir if thepath
points to anywhere within the package (#1759, @AshesITR)-
Improved error behavior in
Lint()
,lint()
andxml_nodes_to_lints()
(#1427, #763, @AshesITR)-
Lint()
validates its inputs more thoroughly, preventing errors duringprint.Lints
like “Error in rep.int(character, length) : invalid ‘times’ value:”. -
lint()
no longer tries to create an expression tree with unexpected end of input errors, because they can be broken. -
xml_nodes_to_lints()
warns if it can’t find lint locations and uses dummy locations as a fallback.
-
linters_with_defaults()
no longer erroneously marks linter factories as linters (#1725, @AshesITR).Row names for
available_linters()
data frame are now contiguous (#1781, @IndrajeetPatil).object_name_linter()
allows all S3 group Generics (see?base::groupGeneric
) and S3 generics defined in a different file in the same package (#1808, #1841, @AshesITR)-
object_usage_linter()
improves identification of the exact source of a lint- for undefined variables in expressions with where the variable is used as a symbol in a usual way, for example in a formula or in an extraction with
$
(#1914, @MichaelChirico). - for general usage warnings without location info (#1986 and #1917, @AshesITR)
- for undefined variables in expressions with where the variable is used as a symbol in a usual way, for example in a formula or in an extraction with
function_left_parentheses_linter()
produces a more specific lint (and no longer fails) when the opening parenthesis is on a different line thanfunction
or the call name (#1953, @MichaelChirico). Thanks also to @IndrajeetPatil and @lorenzwalthert for identifying a regression in the initial fix, #1963.
Changes to defaults
Set the default for the
except
argument induplicate_argument_linter()
toc("mutate", "transmute")
. This allows sequential updates likex |> mutate(a = b + 1, a = log(a))
(#1345, @IndrajeetPatil).-
- gains
skip_with
argument to skip code inwith()
expressions. To be consistent withR CMD check
, it defaults toTRUE
(#941, #1458, @IndrajeetPatil). - Handles backticked symbols inside {glue} expressions correctly, e.g.
glue("{`x`}")
correctly determinesx
was used (#1619, @MichaelChirico) - Detects problems inside R4.1.0+ lambda functions (
\(...)
) (#1933, @MichaelChirico)
- gains
spaces_inside_linter()
allows terminal missing keyword arguments (e.g.alist(arg = )
; #540, @MichaelChirico)brace_linter()
allows empty braced expression on the same line (e.g.while (updating_condition()) { }
) regardless ofallow_single_line
to match the corresponding behavior in {styler}. This is an expedient while the style guide on handling this case awaits clarification: https://github.com/tidyverse/style/issues/191. (#1346, @MichaelChirico)undesirable_function_linter()
andundesirable_operator_linter()
now produce an error if empty vector of undesirable functions or operators is provided (#1867, @IndrajeetPatil).-
New linters which are also included as defaults (see “New linters” for more details):
lint_package()
also looks for files inexec/
(#1950, @jmaspons).
New and improved features
New
get_r_string()
helper to get the R-equivalent value of a string, especially useful for R-4-style raw strings. Previously an internallintr
helper, now exported to facilitate writing custom linters (#1493, @MichaelChirico).object_usage_linter()
improves lint metadata when detecting undefined infix operators, e.g.%>%
or:=
(#1497, @MichaelChirico)unused_import_linter()
can detect datasets from imported packages and no longer warns when a package is imported only for its datasets (#1545, @IndrajeetPatil).-
When a linter triggers an error,
lint()
will provide a more actionable summary of where the error occurred, particularly useful for cases likelint_package()
where both the responsible file and the responsible linter would be unknown (@MichaelChirico).Typically, linters should not themselves cause R to stop – syntax errors lead to error lints, for example. Please report such failures as they are likely bugs.
pipe_continuation_linter()
recognizes violations involving the native R pipe|>
(#1609, @MichaelChirico)paste_linter()
also catches usages likepaste(rep("*", 10L), collapse = "")
that can be written more concisely asstrrep("*", 10L)
(#1108, @MichaelChirico)spaces_inside_linter()
produces lints for spaces inside[[
(#1673, @IndrajeetPatil).sprintf_linter()
also applies togettextf()
(#1677, @MichaelChirico)Documentation for all linters contains examples of code that does and does not produce lints (#1492, @IndrajeetPatil).
implicit_integer_linter()
gains parameterallow_colon
to skip lints on expressions like1:10
(#1155, @MichaelChirico)infix_spaces_linter()
supports the native R pipe|>
(#1793, @AshesITR)unnecessary_concatenation_linter()
(f.k.a.unneeded_concatenation_linter()
) no longer lints onc(...)
(i.e., passing...
in a function call) whenallow_single_expression = FALSE
(#1696, @MichaelChirico)object_name_linter()
gains parameterregexes
to allow custom naming conventions (#822, #1421, @AshesITR)literal_coercion_linter()
reports a replacement in the lint message, e.g. code likeas.integer(1)
will suggest using1L
instead, and code likeas.numeric(NA)
will suggest usingNA_real_
instead (#1439, @MichaelChirico)Added
format()
functions forlint
andlints
(#1784, @AshesITR)all_linters()
function provides an easy way to access all available linters (#1843, @IndrajeetPatil)missing_argument_linter()
allows missing arguments inquote()
calls (#1889, @IndrajeetPatil).get_source_expressions()
correctly extracts indented code chunks from R Markdown documents, which helps avoid spurious lints related to whitespace (#1945, @MichaelChirico). The convention taken is that, within each chunk, all code is anchored relative to the leftmost non-whitespace column.available_linters()
gives priority totags
overexclude_tags
in the case of overlap. In particular, this means thatavailable_linters(tags = "deprecated")
will work to return deprecated linters without needing to specifyexclude_tags
(#1959, @MichaelChirico).The {lintr} configuration file is now searched in the system’s user configuration path; the lintr config filename can also be configured explicitly by setting the environment variable
R_LINTR_LINTER_FILE
(#460, @klmr)Errors in the {lintr} configuration file now produce more informative error messages (#886, @AshesITR)
New linters
matrix_apply_linter()
recommends use of dedicatedrowSums()
,colSums()
,colMeans()
,rowMeans()
overapply(., MARGIN, sum)
orapply(., MARGIN, mean)
. The recommended alternative is much more efficient and more readable (#1869, @Bisaloo).unnecessary_lambda_linter()
: detect unnecessary lambdas (anonymous functions), e.g.lapply(x, function(xi) sum(xi))
can belapply(x, sum)
andpurrr::map(x, ~quantile(.x, 0.75, na.rm = TRUE))
can bepurrr::map(x, quantile, 0.75, na.rm = TRUE)
. Namingprobs = 0.75
can further improve readability (#1531, #1866, @MichaelChirico, @Bisaloo).redundant_equals_linter()
for redundant comparisons toTRUE
orFALSE
likeis_treatment == TRUE
(#1500, @MichaelChirico)lengths_linter()
for encouraging usage oflengths(x)
instead ofsapply(x, length)
(and similar)function_return_linter()
for handling issues in functionreturn()
statements. Currently handles assignments within thereturn()
clause, e.g.return(x <- foo())
(@MichaelChirico)boolean_arithmetic_linter()
for identifying places where logical aggregations are more appropriate, e.g.length(which(x == y)) == 0
is the same as!any(x == y)
or evenall(x != y)
(@MichaelChirico)for_loop_index_linter()
to prevent overwriting local variables in afor
loop declared likefor (x in x) { ... }
(@MichaelChirico)is_numeric_linter()
for redundant checks equivalent tois.numeric(x)
such asis.numeric(x) || is.integer(x)
orclass(x) %in% c("numeric", "integer")
(@MichaelChirico)empty_assignment_linter()
for identifying empty assignments likex = {}
that are more clearly written asx = NULL
(@MichaelChirico)unnecessary_placeholder_linter()
for identifying where usage of the {magrittr} placeholder.
could be omitted (@MichaelChirico)routine_registration_linter()
for identifying native routines that don’t use registration (useDynLib
in theNAMESPACE
; @MichaelChirico)indentation_linter()
for checking that the indentation conforms to 2-space Tidyverse-style (@AshesITR and @dgkf, #1411, #1792, #1898).unnecessary_nested_if_linter()
for checking unnecessary nestedif
statements where a singleif
statement with appropriate conditional expression would suffice (@IndrajeetPatil and @AshesITR, #1778).implicit_assignment_linter()
for checking implicit assignments in function calls (@IndrajeetPatil and @AshesITR, #1777).quotes_linter()
is a generalized version of (now deprecated)single_quotes_linter()
. It accepts an argumentdelimiter
to specify whether"
or'
should be the accepted method for delimiting character literals. The default,"
, reflects the Tidyverse style guide recommendation and matches the behavior ofsingle_quotes_linter()
.unnecessary_concatenation_linter()
is simplyunneeded_concatenation_linter()
, renamed.consecutive_assertion_linter()
(f.k.a.consecutive_stopifnot_linter()
) now lints for consecutive calls toassertthat::assert_that()
(as long as themsg=
argument is not used; #1604, @MichaelChirico).whitespace_linter()
is simplyno_tab_linter()
, renamed. In the future, we plan to extend it to work for different whitespace preferences.
Notes
{lintr} now depends on R version 3.5.0, in line with the tidyverse policy for R version compatibility.
-
lint()
continues to support Rmarkdown documents. For users of custom .Rmd engines, e.g.marginformat
from {tufte} ortheorem
from {bookdown}, note that those engines must be registered in {knitr} prior to runninglint()
in order for {lintr} to behave as expected, i.e., they should be shown as part ofknitr::knit_engines$get()
.For {tufte} and {bookdown} in particular, one only needs to load the package namespace to accomplish this (i.e., minimally
loadNamespace("tufte")
orloadNamespace("bookdown")
, respectively, will register those packages’ custom engines; sincelibrary()
also runsloadNamespace()
, runninglibrary()
will also work). Note further that {tufte} only added this code to their.onLoad()
recently after our request to do so (see https://github.com/rstudio/tufte/issues/117). Therefore, ensure you’re using a more recent version to get the behavior described here for {tufte}.More generally, there is no requirement that
loadNamespace()
will register a package’s custom {knitr} engines, so you may need to work with other package authors to figure out a solution for other engines.Thanks to Yihui and other developers for their helpful discussions around this issue (#797, @IndrajeetPatil).
-
The output of
lint()
andLint()
gain S3 class"list"
to assist with S3 dispatch (#1494, @MichaelChirico)- As a corollary, we now register an
as_tibble
method for classlints
, conditional on {tibble} availability, to avoid dispatching to thelist
method which does not work withlint()
output (#1997, @MichaelChirico)
- As a corollary, we now register an
object_usage_linter()
gives a more helpful warning when aglue()
expression fails to evaluate (#1985, @MichaelChirico)The documentation of
object_name_linter()
now describes how"symbols"
works when passed to thestyles
parameter (#1924, @hedsnz).
lintr 3.0.2
CRAN release: 2022-10-19
- Fix test to avoid leaving behind cache files in the global cache directory.
lintr 3.0.1
CRAN release: 2022-09-13
Skip multi-byte tests in non UTF-8 locales (#1504)
modify_defaults()
no longer uses the mistaken"lintr_function"
S3 class, instead applying the"linter"
class also common toLinter()
.Linter()
also includes"function"
in the S3 class of its output to facilitate S3 dispatch tofunction
methods where appropriate (#1392, @MichaelChirico).
Changes to defaults
brace_linter()
allows opening curly braces on a new line when there is a comment ending the preceding line (#1433 and #1434, @IndrajeetPatil).seq_linter()
produces lint forseq(...)
, since it also cannot properly handle empty edge cases (#1468, @IndrajeetPatil).seq_linter()
additionally lints on1:n()
(from {dplyr}) and1:.N
(from {data.table}) (#1396, @IndrajeetPatil).literal_coercion_linter()
lints {rlang}’s atomic vector constructors (i.e.,int()
,chr()
,lgl()
, anddbl()
) if the argument is a scalar (#1437, @IndrajeetPatil).redundant_ifelse_linter()
’s lint message correctly suggests negation when theyes
condition is0
(#1432, @IndrajeetPatil).seq_linter()
provides more specific replacement code in lint message (#1475, @IndrajeetPatil).
New and improved features
New
sort_linter()
to detectx[order(x)]
and recommend the faster and clearer alternative:sort(x)
(#1528, @Bisaloo)unreachable_code_linter()
ignores trailing comments if they match a closing nolint block (#1347, @AshesITR).New
function_argument_linter()
to enforce that arguments with defaults appear last in function declarations, see the Tidyverse design guide (#450, @AshesITR).New
allow_trailing
argument added toassignment_linter()
to check when assignment operators are at the end of a line, and the value is on the following line (#1491, @ashbaldry)New
sarif_output()
function to output lints to SARIF output (#1424, @shaopeng-gh)commented_code_linter()
now lints commented argument code, containing a trailing comma, as well (#386, @AshesITR). For example a comment containing# na.rm = TRUE,
now triggers a lint.
Bug fixes
-
object_length_linter()
does not fail in case there are dependencies with no exports (e.g. data-only packages) (#1424, #1509, @IndrajeetPatil). -
get_source_expressions()
no longer fails on R files that match a knitr pattern (#743, #879, #1406, @AshesITR). - Parse error lints now appear with the linter name
"error"
instead ofNA
(#1405, @AshesITR).
Also, linting no longer runs if thesource_expressions
contain invalid string data that would cause error messages in other linters. in other linters. - Prevent
lint()
from hanging on Rmd files with some syntax errors (#1443, @MichaelChirico). -
get_source_expressions()
no longer omits trailing non-code lines from knitr files (#1400, #1415, @AshesITR).
This fixes the location information fortrailing_blank_lines_linter()
in RMarkdown documents without terminal newlines. - The
vignette("lintr")
incorrectly citedexclude
as the key for setting file exclusions in.lintr
when it is actuallyexclusions
. (#1401, @AshesITR) - Fixed file exclusion detection in
lint_dir()
so it no longer errors if there are multiple exclusions or no global exclusions configured for a single file (#1413, #1442, @AshesITR).
Other changes
- The minimum needed version for soft dependency withr has been bumped to
2.5.0
(#1404, @IndrajeetPatil). - Changed the deprecation warning for
with_defaults()
to also mentionmodify_defaults()
(#1438, @AshesITR). - Quarto files (
.qmd
) were supported out of the box. The documentation and the testing infrastructure are updated to reflect this (#1486, @IndrajeetPatil).
lintr 3.0.0
CRAN release: 2022-06-13
Breaking changes
-
All linters are now function factories (i.e., functions that return functions) for consistency. Previously, only linters with customizable parameters were factories (#245, @fangly, @AshesITR, and @MichaelChirico).
This means that usage such as
lint("file.R", seq_linter)
should be updated tolint("file.R", seq_linter())
, and the following update for custom linters:my_custom_linter <- function(source_expression) { ... } # becomes my_custom_linter <- function() Linter(function(source_expression) { ... })
Exclusions specified in the
.lintr
file are now relative to the location of that file and support excluding entire directories (#158, #438, @AshesITR).-
Removed long-deprecated linters (they’ve been marked as deprecated since v1.0.1 in 2017):
absolute_paths_linter()
camel_case_linter()
multiple_dots_linter()
snake_case_linter()
trailing_semicolons_linter()
-
Removed
return()
fromall_undesirable_functions
because early returns (which often improve readability and reduce code complexity) require explicit use ofreturn()
. Follow #1100 for an upcomingreturn_linter()
to lint unnecessaryreturn()
statements (#1146, @AshesITR).Note that you can replicate old behavior by supplying
return
as a custom undesirable function:undesirable_function_linter(c(all_undesirable_functions, list(return = NA)))
Deprecations
- Lints are now marked with the name of the
linter
that caused them instead of the name of their implementation function. Deprecated the obsoletelinter
argument ofLint()
(#664, #673, #746, @AshesITR). Downstream custom linters should follow suit. - Renamed
semicolon_terminator_linter()
tosemicolon_linter()
for better consistency.semicolon_terminator_linter()
survives but is marked for deprecation. The new linter also has a new signature, taking argumentsallow_compound
andallow_trailing
to replace the old single argumentsemicolon
, again for signature consistency with other linters. - The following linters were subsumed into
brace_linter()
and are now deprecated; see the item onbrace_linter()
below: - The
...
argument forlint()
,lint_dir()
, andlint_package()
has been promoted to an earlier position to better match the Tidyverse design principle of data->descriptor->details. This change enables passing objects to...
without needing to specify non-required arguments, e.g.lint_dir("/path/to/dir", linter())
now works without the need to specifyrelative_path
. This affects some code that uses positional arguments (#935, @MichaelChirico).- For
lint()
,...
is now the 3rd argument, where earlier this wascache
. - For
lint_dir()
andlint_package()
,...
is now the 2nd argument, where earlier this wasrelative_path
.
- For
- Deprecated argument
source_file
to exported functionswith_id()
andids_with_token()
. It has been renamed tosource_expression
to better reflect that this argument is typically the output ofget_source_expressions()
. For now, the old argumentsource_file
can still be used (with warning). The now-private functional versions of many linters also underwent the same renaming (source_file
->source_expression
). This has no direct effect on packages importing lintr, but is mentioned in case custom linters imitatinglintr
style had also adopted thesource_file
naming and want to adapt to keep in sync. - Deprecated
with_defaults()
in favor oflinters_with_defaults()
, and addmodify_defaults()
which is intended to be used more generally to modify (i.e., extend, trim, and/or update) a list of defaults. Note that the argument corresponding towith_defaults()
’sdefault=
is calleddefaults=
(i.e., pluralized) in both of these, and that usage likewith_defaults(default = NULL, ...)
should be converted tolinters_with_defaults(defaults = list(), ...)
(#1029, #1336, #1361, @AshesITR and @michaelchirico). - Deprecated the
find_line()
andfind_column()
helpers from the item-levelexpressions
returned withget_source_expressions()
. These helpers were typically associated with regex-based logic for building linters, which is rarely needed and prone to false positives; now that lintr almost exclusively uses XPath-based logic for linters, these are no longer necessary (#1373, @MichaelChirico).
Other changes to defaults
Updates to default_linters
- New
brace_linter()
which combines several curly brace related linters, deprecating the following predecessors (#1041, @AshesITR):-
closed_curly_linter()
; both now also allow}]
in addition to})
and},
as exceptions, i.e.,}
doesn’t need to be on its own line if paired with a closing square bracket, a closing parenthesis, or a comma. Also improved lint metadata so that source markers land at the closing brace instead of the closing parenthesis to improve the experience of fixing the lint (#583, @AshesITR). -
open_curly_linter()
; both also no longer lint unnecessary trailing whitespace (usetrailing_whitespace_linter()
for this) and also allow(
,,
, and%>%
on preceding lines as exceptions, i.e.,{
can be alone on a line if the previous line is terminated with an opening parenthesis, a comma, or a pipe (%>%
) (#487, #1028, @AshesITR). -
paren_brace_linter()
;brace_linter()
also lintsif
/else
andrepeat
with missing whitespace. -
brace_linter()
also newly enforces the following rules surrounding curly braces (originally Google linters, see below):- Require
else
to come on the same line as the preceding}
, if present (#884, @MichaelChirico). - Require functions spanning multiple lines to use curly braces (#987, @MichaelChirico).
- Require balanced usage of
{}
inif
/else
conditions, i.e., if theif
branch uses braces, then so must theelse
branch, and vice versa (#983, @MichaelChirico).
- Require
-
- New
paren_body_linter()
checks that there is a space between a right parenthesis and a body expression (#809, @kpagacz). - Added
semicolon_linter()
as a default because it enforces a tidyverse style guide rule (#683, @AshesITR). -
assignment_linter()
(#915, @MichaelChirico):- Right assignments are now linted by default (
->
and->>
). - New argument
allow_cascading_assign
(TRUE
by default) toggles whether to lint<<-
and->>
. - New argument
allow_right_assign
(FALSE
by default) toggles whether to lint->
and->>
.
- Right assignments are now linted by default (
-
commented_code_linter()
: use the parse tree to find comments, eliminating some false positives (#451, @AshesITR). -
equals_na_linter()
(#545, @MichaelChirico):- Extended to lint
x != NA
(before, only==
was caught) andNA == x
(before, onlyNA
on RHS was caught). - Extended to skip usages in comments like
is.na(x) # use is.na(x), not x == NA
.
- Extended to lint
-
function_left_parentheses_linter()
: improved location information (#1266, #1267, @AshesITR). -
infix_spaces_linter()
:- Added argument
allow_multiple_spaces
(TRUE
by default) which toggles whether to generate a lint for operators used with multiple spaces, e.g.x + 2
. The default setting allows extra spacing to be used to increase line-to-line alignment (#940, @f-ritter and @MichaelChirico). - Extended so that usages like
a~b
andfunction(a=1) { ... }
are linted (#930, #michaelchirico). - Added argument
exclude_operators
to disable lints on selected infix operators. By default, all “low-precedence” operators throw lints; see?infix_spaces_linter
for an enumeration of these. (#914, @MichaelChirico). - Add an exception for
/
usage inbox::use()
declarations (#1087, @klmr).
- Added argument
-
line_length_linter()
: place the source marker at the margin of the affected line to improve user experience during de-linting – just press Return (#735, @AshesITR).* -
no_tab_linter()
: use more reliable matching (e.g., excluding matches found in comments; #441, @russHyde). -
object_length_linter()
: correctly detect generics and only count the implementation class towards the length. This prevents false positive lints in the case of long generic names, e.g.very_very_very_long_generic_name.short_class
no longer produces a lint (#871, @AshesITR). -
object_name_linter()
:- Improved generic detection – in user-defined method
my_method.upstream.class
,upstream.class
no longer throws a lint because the generic (my_method
) properly usessnake_case
(#737, @AshesITR). - Exclude special R namespace hook functions such as
.onLoad()
(#500, #614, @AshesITR and @MichaelChirico). - Correctly detect imported functions when linting packages (#642, @AshesITR).
- Correctly detect assignment generics like
names<-.class_name
(#843, @jonkeane). - Added new styles
"symbols"
and"SNAKE_CASE"
(#494, #495, #615, #670, @MichaelChirico and @AshesITR).-
"symbols"
is a new default style which won’t lint all-symbol object names. In particular, that means operator names like%+%
are allowed.
-
- No longer lints names used in
$
extractions (#582, @AshesITR).
- Improved generic detection – in user-defined method
-
object_usage_linter()
:- Detect global variables if there are top-level dollar-assignments (#666, @AshesITR).
- Report usage warnings spanning multiple lines (#507, @AshesITR).
- Detect usages inside
glue::glue()
constructs (#942, @AshesITR). - Extended to include functions assigned with
=
instead of<-
(#1081, @MichaelChirico). - Detect functions exported by packages that are explicitly attached using
library()
orrequire()
calls (#1127, @AshesITR). - Improved location information in some cases where the previous regex-based approach didn’t work, e.g. unicode characters in variable names (#1285, @AshesITR).
- Correctly detect functions declared within
assign()
andsetMethod()
(#1322, @AshesITR).
-
spaces_inside_linter()
: ignore spaces preceding trailing comments (#636, @MichaelChirico). -
T_and_F_symbol_linter()
: -
trailing_blank_lines_linter()
: -
trailing_whitespace_linter()
:- Extended to also lint completely blank lines by default (#1044, @AshesITR).
- Added argument
allow_empty_lines
(FALSE
by default) to toggle this behavior. - Improved so that trailing whitespace inside string literals does not trigger a lint (#1045, @AshesITR).
- Added argument
allow_in_strings
(TRUE
by default) to toggle this behavior.
-
undesirable_function_linter()
:- Added new functions to
default_undesirable_functions
related to debugging (#876, @MichaelChirico): - No longer lints
library()
andrequire()
calls attaching a package with an undesired name, e.g.library(foo)
(#814, @kpagacz and @MichaelChirico). - No longer lints undesirable symbols if they are used as names in
$
extractions (#1050, @AshesITR). - Added more explanation why certain functions might be undesirable and what alternatives to use; ditto for
undesirable_operator_linter()
(#1133, #1146, #1159, @AshesITR).
- Added new functions to
Other noteworthy changes
-
cyclocomp_linter()
: set the defaultcomplexity_limit
to 15. This brings the default into sync with what is enforced viadefault_linters
(#693, @AshesITR). -
lint_package()
now lints files in thedemo
directory by default (#703, @dmurdoch). - Moved the default lintr cache directory from
~/.R/lintr_cache
(which was a violation of CRAN policy) toR_user_dir("lintr", "cache")
. Note that 3.0.0 is a major version update and invalidates the old cache anyway, so it can be safely deleted (#1062, @AshesITR).
New and improved features
New linters
-
backport_linter()
for detecting mismatched R version dependencies (#506, #1316, #1318, #1319, @MichaelChirico and @AshesITR). -
duplicate_argument_linter()
similarly checks that there are no duplicate arguments supplied to function calls (#850, @renkun-ken). -
missing_argument_linter()
to check for empty (missing) arguments in function calls (#563, #1152, @renkun-ken and @AshesITR). -
missing_package_linter()
to check if packages in calls tolibrary()
and friends are missing (#536, #1037, @renkun-ken and @MichaelChirico). -
namespace_linter()
to check for common mistakes inpkg::symbol
usages (#548, @renkun-ken). -
package_hooks_linter()
to run a series of checks also done byR CMD check
on the.onLoad()
,.onAttach()
,.Last.lib()
and.onDetach()
hooks (#882, @MichaelChirico). -
pipe_call_linter()
to enforce that all steps ofmagrittr
pipelines use explicit calls instead of symbols, e.g.x %>% mean()
instead ofx %>% mean
(#801, @MichaelChirico). -
sprintf_linter()
to check for common mistakes insprintf()
usage (#544, #624, @renkun-ken and @AshesITR). -
unused_import_linter()
to detect unnecessarylibrary()
calls in R scripts (#239, @jimhester, @AshesITR).
Google linters
Google is a heavy user of lintr internally, and has developed a large set of linters improving code consistency and correcting common R usage mistakes. This release includes many of these linters that are of general interest to the broader R community. More will be included in future releases. See, e.g. #884, #979, #998, #1011, #1016, #1036, #1051, #1066, and #1067; special thanks to @MichaelChirico and @michaelquinn32.
-
any_duplicated_linter()
Require usage ofanyDuplicated(x) > 0L
overany(duplicated(x))
and similar. -
any_is_na_linter()
Require usage ofanyNA(x)
overany(is.na(x))
. -
class_equals_linter()
Prevent comparingclass(x)
with==
,!=
, or%in%
, whereinherits()
is typically preferred. -
condition_message_linter()
Prevent condition messages from being constructed likestop(paste(...))
(where juststop(...)
is preferable). -
conjunct_test_linter()
Require usage ofexpect_true(x); expect_true(y)
overexpect_true(x && y)
and similar. -
consecutive_stopifnot_linter()
Require consecutive calls tostopifnot()
to be unified into one. -
expect_comparison_linter()
Require usage ofexpect_gt(x, y)
overexpect_true(x > y)
and similar. -
expect_identical_linter()
Require usage ofexpect_identical()
by default, andexpect_equal()
only by exception. -
expect_length_linter()
Require usage ofexpect_length(x, n)
overexpect_equal(length(x), n)
and similar. -
expect_named_linter()
Require usage ofexpect_named(x, n)
overexpect_equal(names(x), n)
and similar. -
expect_not_linter()
Require usage ofexpect_false(x)
overexpect_true(!x)
, and vice versa. -
expect_null_linter()
Require usage ofexpect_null(x)
overexpect_equal(x, NULL)
and similar. -
expect_s3_class_linter()
Require usage ofexpect_s3_class(x, k)
overexpect_equal(class(x), k)
and similar. -
expect_s4_class_linter()
Require usage ofexpect_s4_class(x, k)
overexpect_true(methods::is(x, k))
. -
expect_true_false_linter()
Require usage ofexpect_true(x)
overexpect_equal(x, TRUE)
and similar. -
expect_type_linter()
Require usage ofexpect_type(x, t)
overexpect_equal(typeof(x), t)
and similar. -
fixed_regex_linter()
Requirefixed = TRUE
orstringr::fixed()
for regular expressions that can be expressed statically, e.g.strsplit(x, "[.]")
can bestrsplit(x, ".", fixed = TRUE)
.- Added parameter
allow_grepl
(defaultFALSE
) to toggle whethergrepl()
usages should be linted. These might be treated separately becausegrepl("^x", NA)
isFALSE
; thestartsWith()
equivalent to getFALSE
for missing input is clunkier, but more explicit:!is.na(x) & startsWith(x, string)
(#1376, @MichaelChirico).
- Added parameter
-
ifelse_censor_linter()
Require usage ofpmax()
/pmin()
where appropriate, e.g.ifelse(x > y, x, y)
ispmax(x, y)
. -
inner_combine_linter()
Require inputs to known-vectorized functions to be combined first rather than later, e.g.as.Date(c(x, y))
overc(as.Date(x), as.Date(y))
. -
literal_coercion_linter()
Require using correctly-typed literals instead of direct coercion, e.g.1L
instead ofas.numeric(1)
. -
nested_ifelse_linter()
Prevent nested calls toifelse()
likeifelse(A, x, ifelse(B, y, z))
, and similar. -
numeric_leading_zero_linter()
Require a leading0
in fractional numeric constants, e.g.0.1
instead of.1
. -
outer_negation_linter()
Require usage of!any(x)
overall(!x)
and!all(x)
overany(!x)
. -
paste_linter()
lint for common mis-use ofpaste()
andpaste0()
:-
paste0()
encouraged instead ofpaste(sep = "")
. -
toString()
orglue::glue_collapse()
encouraged instead ofpaste(x, collapse = ", ")
. - Lint
sep=
passed topaste0()
– typically a mistake.
-
-
redundant_ifelse_linter()
Prevent usage likeifelse(A & B, TRUE, FALSE)
orifelse(C, 0, 1)
(the latter isas.numeric(!C)
). -
regex_subset_linter()
Require usage ofgrep(ptn, x, value = TRUE)
overx[grep(ptn, x)]
and similar. -
string_boundary_linter()
Require usage ofstartsWith(x, ptn)
overgrepl("^ptn", x)
orsubstr(x, 1, 3) == ptn
and similar. -
strings_as_factors_linter()
Check for code designed to work before and after thestringsAsFactors = FALSE
default change in R 4.0 by examining code fordata.frame()
usages susceptible to assumptions about the default value ofstringsAsFactors=
. -
system_file_linter()
Prevent usage likefile.path(system.file("A", package = "pkg"), "B")
where simplysystem.file("A", "B", package = "pkg")
is more concise and readable. -
unreachable_code_linter()
Prevent code afterreturn()
andstop()
statements that will never be reached (extended for #1051 thanks to early user testing, thanks @bersbersbers!). -
vector_logic_linter()
Require use of scalar logical operators (&&
and||
) insideif()
conditions and similar. -
yoda_test_linter()
Require usage ofexpect_identical(x, 1L)
overexpect_equal(1L, x)
and similar.
Other features and improvements
-
Documentation: Reorganize linter documentation into new tag-based Rd pages (#888, #1015, @AshesITR).
- Each linter has its own help page.
-
?linters
also links to tag help pages, collecting linters with a similar goal. - Each linter can have multiple tags.
-
available_linters()
: new function to list available linters and their tags. This feature is extensible by package authors providing add-on linters for {lintr}. -
available_tags()
: new function to list available tags. -
linters_with_tags()
: new function to help build a list of linters using tags.
-
Encodings: lintr now supports non-system character Encodings. The correct encoding is auto-detected from .Rproj or DESCRIPTION files in your project. Override the default in the
encoding
setting of lintr (#752, #782, @AshesITR). - Jenkins CI: Support for writing comments to GitHub repo when running in Jenkins CI (#488, @fdlk).
-
Performance: Optimized performance-critical functions in lintr, such as
get_source_expressions()
resulting in about 2x speedup in our test suite and even more for complex files (#1169, #1197, #1200, #1201, #1214, @MichaelChirico and @AshesITR). Averagelint_package()
execution time is down about 30% and the median package sees about 40% improvement. -
Raw strings: Several linters tightened internal logic to allow for raw strings like
R"( a\string )"
(#1034, #1285, @MichaelChirico and @AshesITR). -
Selective exclusion syntax: New syntax to exclude only selected linters from certain lines or passages. Use
# nolint: linter_name, linter2_name.
or# nolint start: linter_name, linter2_name.
in source files or named lists of line numbers in.lintr
. Note the terminal.
is required. Also allows for partial matching as long as the supplied prefix is unique, e.g.# nolint: infix_spaces.
works to excludeinfix_spaces_linter
(#605, #872, @AshesITR). - Improved S3 generic detection for non-standard S3 generics where
UseMethod()
is called after several preceding expressions (#846, @jonkeane). -
extraction_operator_linter()
: no longer lintx[NULL]
(#1273, @AshesITR). -
is_lint_level()
: new exported helper for readably explaining which type of expression is required for a custom linter. Some linters are written to require the full file’s parse tree (for example,single_quotes_linter()
). Others only need single expressions, which is more cache-friendly (most linters are written this way to leverage caching) (#921, @MichaelChirico). -
lint_dir()
excludes therenv
andpackrat
directories by default (#697, @AshesITR). -
lint()
: new optional argumenttext
for supplying a line or lines directly, e.g. if the file is already in memory or linting is being done ad hoc (#503, @renkun-ken). -
seq_linter()
: improve lint message to be clearer about the reason for linting (#522, @MichaelChirico). -
unneeded_concatenation_linter()
:- Correctly considers arguments in pipelines (
%>%
or|>
; #573, #1270, @michaelquinn32 and @AshesITR). - New argument
allow_single_expression
, defaultTRUE
, toggling whetherc(x)
should be linted, i.e., a call toc()
with only one entry which is not a constant. In some such cases,c()
can simply be dropped, e.g.c(a:b)
; in others, the parentheses are still needed, e.g.-c(a:b)
should be-(a:b)
; and in still others,c()
is used for the side-effect of stripping attributes, e.g.c(factor(letters))
orc(matrix(1:10, 5, 2))
. In this last case,c()
can (and should) in most cases be replaced byas.vector()
oras.integer()
for readability. In fact, we suspect it is always preferable to do so, and may change the default toallow_single_expression = FALSE
in the future. Please report your use case ifas.vector()
does not suit your needs (#1344, @MichaelChirico).
- Correctly considers arguments in pipelines (
-
use_lintr()
: new exported helper for creating a minimal.lintr
configuration (#902, @AshesITR). -
xml_nodes_to_lints()
: new exported helper for convertingxml_node
objects obtained using linter logic expressed in XPath intoLint
objects (#1124, #1216, #1234, @MichaelChirico and @AshesITR).
Bug fixes
- RStudio: Source markers are cleared when there are no lints (#520, @AshesITR).
- Error message for mismatched starts and ends of exclusion ranges is now more helpful. (#571, #860, @AshesITR and @danielinteractive).
- Improved location information for R parse errors (#894, #892, @renkun-ken and @AshesITR).
-
get_source_expressions()
:- Fix possible error on invalid XML produced by
xmlparsedata::xml_parse_data()
(#559, @renkun-ken). - Fix handling zero-length variable name error (#566, @renkun-ken).
- Malformed Rmd files now cause a lint instead of an error (#571, @AshesITR).
- No longer fails if
getParseData()
returns a truncated (invalid) Unicode character as parsed text (#815, @leogama). - Fixes the
text
value forSTR_CONST
nodes involving 1- or 2-width octal escapes (e.g."\1"
) to account for an R parser bug (https://bugs.r-project.org/show_bug.cgi?id=18323; #1056, @MichaelChirico). - Handle Rmd inputs containing unevaluated code blocks with named format specifiers (#472, @russHyde).
- Fix possible error on invalid XML produced by
-
line_length_linter()
: fix a bug causing duplicate lints for lines containing multiple expressions (#681, @AshesITR). -
lint_package()
:- Warns and returns
NULL
if no package is found (instead of giving a peculiar error message; #776, @MichaelChirico). - Stricter about what is considered to be a package – folders named
DESCRIPTION
are ignored (#702, @MichaelChirico).
- Warns and returns
-
linters_with_defaults()
(formerlywith_defaults()
): -
linters_with_defaults()
handles automatic naming of very long arguments correctly (#774, @MichaelChirico). -
save_cache()
will now recursively create the cache directory; this avoids errors that could arise if any parent directories do not exist (#60, @dankessler). -
spaces_left_parentheses_linter()
: fix a bug causing warnings like “Inparent == parent[before_operator_idx]
longer object length is not a multiple of shorter object length” in nested expressions (#654, @AshesITR).
Internals
- Added a new, more restrictive test workflow -
test-package
- that fails on warnings emitted by tests (#1263, #1272, @AshesITR). - Added a secondary, more restrictive lint workflow -
lint-changed-files
- for newly written / modified code (#641, @dragosmg). - Several optional
Imported
packages have becomeSuggested
dependencies:httr
,testthat
, andrstudioapi
. This should allow snappier CI builds for usages not relying on some more “peripheral” features of the package. - Special thanks to @bersbersbers for early testing on the 3.0.0 changes.
- Switched CI from Travis to GitHub Actions, using the full tidyverse recommended
R CMD check
. Code coverage and linting are implemented using separate GitHub Actions workflows (#572, @dragosmg). - Updated R CMD GitHub Actions workflow to check for R 3.6 on Ubuntu, instead of R 3.3, and for R 4.0 on Windows, instead of R 3.6 (#803, @ dragosmg).
-
lintr
now uses the 3rd edition oftestthat
(@MichaelChirico, @AshesITR, #910, #967).
lintr 2.0.1
CRAN release: 2020-02-19
New features
- lintr now supports GitHub Actions and will print the lints as warning messages if lints are printed during an action.
-
lint_package()
will now lint vignettes and data-raw by default (#447, @AshesITR). -
lint_dir()
will now include Rmd and Rnw files by default (@AshesITR).
Minor fixes and features
-
single_quote_linter()
no longer causes a print issue when open quote appears at a column > than close quote (#457, @jamieRowen) -
absolute_path_linter()
andnonportable_path_linter()
now handle file-paths that are wrapped with double-quotes (#433, #437, @russHyde). -
get_source_expressions()
has been changed to handleexpr_or_assign_or_help
tokens arising when parsing code containing equals-assignments in R-devel (#403, #456, @russHyde). -
object_usage_linter
has been changed to ensure lint-position is indicated relative to the start of the file, rather than the start of a defining function (#432, @russHyde). -
commas_linter
now allows spaces to come before a comma when used to denote a fall-through in a switch statement (#499, @MrMallIronmaker)
lintr 2.0.0
CRAN release: 2019-10-01
lintr 2.0.0 is a major release, and incorporates development changes since the last major release (1.0.0) in 2016-04-16.
Deprecated functions
- Deprecated
camel_case_linter()
,snake_case_linter()
andmultiple_dots_linter()
in favor ofobject_name_linter()
which enforce the given style: snake_case, dotted.case, lowerCamelCalse, UpperCamelCase, alllowercase or ALLUPPERCASE (#59, @fangly). - Deprecated absolute_paths_linter() in favor of the new
absolute_path_linter()
, with a lax mode for fewer false positive lints (#199, @fangly).
New linters
- New
cyclocomp_linter()
identifies overly complex functions (#361, @fabian-s) - New
equals_na_linter()
(#143, #326, @jabranham) - New
extraction_operator_linter()
checks that the[[
operator is used when extracting a single element from an object, not[
(subsetting) nor$
(interactive use) (@fangly). - New
function_left_parentheses_linter()
to check that there is no space between a function name and its left parentheses (#204, @jrnold). - New
implicit_integer_linter()
detects round numbers not declared as integers, i.e. 1 instead of 1L (@fangly). - New
nonportable_path_linter()
identifies paths constructed without file.path() (@fangly). - New
paren_brace_linter()
checks that there is a space between right parenthesis and an opening curly brace (@bfgray3, #242). - New
pipe_continuation_linter()
to ensure there is a space before %>% and newline afterwards (#216). - New
semicolon_terminator_linter()
reports semicolons at the end of a line (#147, - New
seq_linter()
, finds1:length(...)
(and similar) expressions (#155, 1) - New
todo_comment_linter()
lints TODOs (@fangly). - New
T_and_F_symbol_linter()
warns when using T and F instead of TRUE and FALSE (@fangly). - New
undesirable_operator_linter()
andundesirable_function_linter()
lint uses of user-specified functions and operators (#48, #149, @fangly). - New
unneeded_concatenation_linter()
lints uses of c() with a constant or no arguments (@fangly).
New functions for writing linters
- Export
expect_lint()
(#178, #210) - Export
ids_with_token()
andwith_id()
(#297 @stufield) - linters can use the XML parse tree as well now, via the https://github.com/MangoTheCat/xmlparsedata package (#154, 1)
New functions for users
- New
lint_dir()
function to lint files under a given directory (@arekbee, #360) - New
summary.lints()
function to summarize the linter results (#260, #262, @wlandau). - New
checkstyle_output()
function to output lints to checkstyle XML output (#156, @joshkgold)
Linter fixes
-
closed_curly_linter()
now allows closing parenthesis or comma after closing curly brace (#167, @Enchufa2) -
commas_linter()
now handles missing arguments calls properly (#145) -
commented_code_linter()
now relaxed, it no longer lints comments within roxygen blocks and does not consider “-” an R operator to avoid too many false positives. -
function_left_parentheses_linter()
now allows spaces if a function starts with a left parenthesis (#311) -
no_tab_linter()
now reports proper line in all cases (#134, @fangly) -
object_length_linter()
argumentlength
now defaults to 30 for consistency (#325 @DragosMG) -
object_name_linter()
now works when passed multiple styles (#341, @infotroph) -
object_usage_linter()
has been changed to better detect lexical scoping of global variables (#27, #336, #91, #382) -
object_usage_linter()
now respectsutils::globalVariables()
, so it can be used to avoid false positive warnings due to non-standard evaluation (#352) -
object_usage_linter()
now ignores top level calls that contain function definitions (#26). -
object_linter*()
s now only lint objects declared in the current file (#76, #108, #136, #191, #194, #201, @fangly). -
open_curly_linter()
andclosed_curly_linter()
now do not lint double curly syntax (#388) -
open_curly_linter()
now allows comments after the curly braces (#188) -
pipe_continuation_linter()
now behaves better in nested expressions, functions etc. (#366 @russHyde) -
space_inside_linter()
now reports proper line and column numbers (#203, @fangly)
General improvements and fixes
-
expect_lint()
now no longer shows Rstudio markers and error messages are correctly preserved (#180, #211, @fangly) -
Lint()
/as.data.frame()
error now fixed (#179, @fangly). -
lint()
no longer errors with inline\\Sexpr
(#127). -
lint()
no longer errors with ‘<% %>’ constructs (#185). -
lint_package()
now works with the cache, as intended (#146, @schloerke) -
lint_package()
now excludesR/RcppExports.R
by default (#282) -
lint_package()
now removes fully excluded files as soon as possible - lintr now looks up its configuration in any parent directories as well as the package directory (#238, #345)
-
seq_linter
is now one of the default linters (#316). - Fix issue in lintr’s compatibility with R-devel, due to to a new version of the PCRE library (#411.)
-
read_settings()
now has a better error message when the config file does not end with a newline (#160, #189) -
expect_lint_free()
is now automatically skipped when run on covr (#287) - Now lintr only tries to generate comments if running in wercker or travis CI (#166)
- Add support for overriding GitHub API Token via
GITHUB_TOKEN
environment variable (#63, @mattyb) - Config files are now also searched for in the users’ home directory (#266, @randy3k)
- Fixed crash caused by ambiguous cache file paths (#212, @fangly).
- RStudio addins to lint current source and project (fixes #264, @JhossePaul)
- Added proper handling of tab characters (fixes #44, @fangly)
- lintr does not need the igraph package any more (#152, 1)
- Fixed cache not saved in a directory other than requested (#213, @fangly) avoid reading and pre-processing of ignored files (@mwaldstein)
- Allow for any number of
#
to start a comment. Useful in ESS (#299, @prosoitos) - R Markdown files that do not contain chunks are no longer treated as code (#370).
- Fixed plain-code-block bug in Rmarkdown (#252, @russHyde)
- Fixed bug where non-R chunks using {lang}
engine format
were parsed from R-markdown (#322, @russHyde) - Ensured
lintr
runs / installs / tests on R-3.6: pinned to githubxmlparsedata
; ensure vectors are length-1 when compared using&&
and||
(#363 #377 #384 #391, @russHyde).
lintr 1.0.1
CRAN release: 2017-08-10
- bugfix to work with knitr 1.16.7
-
expect_lint_free()
now is always skipped on CRAN. This is necessary because the non-binary R source may not be available when running tests on CRAN, and those tests may not be run in the package directory.
lintr 0.3.3
CRAN release: 2015-09-15
- infix_spaces_linter now properly checks
=
in named arguments. (#130, @saurfang). - commas_linter now properly recognizes lints when preceded by a blank line and points to the missing space rather than the comma (#111, #129, @saurfang).
- Make spaces_left_parentheses_linter more robust when determining
(
type (#128, @saurfang) - commented_code_linter (#83, @jackwasey)
- Now trims long comments (#55, reported by @paulstaab)
- Automatic commenting of Github commits and pull requests when linting on Travis-CI
- expect_lint_free expectation can be added to testthat unit tests.
- Robust configuration system and exclusion logic
- Emacs and Sublime Text 3 plugins now available from their respective package repositories.
- add
names.lints
,split.lints
(#49, @ttriche) - Fixed bug that caused vim syntastic plugin not to work properly in windows (#46, @abossenbroek)
- allow lintr customization per project using
.lintr
config files. - use
globalenv()
instead ofbaseenv()
for default parent environment so thatmethods
will be included. - do not check object usage if eval fails. Fixes (#24, reported by @fabian-s)
-
trailing_whitespace_linter
was reporting the incorrect line number - Use RStudio source marker API to display lints (#37, @jjallaire)
- Permit single quotes if they quote literal double quotes (#28, @jackwasey)
-
# nolint
comments are respected with caching (#68, @krlmlr) - Properly handle all knitr document formats
- Allow for (( when linting (#259, @nathaneastwood)
- Remove ^ from infix spaces to conform with tidyverse. (#302, @nathaneastwood)