lintr 3.0.0
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 supplyingreturn
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:closed_curly_linter()
open_curly_linter()
paren_brace_linter()
- 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 to
source_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 the
source_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 with
get_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()
or
require()
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):browser()
debug()
debugcall()
debugonce()
trace()
untrace()
- 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 forundesirable_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)
is
pmax(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 of
as.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 simply
system.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 the correct encoding
is auto-detected from .Rproj or DESCRIPTION files in your project.
Override the default in theencoding
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).