pipe_consistency_linter() is changed from "auto" (require one pipe style, either magrittr or native) to "|>" (R native pipe required) to coincide with the same change in the Tidyverse Style Guide (#2707, @MichaelChirico).lint() no longer picks up settings automatically in ad hoc invocations like lint("text\n") or lint(text = "str"). You should set parse_settings=TRUE to force settings to be read. Emacs ESS users may need to update to a recent version, e.g. ESS>20251003.allow_cascading_assign=, allow_right_assign=, and allow_pipe_assign= to assignment_linter() are now defunct.consecutive_stopifnot_linter(), extraction_operator_linter(), no_tab_linter(), single_quotes_linter(), unnecessary_nested_if_linter(), and unneeded_concatenation_linter(). They will be removed in the next release.source_file= argument to ids_with_token() and with_id()."linter"-classed functions.linter= argument of Lint().with_defaults().closed_curly_linter(), open_curly_linter(), paren_brace_linter(), and semicolon_terminator_linter().interpret_glue to object_usage_linter() is deprecated in favor of the more general interpret_extensions, in which "glue" is present by default (#1472, @MichaelChirico). See the description below under 'New and improved features'.Lint(), and thus all linters, require that the returned object's message attribute is consistently a simple character string (and not, for example, an object of class "glue"; #2740, @MichaelChirico). In general it is good to avoid slower string builders like glue() inside a loop (a linter might be run on every expression in your pakcage). Classed character strings return a warning in this release, which will be upgraded to an error subsequently.lint(parse_settings = TRUE) (#2803, @MichaelChirico). Thanks also to @bastistician for detecting a regression caused by the initial change for users of Emacs (#2847).assignment_linter() no longer errors if "%<>%" is an allowed operator (#2850, @AshesITR).expect_lint() conforms to {testthat} v3.3.0+ rules for custom expectations, namely that they produce either exactly one success or exactly one failure (#2937, @hadley).pipe_consistency_linter(), with its new default to enforce the native pipe |>, is now a default linter, since it corresponds directly to a rule in the Tidyverse Style Guide (#2707, @MichaelChirico).all_equal_linter() warns about incorrect use of all.equal() in if clauses or preceded by ! (#2885, @Bisaloo). Such usages should wrap all.equal() with isTRUE(), for example.download_file_linter() encourages the use of mode = "wb" (or mode = "ab") when using download.file(), rather than mode = "w" or mode = "a", as the latter can produce broken files in Windows (#2882, @Bisaloo).list2df_linter() encourages the use of the list2DF() function, or the data.frame() function when recycling is required, over the slower and less readable do.call(cbind.data.frame, ) alternative (#2834, @Bisaloo).coalesce_linter() encourages the use of the infix operator x %||% y, which is equivalent to if (is.null(x)) y else x (#2246, @MichaelChirico). While this has long been used in many tidyverse packages (it was added to {ggplot2} in 2008), it became part of every R installation from R 4.4.0. Thanks also to @emmanuel-ferdman for fixing a false positive before release.brace_linter() has a new argument function_bodies (default "multi_line") which controls when to require function bodies to be wrapped in curly braces, with the options "always", "multi_line" (only require curly braces when a function body spans multiple lines), "not_inline" (only require curly braces when a function body starts on a new line) and "never" (#1807, #2240, @salim-b).seq_linter():seq_along(x) instead of seq_len(length(x)) (#2577, @MichaelChirico).sequence() instead of unlist(lapply(ints, seq)) (#2618, @Bisaloo).undesirable_operator_linter():`%%`(x, 2) (#1910, @MichaelChirico). Disable this by setting call_is_undesirable=FALSE.undesirable_operator_linter("%%") (#2536, @MichaelChirico).undesirable_function_linter() accepts unnamed entries, treating them as undesirable functions, e.g. undesirable_function_linter("sum") (#2536, @MichaelChirico).indentation_linter() handles un-braced for loops correctly (#2564, @MichaelChirico).exclusions supports globs like knitr* to exclude files/directories with a pattern (#1554, @MichaelChirico).object_name_linter() and object_length_linter() apply to objects assigned with assign() or generics created with setGeneric() (#1665, @MichaelChirico).object_usage_linter() gains argument interpret_extensions to govern which false positive-prone common syntaxes should be checked for used objects (#1472, @MichaelChirico). Currently "glue" (renamed from earlier argument interpret_glue) and "rlang" are supported. The latter newly covers usage of the .env pronoun like .env$key, where key was previously missed as being a used variable.boolean_arithmetic_linter() finds many more cases like sum(x | y) == 0 where the total of a known-logical vector is compared to 0 (#1580, @MichaelChirico).any_duplicated_linter() is extended to recognize some usages from {dplyr} and {data.table} that could be replaced by anyDuplicated(), e.g. n_distinct(col) == n() or uniqueN(col) == .N (#2482, @MichaelChirico).fixed_regex_linter() recognizes usage of the new (R 4.5.0) grepv() wrapper of grep(); regex_subset_linter() also recommends grepv() alternatives (#2855, @MichaelChirico).object_usage_linter() lints missing packages that may cause false positives (#2872, @AshesITR)sprintf_linter() lints sprintf() and gettextf() calls when a constant string is passed to fmt (#2894, @Bisaloo).length_test_linter() is extended to check incorrect usage of nrow(), ncol(), NROW(), NCOL() (#2933, @mcol).implicit_assignment_linter() gains argument allow_paren_print to disable lints for the use of ( for auto-printing (#2962, @TimTaylor).line_length_linter() has a new argument ignore_string_bodies (defaulting to FALSE) which governs whether the contents of multi-line string bodies should be linted (#856, @MichaelChirico). We think the biggest use case for this is writing SQL in R strings, especially in cases where the recommended string width for SQL & R differ.package_hooks_linter() now validates .onUnload() hook signatures, requiring exactly one argument starting with 'lib' (#2940, @emmanuel-ferdman).unnecessary_nesting_linter():\()) the same as normal function bodies (#2748, @MichaelChirico).= assignment the same as <- when deciding to combine consecutive if() clauses (#2245, @MichaelChirico).string_boundary_linter() omits lints of patterns like \\^ which have an anchor but are not regular expressions (#2636, @MichaelChirico).implicit_integer_linter(allow_colon = TRUE) is OK with negative literals, e.g. -1:1 or 1:-1 (#2673, @MichaelChirico).missing_argument_linter() allows empty calls like foo() even if there are comments between ( and ) (#2741, @MichaelChirico).return_linter() works on functions that happen to use braced expressions in their formals (#2616, @MichaelChirico).object_name_linter() and object_length_linter() account for S3 class correctly when the generic is assigned with = (#2507, @MichaelChirico).assignment_linter() with operator = "=" does a better job of skipping implicit assignments, which are intended to be governed by implicit_assignment_linter() (#2765, @MichaelChirico).implicit_assignment_linter() with allow_scoped=TRUE doesn't lint for if (a <- 1) print(a) (#2913, @MichaelChirico).expect_true_false_linter() is pipe-aware, so that 42 |> expect_identical(x, ignore_attr = TRUE) no longer lints (#1520, @MichaelChirico).T_and_F_symbol_linter() ignores T and F:y ~ T + F), which can represent variables in data not controlled by the author (#2637, @MichaelChirico).[ or [[ (#2944, @mcol).todo_comment_linter() finds comments inside {roxygen2} markup comments (#2447, @MichaelChirico).\() (#2818, continuation of earlier #2190, @MichaelChirico).library_call_linter()terminal_close_linter()unnecessary_lambda_linter()@ extractions to match how similar $ extractions would be linted (#2820, @MichaelChirico).function_left_parentheses_linter()indentation_linter()library_call_linter()missing_argument_linter()condition_call_linter() no longer covers cases where the object type in the ellipsis cannot be determined with certainty (#2888, #2890, @Bisaloo). In particular, this fixes the known false positive of custom conditions created via errorCondition() or warningCondition() not being compatible with the call. argument in stop() or warning().get_source_expression() captures warnings emitted by the R parser (currently always for mis-specified literal integers like 1.1L) and lint() returns them as lints (#2065, @MichaelChirico).expect_lint() has a new argument ignore_order (default FALSE), which, if TRUE, allows the checks= to be provided in arbitary order vs. how lint() produces them (@MichaelChirico).gitlab_output() function to output lints to GitLab format (#2858, @lschneiderbauer).include_s4_slots for the xml_find_function_calls() entry in the get_source_expressions() to govern whether calls of the form s4Obj@fun() are included in the result (#2820, @MichaelChirico).use_lintr() adds the created .lintr file to the .Rbuildignore if run in a package (#2926, initial work by @MEO265, finalized by @Bisaloo).{lintr} now has an associated paper at the Journal of Open Source Software that you can use to cite the package if you use it in a paper - see citation("lintr") for details.expect_lint_free() and other functions that rely on the {testthat} framework now have a consistent error message. (#2585, @F-Noelle).unnecessary_nesting_linter() gives a more specific lint message identifying:if() statement that can be combined with the linted one (#1891, @MichaelChirico).?paste_linter of allow_file_path= has been corrected (#2675, @MichaelChirico). In particular, allow_file_path="never" is the most strict form, allow_file_path="always" is the most lax form.comment_token is removed from settings. This was a vestige of the now-defunct support for posting GitHub comments.source_file= argument to ids_with_token() and with_id()."linter"-classed functions.linter= argument of Lint().with_defaults().closed_curly_linter(), open_curly_linter(), paren_brace_linter(), and semicolon_terminator_linter().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.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().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.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.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).return_linter() for the style guide rule that terminal returns should be left implicit (#1100, #2343, #2354, and #2356, @MEO265 and @MichaelChirico).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).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.library_call_linter() is extendedlibrary(symbol), not library("symbol", character.only = TRUE) or "vectorized" approaches looping over package names (part of #884, @MichaelChirico).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)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).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).{lintr} now has a hex sticker (https://github.com/rstudio/hex-stickers/pull/110). Thank you, @gregswinehart!{cli} package (#2418 and #2641, @IndrajeetPatil). As noted above, all messages have been reviewed and updated to be more informative and consistent.unreachable_code_linter(allow_comment_regex=) and expect_lint(checks=).unreachable_code_linter() ignores reachable code in inline functions like function(x) if (x > 2) stop() else x (#2259, @MEO265).unnecessary_lambda_linter()lapply(l, function(x) foo(x)$bar) (#2258, @MichaelChirico).lapply(l, function(x) "a" %in% names(x)) (#2310, @MichaelChirico).vector_logic_linter() recognizes some cases where bitwise &/| are used correctly (#1453, @MichaelChirico).expect_comparison_linter() ignores faulty usage like expect_true(x, y > z) (#2083, @MichaelChirico). Note that y > z is being passed to the info= argument, so this is likely a mistake.consecutive_assertion_linter() ignores cases where a second assertion follows an intervening assignment with = (#2444, @MichaelChirico).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).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-supplied exclude_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.find_line() and find_column() entries from get_source_expressions() expression-level objects. These have been marked deprecated since version 3.0.0. No users were found on GitHub.?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.?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.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).assignment_linter() lints the {magrittr} assignment pipe %<>% (#2008, @MichaelChirico). This can be deactivated by setting the new argument allow_pipe_assign to TRUE.object_usage_linter():glue() is glue::glue() when interpret_glue=TRUE (#2032, @MichaelChirico).glue() calls to avoid false positives for "unused objects" (#2029 and #2069, @MichaelChirico).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 tags EQ_ASSIGN, EQ_SUB, and EQ_FORMALS (#1977, @MichaelChirico).equals_na_linter() checks for x %in% NA, which is a more convoluted form of is.na(x) (#2088, @MichaelChirico).# 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 the exclude_next config entry or R option "lintr.exclude_next".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.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.show_progress to lint_dir() and lint_package() (#972, @MichaelChirico). The default is still to show progress in interactive() 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(), and lint_package() fail more gracefully when the user mis-spells an argument name (#2134, @MichaelChirico).lint_dir() by default (#2150, @dave-lovell).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 be c(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 of x$"a b").length_levels_linter() for using the specific function nlevels() instead of checking length(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 encouraging if statements to be structured as if (A) x else y instead of if (!A) y else x (part of #884, @MichaelChirico).repeat_linter() for encouraging repeat for infinite loops instead of while (TRUE) (#2106, @MEO265).length_test_linter() detects the common mistake length(x == 0) which is meant to be length(x) == 0 (#1991, @MichaelChirico).fixed_regex_linter() gains an option allow_unescaped (default FALSE) to toggle linting regexes not requiring any escapes or character classes (#1689, @MichaelChirico). Thus fixed_regex_linter(allow_unescaped = TRUE) would lint on grepl("[$]", x) but not on grepl("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 like dplyr::filter(x, A & B) in favor of using dplyr::filter(x, A, B) (part of #884; #2110 and #2078, @salim-b and @MichaelChirico). Option allow_filter toggles when this applies. allow_filter = "always" drops such lints entirely, while "not_dplyr" only lints calls explicitly qualified as dplyr::filter(). The default, "never", assumes all unqualified calls to filter() are dplyr::filter().sort_linter() checks for code like x == sort(x) which is better served by using the function is.unsorted() (part of #884, @MichaelChirico).paste_linter() gains detection for file paths that are better constructed with file.path(), e.g. paste0(dir, "/", file) would be better as file.path(dir, file) (part of #884, #2082, @MichaelChirico). What exactly gets linted here can be fine-tuned with the allow_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 with file.path().seq_linter() recommends rev() in the lint message for lints like nrow(x):1 (#1542, @MichaelChirico).function_argument_linter() detects usage of missing() for the linted argument (#1546, @MichaelChirico). The simplest fix for function_argument_linter() lints is typically to set that argument to NULL by default, in which case it's usually preferable to update function logic checking missing() to check is.null() instead.commas_linter() gains an option allow_trailing (default FALSE) to allow trailing commas while indexing. (#2104, @MEO265)unreachable_code_linter()if (FALSE) and other conditional loops with deterministically false conditions (#1428, @ME0265).if, else, for, while, and repeat blocks, including combinations with break and next statements. (#2105, @ME0265).implicit_assignment_linter() gains an argument allow_lazy (default FALSE) that allows optionally skipping lazy assignments like A && (B <- foo(A)) (#2016, @MichaelChirico).unused_import_linter() gains an argument interpret_glue (default TRUE) paralleling that in object_usage_linter() to toggle whether glue::glue() expressions should be inspected for exported object usage (#2042, @MichaelChirico).default_undesirable_functions is updated to also include Sys.unsetenv() and structure() (#2192 and #2228, @IndrajeetPatil and @MichaelChirico).%>% consistently apply it to the other pipes %!>%, %T>%, %<>% (and possibly %$%) where appropriate (#2008, @MichaelChirico).brace_linter()pipe_call_linter()pipe_continuation_linter()unnecessary_concatenation_linter()unnecessary_placeholder_linter()\() (#2190, @MichaelChirico).brace_linter()function_left_parentheses_linter()indentation_linter()object_length_linter()object_name_linter()package_hooks_linter()paren_body_linter()unnecessary_lambda_linter()unreachable_code_linter()fixed_regex_linter()x |> str_replace(fixed("a"), "b") (#1811, @MichaelChirico).pattern= as a keyword argument (#2159, @MichaelChirico).$ extractions get the same exceptions for @ extractions, e.g. S4@T will no longer throw a T_and_F_symbol_linter() hit (#2039, @MichaelChirico).T_and_F_symbol_linter()for_loop_index_linter()literal_coercion_linter()object_name_linter()undesirable_function_linter()unreachable_code_linter()yoda_test_linter()sprintf_linter() is pipe-aware, so that x %>% sprintf(fmt = "%s") no longer lints (#1943, @MichaelChirico).condition_message_linter() ignores usages of extracted calls like env$stop(paste(a, b)) (#1455, @MichaelChirico).inner_combine_linter() no longer throws on length-1 calls to c() like c(exp(2)) or c(log(3)) (#2017, @MichaelChirico). Such usage is discouraged by unnecessary_concatenation_linter(), but inner_combine_linter() per se does not apply.sort_linter() only lints on order() of a single vector, excluding e.g. x[order(x, y)] and x[order(y, x)] (#2156, @MichaelChirico).redundant_ifelse_linter() is aware of dplyr::if_else()'s missing= argument, so that if_else(A, TRUE, FALSE, missing = FALSE) doesn't lint, but if_else(A, TRUE, FALSE, NA) does (#1941, @MichaelChirico). Note that dplyr::coalesce() or tidyr::replace_na() may still be preferable.unreachable_code_linter() finds unreachable code even in the presence of a comment or semicolon after return() or stop() (#2127, @MEO265).implicit_assignment_linter()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)..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 see vignette("lintr") for details on how configs are discovered (#1746, @tonyk7440 and @klmr).single_quotes_linter() is deprecated in favor of the more generalizable quotes_linter() (#1729, @MichaelChirico).unneeded_concatentation_linter() is deprecated in favor of unnecessary_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 of whitespace_linter() for naming consistency and future generalization (#1954, @MichaelChirico).available_linters() prioritizes tags over exclude_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 where linters_with_tags() implicitly assumed this behavior.lint(), lint_dir(), and lint_package() no longer accept certain arguments (cache= for lint(), relative_path= for the latter two) positionally. The warning() since 3.0.0 has been upgraded to an error.linters_with_tags() now includes the previously missing spaces around "and" when listing missing linters advertised by available_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 when allow_trailing = FALSE (#1701, @ashbaldry)
object_usage_linter()
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 (like rlang::`%||%`) (#1752, @IndrajeetPatil)
lint_package() correctly finds a package from within a subdir if the path points to anywhere within the package (#1759, @AshesITR)
Improved error behavior in Lint(), lint() and xml_nodes_to_lints() (#1427, #763, @AshesITR)
Lint() validates its inputs more thoroughly, preventing errors during print.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
$ (#1914, @MichaelChirico).for general usage warnings without location info (#1986 and #1917, @AshesITR)
function_left_parentheses_linter() produces a more specific lint (and no longer fails) when the opening parenthesis is on a different line than function or the call name (#1953, @MichaelChirico). Thanks also to @IndrajeetPatil and @lorenzwalthert for identifying a regression in the initial fix, #1963.
Set the default for the except argument in duplicate_argument_linter() to c("mutate", "transmute").
This allows sequential updates like x |> mutate(a = b + 1, a = log(a)) (#1345, @IndrajeetPatil).
object_usage_linter()
skip_with argument to skip code in with() expressions. To be consistent with
R CMD check, it defaults to TRUE (#941, #1458, @IndrajeetPatil).glue("{`x`}") correctly
determines x was used (#1619, @MichaelChirico)Detects problems inside R4.1.0+ lambda functions (\(...)) (#1933, @MichaelChirico)
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 of allow_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() and undesirable_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):
indentation_linter()quotes_linter()unnecessary_concatenation_linter()whitespace_linter()
lint_package() also looks for files in exec/ (#1950, @jmaspons).
New get_r_string() helper to get the R-equivalent value of a string, especially useful for R-4-style raw strings.
Previously an internal lintr 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 like lint_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 like paste(rep("*", 10L), collapse = "") that can be written more
concisely as strrep("*", 10L) (#1108, @MichaelChirico)
spaces_inside_linter() produces lints for spaces inside [[ (#1673, @IndrajeetPatil).
sprintf_linter() also applies to gettextf() (#1677, @MichaelChirico)
Documentation for all linters contains examples of code that does and does not produce lints (#1492, @IndrajeetPatil).
implicit_integer_linter() gains parameter allow_colon to skip lints on expressions like 1: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 on c(...) (i.e., passing ... in a function call) when allow_single_expression = FALSE (#1696, @MichaelChirico)
object_name_linter() gains parameter regexes to allow custom naming conventions (#822, #1421, @AshesITR)
literal_coercion_linter() reports a replacement in the lint message, e.g. code like as.integer(1) will
suggest using 1L instead, and code like as.numeric(NA) will suggest using NA_real_ instead (#1439, @MichaelChirico)
Added format() functions for lint and lints (#1784, @AshesITR)
all_linters() function provides an easy way to access all available linters (#1843, @IndrajeetPatil)
missing_argument_linter() allows missing arguments in quote() 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 to tags over exclude_tags in the case of overlap. In particular, this means that available_linters(tags = "deprecated") will work to return deprecated linters without needing to specify exclude_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)
matrix_apply_linter() recommends use of dedicated rowSums(), colSums(), colMeans(), rowMeans() over apply(., MARGIN, sum) or apply(., 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 be lapply(x, sum) and purrr::map(x, ~quantile(.x, 0.75, na.rm = TRUE))
can be purrr::map(x, quantile, 0.75, na.rm = TRUE). Naming probs = 0.75 can further improve readability (#1531, #1866, @MichaelChirico, @Bisaloo).
redundant_equals_linter() for redundant comparisons to TRUE or FALSE like is_treatment == TRUE (#1500, @MichaelChirico)
lengths_linter() for encouraging usage of lengths(x) instead of sapply(x, length) (and similar)
function_return_linter() for handling issues in function return() statements. Currently handles assignments within the return()
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 even all(x != y) (@MichaelChirico)
for_loop_index_linter() to prevent overwriting local variables in a for loop declared like for (x in x) { ... } (@MichaelChirico)
is_numeric_linter() for redundant checks equivalent to is.numeric(x) such as is.numeric(x) || is.integer(x) or
class(x) %in% c("numeric", "integer") (@MichaelChirico)
empty_assignment_linter() for identifying empty assignments like x = {} that are more clearly written as x = 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 the NAMESPACE; @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 nested if statements where a single
if 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 argument delimiter 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 of single_quotes_linter().
unnecessary_concatenation_linter() is simply unneeded_concatenation_linter(), renamed.
consecutive_assertion_linter() (f.k.a. consecutive_stopifnot_linter()) now lints for consecutive calls to assertthat::assert_that() (as long as the msg= argument is not used; #1604, @MichaelChirico).
whitespace_linter() is simply no_tab_linter(), renamed. In the future, we plan to extend it to work for different whitespace preferences.
{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} or theorem from {bookdown}, note that those engines must be registered
in {knitr} prior to running lint() in order for {lintr} to behave as expected, i.e., they should be
shown as part of knitr::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") or loadNamespace("bookdown"), respectively, will
register those packages' custom engines; since library() also runs loadNamespace(), running
library() 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).
lint() and Lint() gain S3 class "list" to assist with S3 dispatch (#1494, @MichaelChirico)As a corollary, we now register an as_tibble method for class lints, conditional on {tibble} availability, to avoid dispatching to the list method which does not work with lint() output (#1997, @MichaelChirico)
object_usage_linter() gives a more helpful warning when a glue() expression fails to evaluate (#1985, @MichaelChirico)
The documentation of object_name_linter() now describes how "symbols"
works when passed to the styles parameter (#1924, @hedsnz).
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 to Linter(). Linter() also includes "function" in the S3
class of its output to facilitate S3 dispatch to function methods where appropriate (#1392, @MichaelChirico).
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 for seq(...), since it also cannot properly
handle empty edge cases (#1468, @IndrajeetPatil).
seq_linter() additionally lints on 1:n() (from {dplyr})
and 1:.N (from {data.table}) (#1396, @IndrajeetPatil).
literal_coercion_linter() lints {rlang}'s atomic vector constructors
(i.e., int(), chr(), lgl(), and dbl()) if the argument is a scalar
(#1437, @IndrajeetPatil).
redundant_ifelse_linter()'s lint message correctly suggests negation when
the yes condition is 0 (#1432, @IndrajeetPatil).
seq_linter() provides more specific replacement code in lint message
(#1475, @IndrajeetPatil).
New sort_linter() to detect x[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 to assignment_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.
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)."error" instead of NA (#1405, @AshesITR).
Also, linting no longer runs if the source_expressions contain invalid string data that would cause error messages
in other linters.
in other linters.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 for trailing_blank_lines_linter() in RMarkdown documents without terminal
newlines.vignette("lintr") incorrectly cited exclude as the key for setting file exclusions in .lintr when it is
actually exclusions. (#1401, @AshesITR)lint_dir() so it no longer errors if there are multiple exclusions or no global
exclusions configured for a single file (#1413, #1442, @AshesITR).{withr} has been bumped to 2.5.0
(#1404, @IndrajeetPatil).with_defaults() to also mention modify_defaults() (#1438, @AshesITR)..qmd) were supported out of the box. The documentation and the
testing infrastructure are updated to reflect this (#1486, @IndrajeetPatil).This means that usage such as lint("file.R", seq_linter) should be updated to lint("file.R", seq_linter()), and
the following update for custom linters:
```r 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()* Removedreturn()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 supplying return as a custom undesirable function:
undesirable_function_linter(c(all_undesirable_functions, list(return = NA)))
linter that caused them instead of the name of their implementation
function. Deprecated the obsolete linter argument of Lint() (#664, #673, #746, @AshesITR). Downstream custom
linters should follow suit.semicolon_terminator_linter() to semicolon_linter() for better consistency.
semicolon_terminator_linter() survives but is marked for deprecation. The new linter also has a new signature,
taking arguments allow_compound and allow_trailing to replace the old single argument semicolon, again for
signature consistency with other linters.brace_linter() and are now deprecated; see the item on brace_linter()
below:closed_curly_linter()open_curly_linter()paren_brace_linter()... argument for lint(), lint_dir(), and lint_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 specify relative_path.
This affects some code that uses positional arguments (#935, @MichaelChirico).lint(), ... is now the 3rd argument, where earlier this was cache.lint_dir() and lint_package(), ... is now the 2nd argument, where earlier this was relative_path.source_file to exported functions with_id() and ids_with_token(). It has been renamed to
source_expression to better reflect that this argument is typically the output of get_source_expressions().
For now, the old argument source_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 imitating lintr style had also adopted the
source_file naming and want to adapt to keep in sync.with_defaults() in favor of linters_with_defaults(), and add modify_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 to with_defaults()'s default= is called defaults= (i.e., pluralized) in both of these, and that
usage like with_defaults(default = NULL, ...) should be converted to linters_with_defaults(defaults = list(), ...)
(#1029, #1336, #1361, @AshesITR and @michaelchirico).find_line() and find_column() helpers from the item-level expressions 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).default_lintersbrace_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 (use trailing_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 lints if/else and repeat with missing whitespace.brace_linter() also newly enforces the following rules surrounding curly braces (originally Google linters, see
below):else to come on the same line as the preceding }, if present (#884, @MichaelChirico).{} in if/else conditions, i.e., if the if branch uses braces,
then so must the else branch, and vice versa (#983, @MichaelChirico).paren_body_linter() checks that there is a space between a right parenthesis and a body expression (#809,
@kpagacz).semicolon_linter() as a default because it enforces a tidyverse style guide rule (#683, @AshesITR).assignment_linter() (#915, @MichaelChirico):-> and ->>).allow_cascading_assign (TRUE by default) toggles whether to lint <<- and ->>.allow_right_assign (FALSE by default) toggles whether to lint -> and ->>.commented_code_linter(): use the parse tree to find comments, eliminating some false positives (#451, @AshesITR).equals_na_linter() (#545, @MichaelChirico):x != NA (before, only == was caught) and NA == x (before, only NA on RHS was caught).is.na(x) # use is.na(x), not x == NA.function_left_parentheses_linter(): improved location information (#1266, #1267, @AshesITR).infix_spaces_linter():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).a~b and function(a=1) { ... } are linted (#930, #michaelchirico).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)./ usage in box::use() declarations (#1087, @klmr).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():my_method.upstream.class,
upstream.class no longer throws a lint because the generic (my_method)
properly uses snake_case (#737, @AshesITR)..onLoad() (#500, #614, @AshesITR and @MichaelChirico).names<-.class_name (#843, @jonkeane)."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.$ extractions (#582, @AshesITR).object_usage_linter():glue::glue() constructs (#942, @AshesITR).= instead of <- (#1081, @MichaelChirico).library() or
require() calls (#1127, @AshesITR).assign() and setMethod() (#1322, @AshesITR).spaces_inside_linter(): ignore spaces preceding trailing comments (#636, @MichaelChirico).T_and_F_symbol_linter():T and F when used for subsetting, and give a better
message when used as variable names (#657, @AshesITR).trailing_blank_lines_linter():lint() on a file without a terminal newline no longer throws a warning().trailing_whitespace_linter():allow_empty_lines (FALSE by default) to toggle this behavior.allow_in_strings (TRUE by default) to toggle this behavior.undesirable_function_linter():default_undesirable_functions related to debugging (#876, @MichaelChirico):browser()debug()debugcall()debugonce()trace()untrace()library() and require() calls attaching a package with an undesired name,
e.g. library(foo) (#814, @kpagacz and @MichaelChirico).$ extractions (#1050, @AshesITR).undesirable_operator_linter() (#1133, #1146, #1159, @AshesITR).cyclocomp_linter(): set the default complexity_limit to 15. This brings the default into sync with what
is enforced via default_linters (#693, @AshesITR).lint_package() now lints files in the demo directory by default (#703, @dmurdoch).~/.R/lintr_cache (which was a violation of
CRAN policy) to R_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).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 to library() and friends
are missing (#536, #1037, @renkun-ken and @MichaelChirico).namespace_linter() to check for common mistakes in pkg::symbol usages (#548, @renkun-ken).package_hooks_linter() to run a series of checks also done by R CMD check on the .onLoad(), .onAttach(),
.Last.lib() and .onDetach() hooks (#882, @MichaelChirico).pipe_call_linter() to enforce that all steps of magrittr pipelines use explicit calls instead of symbols,
e.g. x %>% mean() instead of x %>% mean (#801, @MichaelChirico).sprintf_linter() to check for common mistakes in sprintf() usage (#544, #624, @renkun-ken and @AshesITR).unused_import_linter() to detect unnecessary library() calls in R scripts (#239, @jimhester, @AshesITR).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.
any_duplicated_linter() Require usage of anyDuplicated(x) > 0L over any(duplicated(x)) and similar.any_is_na_linter() Require usage of anyNA(x) over any(is.na(x)).class_equals_linter() Prevent comparing class(x) with ==, !=, or %in%, where inherits() is typically
preferred.condition_message_linter() Prevent condition messages from being constructed like stop(paste(...))
(where just stop(...) is preferable).conjunct_test_linter() Require usage of expect_true(x); expect_true(y) over expect_true(x && y) and similar.consecutive_stopifnot_linter() Require consecutive calls to stopifnot() to be unified into one.expect_comparison_linter() Require usage of expect_gt(x, y) over expect_true(x > y) and similar.expect_identical_linter() Require usage of expect_identical() by default, and expect_equal() only by exception.expect_length_linter() Require usage of expect_length(x, n) over expect_equal(length(x), n) and similar.expect_named_linter() Require usage of expect_named(x, n) over expect_equal(names(x), n) and similar.expect_not_linter() Require usage of expect_false(x) over expect_true(!x), and vice versa.expect_null_linter() Require usage of expect_null(x) over expect_equal(x, NULL) and similar.expect_s3_class_linter() Require usage of expect_s3_class(x, k) over expect_equal(class(x), k) and similar.expect_s4_class_linter() Require usage of expect_s4_class(x, k) over expect_true(methods::is(x, k)).expect_true_false_linter() Require usage of expect_true(x) over expect_equal(x, TRUE) and similar.expect_type_linter() Require usage of expect_type(x, t) over expect_equal(typeof(x), t) and similar.fixed_regex_linter() Require fixed = TRUE or stringr::fixed() for regular expressions that can be
expressed statically, e.g. strsplit(x, "[.]") can be strsplit(x, ".", fixed = TRUE).allow_grepl (default FALSE) to toggle whether grepl() usages should be linted.
These might be treated separately because grepl("^x", NA) is FALSE; the startsWith() equivalent to
get FALSE for missing input is clunkier, but more explicit: !is.na(x) & startsWith(x, string) (#1376, @MichaelChirico).ifelse_censor_linter() Require usage of pmax() / 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)) over c(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 to ifelse() like ifelse(A, x, ifelse(B, y, z)), and similar.numeric_leading_zero_linter() Require a leading 0 in fractional numeric constants, e.g. 0.1 instead of .1.outer_negation_linter() Require usage of !any(x) over all(!x) and !all(x) over any(!x).paste_linter() lint for common mis-use of paste() and paste0():paste0() encouraged instead of paste(sep = "").toString() or glue::glue_collapse() encouraged instead of paste(x, collapse = ", ").sep= passed to paste0() -- typically a mistake.redundant_ifelse_linter() Prevent usage like ifelse(A & B, TRUE, FALSE) or ifelse(C, 0, 1)
(the latter is as.numeric(!C)).regex_subset_linter() Require usage of grep(ptn, x, value = TRUE) over x[grep(ptn, x)] and similar.string_boundary_linter() Require usage of startsWith(x, ptn) over grepl("^ptn", x) or substr(x, 1, 3) == ptn
and similar.strings_as_factors_linter() Check for code designed to work before and after the stringsAsFactors = FALSE default
change in R 4.0 by examining code for data.frame() usages susceptible to assumptions about the default value
of stringsAsFactors=.system_file_linter() Prevent usage like file.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 after return() and stop() 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 ||) inside if() conditions and similar.yoda_test_linter() Require usage of expect_identical(x, 1L) over expect_equal(1L, x) and similar.?linters also links to tag help pages, collecting linters with a similar goal.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.encoding setting of lintr (#752, #782, @AshesITR).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). Average lint_package() execution time is down about 30% and the median package sees about 40%
improvement.R"( a\string )"
(#1034, #1285, @MichaelChirico and @AshesITR).# 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 exclude infix_spaces_linter (#605, #872, @AshesITR).UseMethod() is called after several
preceding expressions (#846, @jonkeane).extraction_operator_linter(): no longer lint x[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 the renv and packrat directories by default (#697, @AshesITR).lint(): new optional argument text 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():%>% or |>; #573, #1270, @michaelquinn32 and @AshesITR).allow_single_expression, default TRUE, toggling whether c(x) should be linted, i.e.,
a call to c() 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)) or c(matrix(1:10, 5, 2)). In this last case, c() can (and should) in most cases
be replaced by as.vector() or as.integer() for readability. In fact, we suspect it is always
preferable to do so, and may change the default to allow_single_expression = FALSE in the future. Please
report your use case if as.vector() does not suit your needs (#1344, @MichaelChirico).use_lintr(): new exported helper for creating a minimal .lintr configuration (#902, @AshesITR).xml_nodes_to_lints(): new exported helper for converting xml_node objects obtained using linter logic
expressed in XPath into Lint objects (#1124, #1216, #1234, @MichaelChirico and @AshesITR).get_source_expressions():xmlparsedata::xml_parse_data() (#559, @renkun-ken).getParseData() returns a truncated (invalid) Unicode character as parsed text (#815,
@leogama).text value for STR_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).line_length_linter(): fix a bug causing duplicate lints for lines containing multiple expressions (#681, @AshesITR).lint_package():NULL if no package is found (instead of giving a peculiar error message; #776,
@MichaelChirico).DESCRIPTION are ignored (#702,
@MichaelChirico).linters_with_defaults() (formerly with_defaults()):lintr_function class when it is already present (#511, @AshesITR).NULL but its name is not in defaults (#1049, @AshesITR).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 "In parent == parent[before_operator_idx]
longer object length is not a multiple of shorter object length" in nested expressions (#654, @AshesITR).test-package - that fails on warnings emitted by tests
(#1263, #1272, @AshesITR).lint-changed-files - for newly written / modified code
(#641, @dragosmg).Imported packages have become Suggested dependencies: httr, testthat, and rstudioapi.
This should allow snappier CI builds for usages not relying on some more "peripheral" features of the package.R CMD check. Code coverage and
linting are implemented using separate GitHub Actions workflows (#572, @dragosmg).lintr now uses the 3rd edition of testthat (@MichaelChirico, @AshesITR, #910, #967).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).single_quote_linter() no longer causes a print issue when open quote
appears at a column > than close quote (#457, @jamieRowen)absolute_path_linter() and nonportable_path_linter() now handle
file-paths that are wrapped with double-quotes (#433, #437, @russHyde).get_source_expressions() has been changed to handle expr_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 is a major release, and incorporates development changes since the last major release (1.0.0) in 2016-04-16.
camel_case_linter(), snake_case_linter() and multiple_dots_linter()
in favor of object_name_linter() which enforce the given style: snake_case,
dotted.case, lowerCamelCalse, UpperCamelCase, alllowercase or ALLUPPERCASE
(#59, @fangly).absolute_path_linter(),
with a lax mode for fewer false positive lints (#199, @fangly).cyclocomp_linter() identifies overly complex functions (#361, @fabian-s)equals_na_linter() (#143, #326, @jabranham)extraction_operator_linter() checks that the [[ operator is used when
extracting a single element from an object, not [ (subsetting) nor $
(interactive use) (@fangly).function_left_parentheses_linter() to check that there is no space between
a function name and its left parentheses (#204, @jrnold).implicit_integer_linter() detects round numbers not declared as integers,
i.e. 1 instead of 1L (@fangly).nonportable_path_linter() identifies paths constructed without file.path()
(@fangly).paren_brace_linter() checks that there is a space between right
parenthesis and an opening curly brace (@bfgray3, #242).pipe_continuation_linter() to ensure there is a space before %>% and newline afterwards (#216).semicolon_terminator_linter() reports semicolons at the end of a line (#147,
@gaborcsardi) and between expressions (#181, @fangly).seq_linter(), finds 1:length(...) (and similar) expressions (#155, @gaborcsardi)todo_comment_linter() lints TODOs (@fangly).T_and_F_symbol_linter() warns when using T and F instead of TRUE and FALSE
(@fangly).undesirable_operator_linter() and undesirable_function_linter() lint uses of
user-specified functions and operators (#48, #149, @fangly).unneeded_concatenation_linter() lints uses of c() with a constant or no
arguments (@fangly).expect_lint() (#178, #210)ids_with_token() and with_id() (#297 @stufield)lint_dir() function to lint files under a given directory (@arekbee, #360)summary.lints() function to summarize the linter results (#260, #262, @wlandau).checkstyle_output() function to output lints to checkstyle XML output (#156, @joshkgold)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() argument length 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 respects utils::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() and closed_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)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 excludes R/RcppExports.R by default (#282)lint_package() now removes fully excluded files as soon as possibleseq_linter is now one of the default linters (#316).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)GITHUB_TOKEN environment
variable (#63, @mattyb)# to start a comment. Useful in ESS (#299, @prosoitos)engine format were parsed from R-markdown (#322, @russHyde)lintr runs / installs / tests on R-3.6: pinned to github
xmlparsedata; ensure vectors are length-1 when compared using && and ||
(#363 #377 #384 #391, @russHyde).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.= in named arguments. (#130, @saurfang).( type (#128, @saurfang)names.lints, split.lints (#49, @ttriche).lintr config files.globalenv() instead of baseenv() for default parent environment so
that methods will be included.trailing_whitespace_linter was reporting the incorrect line number# nolint comments are respected with caching (#68, @krlmlr)Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.