diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index cc8ffb62f..91711d445 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -39,7 +39,7 @@ jobs: - name: Deploy to GitHub pages 🚀 if: github.event_name != 'pull_request' - uses: JamesIves/github-pages-deploy-action@v4.7.1 + uses: JamesIves/github-pages-deploy-action@v4.7.2 with: clean: false branch: gh-pages diff --git a/DESCRIPTION b/DESCRIPTION index 9f6dd32b5..c1768ba04 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -23,7 +23,7 @@ BugReports: https://github.com/r-lib/lintr/issues Depends: R (>= 4.0) Imports: - backports (>= 1.1.7), + backports (>= 1.4.0), cli (>= 3.4.0), codetools, digest, diff --git a/NAMESPACE b/NAMESPACE index 3da94d313..d7089a304 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,7 @@ S3method("[",lints) S3method(as.data.frame,lints) +S3method(data.table::as.data.table,lints) S3method(format,lint) S3method(format,lints) S3method(names,lints) @@ -9,6 +10,7 @@ S3method(print,lint) S3method(print,lints) S3method(split,lints) S3method(summary,lints) +S3method(tibble::as_tibble,lints) export(Lint) export(Linter) export(T_and_F_symbol_linter) @@ -181,13 +183,10 @@ importFrom(stats,na.omit) importFrom(tools,R_user_dir) importFrom(utils,capture.output) importFrom(utils,getParseData) -importFrom(utils,getTxtProgressBar) importFrom(utils,globalVariables) importFrom(utils,head) importFrom(utils,relist) -importFrom(utils,setTxtProgressBar) importFrom(utils,tail) -importFrom(utils,txtProgressBar) importFrom(xml2,as_list) importFrom(xml2,xml_attr) importFrom(xml2,xml_children) diff --git a/NEWS.md b/NEWS.md index 93c517465..688c18fab 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,7 +6,6 @@ + `source_file=` argument to `ids_with_token()` and `with_id()`. + Passing linters by name or as non-`"linter"`-classed functions. + `linter=` argument of `Lint()`. - + Linters `closed_curly_linter()`, `open_curly_linter()`, `paren_brace_linter()`, and `semicolon_terminator_linter()`.. + `with_defaults()`. + Linters `closed_curly_linter()`, `open_curly_linter()`, `paren_brace_linter()`, and `semicolon_terminator_linter()`. + Helper `with_defaults()`. @@ -19,7 +18,6 @@ * `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. - ## Bug fixes @@ -62,6 +60,7 @@ * `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). +* `{lintr}` now has a hex sticker (https://github.com/rstudio/hex-stickers/pull/110). Thank you, @gregswinehart! ### New linters @@ -91,7 +90,7 @@ ## Notes -* All user-facing messages are now prepared using the `{cli}` package (#2418, @IndrajeetPatil). All messages have been reviewed and updated to be more informative and consistent. +* All user-facing messages (including progress bars) are now prepared using the `{cli}` package (#2418 and #2641, @IndrajeetPatil). 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=)`. diff --git a/R/indentation_linter.R b/R/indentation_linter.R index a89f3da7c..de4a7c777 100644 --- a/R/indentation_linter.R +++ b/R/indentation_linter.R @@ -257,9 +257,12 @@ indentation_linter <- function(indent = 2L, hanging_indent_style = c("tidy", "al } # Only lint non-empty lines if the indentation level doesn't match. + # TODO: remove styler ignore directives once tidyverse/style/issues/197 is resolved + # styler: off bad_lines <- which(indent_levels != expected_indent_levels & nzchar(trimws(source_expression$file_lines)) & !in_str_const) + # styler: on if (length(bad_lines) > 0L) { # Suppress consecutive lints with the same indentation difference, to not generate an excessive number of lints is_consecutive_lint <- c(FALSE, diff(bad_lines) == 1L) diff --git a/R/lint.R b/R/lint.R index 1961acb05..36faf36de 100644 --- a/R/lint.R +++ b/R/lint.R @@ -27,11 +27,14 @@ #' @return An object of class `c("lints", "list")`, each element of which is a `"list"` object. #' #' @examples +#' # linting inline-code +#' lint("a = 123\n") +#' lint(text = "a = 123") +#' +#' # linting a file #' f <- tempfile() #' writeLines("a=1", f) -#' lint(f) # linting a file -#' lint("a = 123\n") # linting inline-code -#' lint(text = "a = 123") # linting inline-code +#' lint(f) #' unlink(f) #' #' @export @@ -182,20 +185,24 @@ lint_dir <- function(path = ".", ..., return(lints) } - pb <- if (isTRUE(show_progress)) { - txtProgressBar(max = length(files), style = 3L) + if (isTRUE(show_progress)) { + lints <- lapply( + # NB: This cli API is experimental (https://github.com/r-lib/cli/issues/709) + cli::cli_progress_along(files, name = "Running linters"), + function(idx) { + lint(files[idx], ..., parse_settings = FALSE, exclusions = exclusions) + } + ) + } else { + lints <- lapply( + files, + function(file) { # nolint: unnecessary_lambda_linter. + lint(file, ..., parse_settings = FALSE, exclusions = exclusions) + } + ) } - lints <- flatten_lints(lapply( - files, - function(file) { - maybe_report_progress(pb) - lint(file, ..., parse_settings = FALSE, exclusions = exclusions) - } - )) - - if (!is.null(pb)) close(pb) - + lints <- flatten_lints(lints) lints <- reorder_lints(lints) if (relative_path) { @@ -688,13 +695,6 @@ has_positional_logical <- function(dots) { !nzchar(names2(dots)[1L]) } -maybe_report_progress <- function(pb) { - if (is.null(pb)) { - return(invisible()) - } - setTxtProgressBar(pb, getTxtProgressBar(pb) + 1L) -} - maybe_append_error_lint <- function(lints, error, lint_cache, filename) { if (is_lint(error)) { error$linter <- "error" diff --git a/R/lintr-package.R b/R/lintr-package.R index d453bcab1..0e7f558b4 100644 --- a/R/lintr-package.R +++ b/R/lintr-package.R @@ -13,8 +13,7 @@ #' @importFrom rex rex regex re_matches re_substitutes character_class #' @importFrom stats complete.cases na.omit #' @importFrom tools R_user_dir -#' @importFrom utils capture.output getParseData getTxtProgressBar globalVariables head relist -#' setTxtProgressBar tail txtProgressBar +#' @importFrom utils capture.output getParseData globalVariables head relist tail #' @importFrom xml2 as_list #' xml_attr xml_children xml_find_all xml_find_chr xml_find_lgl xml_find_num xml_find_first xml_name xml_text ## lintr namespace: end diff --git a/R/methods.R b/R/methods.R index 7a8fe3cc1..211222a58 100644 --- a/R/methods.R +++ b/R/methods.R @@ -173,6 +173,7 @@ as.data.frame.lints <- function(x, row.names = NULL, optional = FALSE, ...) { # ) } +#' @exportS3Method tibble::as_tibble as_tibble.lints <- function(x, ..., # nolint: object_name_linter. .rows = NULL, .name_repair = c("check_unique", "unique", "universal", "minimal"), @@ -181,6 +182,7 @@ as_tibble.lints <- function(x, ..., # nolint: object_name_linter. tibble::as_tibble(as.data.frame(x), ..., .rows = .rows, .name_repair = .name_repair, rownames = rownames) } +#' @exportS3Method data.table::as.data.table as.data.table.lints <- function(x, keep.rownames = FALSE, ...) { # nolint: object_name_linter. stopifnot(requireNamespace("data.table", quietly = TRUE)) data.table::setDT(as.data.frame(x), keep.rownames = keep.rownames, ...) diff --git a/R/nested_pipe_linter.R b/R/nested_pipe_linter.R index fd595b233..adc0d5089 100644 --- a/R/nested_pipe_linter.R +++ b/R/nested_pipe_linter.R @@ -51,8 +51,9 @@ #' @seealso [linters] for a complete list of linters available in lintr. #' @export nested_pipe_linter <- function( - allow_inline = TRUE, - allow_outer_calls = c("try", "tryCatch", "withCallingHandlers")) { + allow_inline = TRUE, + allow_outer_calls = c("try", "tryCatch", "withCallingHandlers") +) { multiline_and <- if (allow_inline) "@line1 != @line2 and" else "" xpath <- glue(" (//PIPE | //SPECIAL[{ xp_text_in_table(magrittr_pipes) }]) diff --git a/R/object_overwrite_linter.R b/R/object_overwrite_linter.R index 099129ed5..acee4c2ea 100644 --- a/R/object_overwrite_linter.R +++ b/R/object_overwrite_linter.R @@ -52,8 +52,9 @@ #' - #' @export object_overwrite_linter <- function( - packages = c("base", "stats", "utils", "tools", "methods", "graphics", "grDevices"), - allow_names = character()) { + packages = c("base", "stats", "utils", "tools", "methods", "graphics", "grDevices"), + allow_names = character() +) { for (package in packages) { if (!requireNamespace(package, quietly = TRUE)) { cli_abort("Package {.pkg {package}} is required, but not available.") diff --git a/R/return_linter.R b/R/return_linter.R index d0bc75d91..bb2383b2d 100644 --- a/R/return_linter.R +++ b/R/return_linter.R @@ -72,11 +72,12 @@ #' - #' @export return_linter <- function( - return_style = c("implicit", "explicit"), - allow_implicit_else = TRUE, - return_functions = NULL, - except = NULL, - except_regex = NULL) { + return_style = c("implicit", "explicit"), + allow_implicit_else = TRUE, + return_functions = NULL, + except = NULL, + except_regex = NULL +) { return_style <- match.arg(return_style) check_except <- !allow_implicit_else || return_style == "explicit" diff --git a/R/unnecessary_nesting_linter.R b/R/unnecessary_nesting_linter.R index 653406881..b198326bf 100644 --- a/R/unnecessary_nesting_linter.R +++ b/R/unnecessary_nesting_linter.R @@ -95,16 +95,17 @@ #' - [linters] for a complete list of linters available in lintr. #' @export unnecessary_nesting_linter <- function( - allow_assignment = TRUE, - allow_functions = c( - "switch", - "try", "tryCatch", "withCallingHandlers", - "quote", "expression", "bquote", "substitute", - "with_parameters_test_that", - "reactive", "observe", "observeEvent", - "renderCachedPlot", "renderDataTable", "renderImage", "renderPlot", - "renderPrint", "renderTable", "renderText", "renderUI" - )) { + allow_assignment = TRUE, + allow_functions = c( + "switch", + "try", "tryCatch", "withCallingHandlers", + "quote", "expression", "bquote", "substitute", + "with_parameters_test_that", + "reactive", "observe", "observeEvent", + "renderCachedPlot", "renderDataTable", "renderImage", "renderPlot", + "renderPrint", "renderTable", "renderText", "renderUI" + ) +) { exit_calls <- c("stop", "return", "abort", "quit", "q") exit_call_expr <- glue(" expr[SYMBOL_FUNCTION_CALL[{xp_text_in_table(exit_calls)}]] diff --git a/R/utils.R b/R/utils.R index 0820c9766..1cdfca15c 100644 --- a/R/utils.R +++ b/R/utils.R @@ -186,8 +186,7 @@ read_lines <- function(file, encoding = settings$encoding, ...) { # nocov start # support for usethis::use_release_issue(). Make sure to use devtools::load_all() beforehand! -release_bullets <- function() { -} +release_bullets <- function() {} # nocov end # see issue #923, PR #2455 -- some locales ignore _ when running sort(), others don't. diff --git a/R/zzz.R b/R/zzz.R index e281353db..5fd5dd74e 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -329,12 +329,5 @@ settings <- new.env(parent = emptyenv()) )) reset_settings() - - if (requireNamespace("tibble", quietly = TRUE)) { - registerS3method("as_tibble", "lints", as_tibble.lints, asNamespace("tibble")) - } - if (requireNamespace("data.table", quietly = TRUE)) { - registerS3method("as.data.table", "lints", as.data.table.lints, asNamespace("data.table")) - } } # nocov end diff --git a/README.md b/README.md index 14bafb124..dc7c993d5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# lintr +# lintr [![R build status](https://github.com/r-lib/lintr/workflows/R-CMD-check/badge.svg)](https://github.com/r-lib/lintr/actions) [![codecov.io](https://codecov.io/gh/r-lib/lintr/branch/main/graphs/badge.svg)](https://app.codecov.io/gh/r-lib/lintr?branch=main) diff --git a/man/figures/logo.png b/man/figures/logo.png new file mode 100644 index 000000000..7d9602ab9 Binary files /dev/null and b/man/figures/logo.png differ diff --git a/man/lint.Rd b/man/lint.Rd index a165a9343..da16dafea 100644 --- a/man/lint.Rd +++ b/man/lint.Rd @@ -89,11 +89,14 @@ Note that if files contain unparseable encoding problems, only the encoding prob unintelligible error messages from other linters. } \examples{ +# linting inline-code +lint("a = 123\n") +lint(text = "a = 123") + +# linting a file f <- tempfile() writeLines("a=1", f) -lint(f) # linting a file -lint("a = 123\n") # linting inline-code -lint(text = "a = 123") # linting inline-code +lint(f) unlink(f) if (FALSE) { diff --git a/_pkgdown.yaml b/pkgdown/_pkgdown.yaml similarity index 100% rename from _pkgdown.yaml rename to pkgdown/_pkgdown.yaml diff --git a/pkgdown/favicon/apple-touch-icon.png b/pkgdown/favicon/apple-touch-icon.png new file mode 100644 index 000000000..de8ec0236 Binary files /dev/null and b/pkgdown/favicon/apple-touch-icon.png differ diff --git a/pkgdown/favicon/favicon-96x96.png b/pkgdown/favicon/favicon-96x96.png new file mode 100644 index 000000000..e01781803 Binary files /dev/null and b/pkgdown/favicon/favicon-96x96.png differ diff --git a/pkgdown/favicon/favicon.ico b/pkgdown/favicon/favicon.ico new file mode 100644 index 000000000..d4b5a379b Binary files /dev/null and b/pkgdown/favicon/favicon.ico differ diff --git a/pkgdown/favicon/favicon.svg b/pkgdown/favicon/favicon.svg new file mode 100644 index 000000000..a02d2dcae --- /dev/null +++ b/pkgdown/favicon/favicon.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/pkgdown/favicon/site.webmanifest b/pkgdown/favicon/site.webmanifest new file mode 100644 index 000000000..4ebda26b5 --- /dev/null +++ b/pkgdown/favicon/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/pkgdown/favicon/web-app-manifest-192x192.png b/pkgdown/favicon/web-app-manifest-192x192.png new file mode 100644 index 000000000..8fef3b1d4 Binary files /dev/null and b/pkgdown/favicon/web-app-manifest-192x192.png differ diff --git a/pkgdown/favicon/web-app-manifest-512x512.png b/pkgdown/favicon/web-app-manifest-512x512.png new file mode 100644 index 000000000..558dc0f77 Binary files /dev/null and b/pkgdown/favicon/web-app-manifest-512x512.png differ diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index b66210eb6..d504070e1 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -62,6 +62,16 @@ skip_if_not_utf8_locale <- function() { testthat::skip_if_not(l10n_info()[["UTF-8"]], "Not a UTF-8 locale") } +safe_load_help_db <- function() { + help_db <- tryCatch(tools::Rd_db("lintr"), error = function(e) NULL) + # e.g. in dev under pkgload::load_all() + if (length(help_db) == 0L) { + help_db <- tryCatch(tools::Rd_db(dir = testthat::test_path("..", "..")), error = function(e) NULL) + testthat::skip_if_not(length(help_db) > 0L, message = "Package help corrupted or not installed") + } + help_db +} + pipes <- function(exclude = NULL) { if (getRversion() < "4.1.0") exclude <- unique(c(exclude, "|>")) all_pipes <- c( diff --git a/tests/testthat/test-fixed_regex_linter.R b/tests/testthat/test-fixed_regex_linter.R index 98a279a02..83a00c141 100644 --- a/tests/testthat/test-fixed_regex_linter.R +++ b/tests/testthat/test-fixed_regex_linter.R @@ -42,7 +42,7 @@ test_that("fixed_regex_linter blocks simple disallowed usages", { }) patrick::with_parameters_test_that( - "fixed_regex_linter is robust to unrecognized escapes error", + "fixed_regex_linter is robust to unrecognized escapes error for {char}", { expect_lint( sprintf(R"{grep('\\%s', x)}", char), @@ -56,14 +56,11 @@ patrick::with_parameters_test_that( fixed_regex_linter() ) }, - .cases = local({ - char <- c( - "^", "$", "{", "}", "(", ")", ".", "*", "+", "?", - "|", "[", "]", R"(\\)", "<", ">", "=", ":", ";", "/", - "_", "-", "!", "@", "#", "%", "&", "~" - ) - data.frame(char = char, .test_name = char) - }) + char = c( + "^", "$", "{", "}", "(", ")", ".", "*", "+", "?", + "|", "[", "]", R"(\\)", "<", ">", "=", ":", ";", "/", + "_", "-", "!", "@", "#", "%", "&", "~" + ) ) test_that("fixed_regex_linter catches regex like [.] or [$]", { @@ -326,7 +323,7 @@ local({ skip_cases <- character() } patrick::with_parameters_test_that( - "fixed replacements are correct", + "fixed replacements of {regex_expr} with {fixed_expr} is correct", { # TODO(google/patrick#19): handle this more cleanly by skipping up-front skip_if( diff --git a/tests/testthat/test-lint_dir.R b/tests/testthat/test-lint_dir.R index 6da4b81bb..da6dd824c 100644 --- a/tests/testthat/test-lint_dir.R +++ b/tests/testthat/test-lint_dir.R @@ -10,12 +10,6 @@ test_that("lint all files in a directory", { expect_s3_class(lints, "lints") expect_identical(sort(linted_files), sort(files)) - - expect_output( - lint_dir(the_dir, parse_settings = FALSE, show_progress = TRUE), - "======", - fixed = TRUE - ) }) test_that("lint all relevant directories in a package", { diff --git a/tests/testthat/test-linter_tags.R b/tests/testthat/test-linter_tags.R index 92ee49f80..800831fef 100644 --- a/tests/testthat/test-linter_tags.R +++ b/tests/testthat/test-linter_tags.R @@ -103,12 +103,7 @@ test_that("rownames for available_linters data frame doesn't have missing entrie # See the roxygen helpers in R/linter_tags.R for the code used to generate the docs. # This test helps ensure the documentation is up to date with the available_linters() database test_that("lintr help files are up to date", { - help_db <- tools::Rd_db("lintr") - # e.g. in dev under pkgload::load_all() - if (length(help_db) == 0L) { - help_db <- tools::Rd_db(dir = test_path("..", "..")) - skip_if_not(length(help_db) > 0L, message = "Package help not installed or corrupted") - } + help_db <- safe_load_help_db() lintr_db <- available_linters(exclude_tags = NULL) lintr_db$package <- NULL diff --git a/tests/testthat/test-lintr-package.R b/tests/testthat/test-lintr-package.R index e7be3c484..80d668b69 100644 --- a/tests/testthat/test-lintr-package.R +++ b/tests/testthat/test-lintr-package.R @@ -1,5 +1,5 @@ test_that("All linter help files have examples", { - help_db <- tools::Rd_db("lintr") + help_db <- safe_load_help_db() linter_db <- help_db[endsWith(names(help_db), "_linter.Rd")] rd_has_examples <- function(rd) any(vapply(rd, attr, "Rd_tag", FUN.VALUE = character(1L)) == "\\examples") linter_has_examples <- vapply(linter_db, rd_has_examples, logical(1L)) diff --git a/tests/testthat/test-pipe_continuation_linter.R b/tests/testthat/test-pipe_continuation_linter.R index 528f0c011..0d3350bca 100644 --- a/tests/testthat/test-pipe_continuation_linter.R +++ b/tests/testthat/test-pipe_continuation_linter.R @@ -138,46 +138,42 @@ test_that("pipe-continuation linter handles native pipe", { local({ linter <- pipe_continuation_linter() - valid_code <- c( - # all on one line + .cases <- tibble::tribble( + ~code_string, ~.test_name, trim_some(" my_fun <- function() { a %>% b() } - "), + "), "two on one line", trim_some(" my_fun <- function() { a %>% b() %>% c() } - "), + "), "three on one line", trim_some(" with( diamonds, x %>% head(10) %>% tail(5) ) - "), + "), "three inside with()", trim_some(" test_that('blah', { test_data <- diamonds %>% head(10) %>% tail(5) }) - "), - - # two different single-line pipelines + "), "three inside test_that()", trim_some(" { x <- a %>% b %>% c y <- c %>% b %>% a } - "), - - # at most one pipe-character per line + "), "two different single-line pipelines", trim_some(" my_fun <- function() { a %>% b() %>% c() } - ") + "), "at most one pipe-character per line" ) patrick::with_parameters_test_that( "valid nesting is handled", @@ -185,8 +181,7 @@ local({ { expect_lint(code_string, NULL, linter) }, - .test_name = valid_code, - code_string = valid_code + .cases = .cases ) })