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. See previous NEWS for advice on how to replace them.
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 and readability (#1330, @MichaelChirico). In general, we favor lint messages to be phrased like "Action, reason" to put 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). One reason to avoid$
is that it allows partial matching where[[
does not. Useoptions(warnPartialMatchDollar = TRUE)
to disable this feature and restore some parity to using$
vs.[[
.unnecessary_nested_if_linter()
is deprecated and subsumed into the new/more generalunnecessary_nesting_linter()
.- Dropped 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 systems 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. With this, we also downgrade {cyclocomp} fromImports:
toSuggests:
. 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.- Arguments
allow_cascading_assign=
,allow_right_assign=
, andallow_pipe_assign=
toassignment_linter()
are all deprecated in favor of the newoperator=
argument. Usage of a positional first argument likeassignment_linter(TRUE)
, of which we found zero cases on GitHub, is totally deprecated to allowoperator=
to be positionally first. See below about the new argument.
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()
returns lints withranges[1L]==1L
when the offending line has 0 spaces (#2550, @MichaelChirico).literal_coercion_linter()
doesn't surface a warning aboutNA
s during coercion for code likeas.integer("a")
(#2566, @MichaelChirico).
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
- 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. 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).- New
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).
assignment_linter()
can be fully customized with the newoperator=
argument to specify an exact vector of assignment operators to allow (#2441, @MichaelChirico and @J-Moravec). The default is<-
and<<-
; authors wishing to use=
(only) for assignment in their codebase can useoperator = "="
. This supersedes several old arguments: to accomplishallow_cascading_assign=TRUE
, add"<<-"
(and/or"->>"
) tooperator=
; forallow_right_assign=TRUE
, add"->"
(and/or"->>"
) tooperator=
; forallow_pipe_assign=TRUE
, add"%<>%"
tooperator=
. Useoperator = "any"
to denote "ignore all assignment operators"; in this case, only the value ofallow_trailing=
matters. Implicit assignments with<-
are always ignored byassignment_linter()
; useimplicit_assignment_linter()
to handle linting these.- 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
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).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). Exclusion comments from {lintr} and {covr} (e.g.# nocov end
) are always excluded.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).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).commented_code_linter()
can detect commented code that ends with a pipe (#2671, @jcken95)
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 signals 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). Note that in older versions of R (e.g. pre-4.0),rep_len()
may not copy attributes as expected.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
{lintr}
now has a hex sticker (rstudio/hex-stickers#110). Thank you, @gregswinehart!- All user-facing messages (including progress bars) are now prepared using the
{cli}
package (#2418 and #2641, @IndrajeetPatil). As noted above, 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=)
.
New Contributors
- @jonthegeek made their first contribution in #2533
- @F-Noelle made their first contribution in #2574
- @olivroy made their first contribution in #2602
- @jcken95 made their first contribution in #2672
- @etiennebacher made their first contribution in #2697
Full Changelog: v3.1.1...v3.2.0