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
.lintrfile 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_functionsbecause 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 supplyingreturnas a custom undesirable function:
undesirable_function_linter(c(all_undesirable_functions, list(return = NA)))
Deprecations
- Lints are now marked with the name of the
linterthat caused them instead of the name of their implementation
function. Deprecated the obsoletelinterargument 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_compoundandallow_trailingto 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_fileto exported functionswith_id()andids_with_token(). It has been renamed to
source_expressionto better reflect that this argument is typically the output ofget_source_expressions().
For now, the old argumentsource_filecan 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 imitatinglintrstyle had also adopted the
source_filenaming 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-levelexpressionsreturned 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/elseandrepeatwith missing whitespace.brace_linter()also newly enforces the following rules surrounding curly braces (originally Google linters, see
below):- Require
elseto 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/elseconditions, i.e., if theifbranch uses braces,
then so must theelsebranch, 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(TRUEby default) toggles whether to lint<<-and->>. - New argument
allow_right_assign(FALSEby 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, onlyNAon 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(TRUEby 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~bandfunction(a=1) { ... }are linted (#930, #michaelchirico). - Added argument
exclude_operatorsto disable lints on selected infix operators.
By default, all "low-precedence" operators throw lints; see?infix_spaces_linterfor 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_classno longer produces a lint (#871, @AshesITR).object_name_linter():- Improved generic detection -- in user-defined method
my_method.upstream.class,
upstream.classno 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(FALSEby 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(TRUEby default) to toggle this behavior.
undesirable_function_linter():- Added new functions to
default_undesirable_functionsrelated 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_limitto 15. This brings the default into sync with what
is enforced viadefault_linters(#693, @AshesITR).lint_package()now lints files in thedemodirectory 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::symbolusages (#548, @renkun-ken).package_hooks_linter()to run a series of checks also done byR CMD checkon the.onLoad(),.onAttach(),
.Last.lib()and.onDetach()hooks (#882, @MichaelChirico).pipe_call_linter()to enforce that all steps ofmagrittrpipelines 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) > 0Loverany(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 = TRUEorstringr::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
getFALSEfor 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.1Linstead 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 leading0in fractional numeric constants, e.g.0.1instead 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 = FALSEdefault
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.
?lintersalso 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 theencodingsetting 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 therenvandpackratdirectories by default (#697, @AshesITR).lint(): new optional argumenttextfor 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 = FALSEin 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.lintrconfiguration (#902, @AshesITR).xml_nodes_to_lints(): new exported helper for convertingxml_nodeobjects obtained using linter logic
expressed in XPath intoLintobjects (#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
textvalue forSTR_CONSTnodes 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
NULLif no package is found (instead of giving a peculiar error message; #776,
@MichaelChirico). - Stricter about what is considered to be a package -- folders named
DESCRIPTIONare 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
Importedpackages have becomeSuggesteddependencies: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). lintrnow uses the 3rd edition oftestthat(@MichaelChirico, @AshesITR, #910, #967).