Skip to content

v3.2.0

Latest
Compare
Choose a tag to compare
@MichaelChirico MichaelChirico released this 12 Feb 17:32
· 28 commits to main since this release
b96e5e5

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 to ids_with_token() and with_id().
    • Passing linters by name or as non-"linter"-classed functions.
    • linter= argument of Lint().
    • with_defaults().
    • Linters closed_curly_linter(), open_curly_linter(), paren_brace_linter(), and semicolon_terminator_linter().
    • Helper with_defaults().
  • all_linters() has signature all_linters(..., packages) rather than all_linters(packages, ...) (#2332, @MichaelChirico). This forces packages= to be supplied by name and will break users who rely on supplying packages= 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. Use options(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 general unnecessary_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} from Imports: to Suggests:. Note that users with cyclocomp_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; use in_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(), and list_comparison_linter() were removed from the tag efficiency (@IndrajeetPatil, #2655). If you use linters_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=, and allow_pipe_assign= to assignment_linter() are all deprecated in favor of the new operator= argument. Usage of a positional first argument like assignment_linter(TRUE), of which we found zero cases on GitHub, is totally deprecated to allow operator= to be positionally first. See below about the new argument.

Bug fixes

  • expect_identical_linter() also skips expect_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 within tolerance, so expect_identical() is not a great substitution.
  • object_name_linter() no longer errors when user-supplied regexes= have capture groups (#2188, @MichaelChirico).
  • .lintr config validation correctly accepts regular expressions which only compile under perl = TRUE (#2375, @MichaelChirico). These have always been valid (since rex::re_matches(), which powers the lint exclusion logic, also uses this setting), but the new up-front validation in v3.1.1 incorrectly used perl = FALSE.
  • .lintr configs set by option lintr.linter_file or environment variable R_LINTR_LINTER_FILE can point to subdirectories (#2512, @MichaelChirico).
  • indentation_linter() returns lints with ranges[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 like as.integer("a") (#2566, @MichaelChirico).

Changes to default linters

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 new xml_find_function_calls() entry in the get_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 argument linter_level= (default NA). This is used by lint() to more efficiently check for expression levels than the idiom if (!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= (default TRUE) which, when FALSE, checks that all terminal if statements are paired with a corresponding else statement (part of #884, @MichaelChirico).
    • return_functions= to customize which functions are equivalent to return() as "exit" clauses, e.g. rlang::abort() can be considered in addition to the default functions like stop() and q() 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 as except=, 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 new operator= 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 use operator = "=". This supersedes several old arguments: to accomplish allow_cascading_assign=TRUE, add "<<-" (and/or "->>") to operator=; for allow_right_assign=TRUE, add "->" (and/or "->>") to operator=; for allow_pipe_assign=TRUE, add "%<>%" to operator=. Use operator = "any" to denote "ignore all assignment operators"; in this case, only the value of allow_trailing= matters. Implicit assignments with <- are always ignored by assignment_linter(); use implicit_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), not library("symbol", character.only = TRUE) or "vectorized" approaches looping over package names (part of #884, @MichaelChirico).
    • to discourage many consecutive calls to suppressMessages() or suppressPackageStartupMessages() (part of #884, @MichaelChirico).
  • unnecessary_lambda_linter() is extended to encourage vectorized comparisons where possible, e.g. sapply(x, sum) > 0 instead of sapply(x, function(x) sum(x) > 0) (part of #884, @MichaelChirico). Toggle this behavior with argument allow_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 like grepl("^abc$", x) that can be replaced by using == instead (#1613, @MichaelChirico).
  • unreachable_code_linter() has an argument allow_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() and print() methods for lint and lints classes get a new option width= to control the printing width of lint messages (#1884, @MichaelChirico). The default is controlled by a new option lintr.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 to print() for clarity (#2257, @MichaelChirico).
  • todo_comment_linter() has a new argument except_regex= for setting valid TODO comments, e.g. for forcing TODO comments to be linked to GitHub issues like TODO(#154) (#2047, @MichaelChirico).
  • vector_logic_linter() is extended to recognize incorrect usage of scalar operators && and || inside subsetting expressions like dplyr::filter(x, A && B) (#2166, @MichaelChirico).
  • any_is_na_linter() is extended to catch the unusual usage NA %in% x (#2113, @MichaelChirico).
  • make_linter_from_xpath() errors up front when lint_message= is missing (instead of delaying this error until the linter is used, #2541, @MichaelChirico).
  • paste_linter() is extended to recommend using paste() instead of paste0() for simply aggregating a character vector with collapse=, i.e., when sep= 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 like expect_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 of call. in warning() and stop(). The default call. = FALSE follows the tidyverse guidance of not displaying the call (#2226, @Bisaloo)
  • sample_int_linter() for encouraging sample.int(n, ...) over equivalents like sample(1:n, ...) (part of #884, @MichaelChirico).
  • stopifnot_all_linter() discourages tests with all() like stopifnot(all(x > 0)); stopifnot() runs all() 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 be x != y (part of #884, @MichaelChirico).
  • nzchar_linter() for encouraging nzchar() to test for empty strings, e.g. nchar(x) > 0 can be nzchar(x) (part of #884, @MichaelChirico).
  • terminal_close_linter() for discouraging using close() to end functions (part of #884, @MichaelChirico). Such usages are not robust to errors, where close() will not be run as intended. Put close() in an on.exit() hook, or use {withr} to manage connections with proper cleanup.
  • rep_len_linter() for encouraging use of rep_len() directly instead of rep(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 discouraging which(grepl(ptn, x)) in favor of directly using grep(ptn, x) (part of #884, @MichaelChirico).
  • list_comparison_linter() for discouraging comparisons on the output of lapply(), e.g. lapply(x, sum) > 10 (part of #884, @MichaelChirico).
  • print_linter() for discouraging usage of print() on string literals like print("Reached here") or print(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 to dplyr::mutate() to be combined (part of #884, @MichaelChirico).
  • if_switch_linter() for encouraging switch() over repeated if/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 like nrow(subset(x, conditions)) in favor of something like with(x, sum(conditions)) which doesn't require a full subset of x (#2313, #2314 and part of #884, @MichaelChirico).
  • pipe_return_linter() for discouraging usage of return() inside a {magrittr} pipeline (part of #884, @MichaelChirico).
  • one_call_pipe_linter() for discouraging one-step pipelines like x |> 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() and object_length_linter() ignore {rlang} name injection like x |> mutate("{new_name}" := foo(col)) (#1926, @MichaelChirico). No checking is applied in such cases. {data.table} in-place assignments like DT[, "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=) and expect_lint(checks=).

New Contributors

Full Changelog: v3.1.1...v3.2.0