diff --git a/API b/API index 42c0c9796..c004261d6 100644 --- a/API +++ b/API @@ -15,7 +15,7 @@ precommit_docopt(doc, args = commandArgs(trailingOnly = TRUE), ...) robust_purl(path) roxygen_assert_additional_dependencies() roxygenize_with_cache(key, dirs) -snippet_generate(snippet, open = rstudioapi::isAvailable(), root = here::here()) +snippet_generate(snippet = "additional-deps-roxygenize", open = rstudioapi::isAvailable(), root = here::here()) uninstall_precommit(scope = "repo", ask = "user", root = here::here()) update_precommit() use_ci(ci = getOption("precommit.ci", "native"), force = FALSE, open = rstudioapi::isAvailable(), root = here::here()) diff --git a/R/setup.R b/R/setup.R index 80b7779d6..5b57833ed 100644 --- a/R/setup.R +++ b/R/setup.R @@ -237,71 +237,79 @@ upstream_repo_url_is_outdated <- function() { #' * additional-deps-roxygenize: Code to paste into #' `.pre-commit-config.yaml` for the additional dependencies required by #' the roxygenize hook. +#' * additional-deps-lintr: Code to paste into +#' `.pre-commit-config.yaml` for the additional dependencies required by +#' the lintr hook if you use `--load-package`. #' @param snippet Name of the snippet. #' @param open Whether or not to open the .pre-commit-config.yaml. The default #' is `TRUE` when working in RStudio. Otherwise, we recommend manually opening #' the file. #' @inheritParams fallback_doc #' @export -snippet_generate <- function(snippet = "", +snippet_generate <- function(snippet = "additional-deps-roxygenize", open = rstudioapi::isAvailable(), root = here::here()) { - rlang::arg_match(snippet, c("additional-deps-roxygenize")) - if (snippet == "additional-deps-roxygenize") { - rlang::inform(paste( - "Generating snippet using CRAN versions. If you need another source,", - "specify with syntax that `renv::install()` understands (see examples in", - "help file).", - "\n" - )) - deps <- desc::desc_get_deps() - hard_dependencies <- deps[(deps$type %in% c("Depends", "Imports")), "package"] %>% - setdiff("R") - if (length(hard_dependencies) < 1) { - cli::cli_alert_success(paste0( - "According to {.code DESCRIPTION}`, there are no hard dependencies of ", - "your package. You are set." - )) - return() - } - hard_dependencies %>% - snippet_generate_impl_additional_deps_roxygenize() %>% - cat(sep = "") - cat("\n") - cli::cli_ul(paste0( - "Replace the `id: roxygenize` key in `.pre-commit-config.yaml` with the ", - "above code." - )) - cli::cli_alert_info(paste0( - "Note that CI services like {.url pre-commit.ci} have build-time ", - "restrictions and installing the above dependencies may exceed those, ", - "resulting in a timeout. In addition, system dependencies are not ", - "supported for {.url pre-commit.ci}. See ", - '{.code vignette("ci", package = "precommit")} for details and solutions.' + snippet_generator <- if (snippet == "additional-deps-roxygenize") { + snippet_generate_impl_additional_deps_roxygenize + } else if (snippet == "additional-deps-lintr") { + snippet_generate_impl_additional_deps_lintr + } else { + rlang::abort(paste0('Snippet "', snippet, '" not supported')) + } + rlang::inform(paste( + "Generating snippet using CRAN versions. If you need another source,", + "specify with syntax that `renv::install()` understands (see examples in", + "help file).", + "\n" + )) + deps <- desc::desc_get_deps() + hard_dependencies <- deps[(deps$type %in% c("Depends", "Imports")), "package"] %>% + setdiff("R") + if (length(hard_dependencies) < 1) { + cli::cli_alert_success(paste0( + "According to {.code DESCRIPTION}`, there are no hard dependencies of ", + "your package. You are set." )) - remote_deps <- rlang::try_fetch( - desc::desc_get_field("Remotes"), - error = function(e) character() - ) - if (length(remote_deps) > 0) { - rlang::warn(paste0( - "It seems you have remote dependencies in your `DESCRIPTION`. You ", - "need to edit the above list manually to match the syntax `renv::install()` ", - "understands, i.e. if you have in your `DESCRIPTION` + return() + } + + hard_dependencies %>% + snippet_generator() %>% + cat(sep = "") + cat("\n") + cli::cli_ul(paste0( + "Replace the `id: roxygenize` key in `.pre-commit-config.yaml` with the ", + "above code." + )) + cli::cli_alert_info(paste0( + "Note that CI services like {.url pre-commit.ci} have build-time ", + "restrictions and installing the above dependencies may exceed those, ", + "resulting in a timeout. In addition, system dependencies are not ", + "supported for {.url pre-commit.ci}. See ", + '{.code vignette("ci", package = "precommit")} for details and solutions.' + )) + remote_deps <- rlang::try_fetch( + desc::desc_get_field("Remotes"), + error = function(e) character() + ) + if (length(remote_deps) > 0) { + rlang::warn(paste0( + "It seems you have remote dependencies in your `DESCRIPTION`. You ", + "need to edit the above list manually to match the syntax `renv::install()` ", + "understands, i.e. if you have in your `DESCRIPTION` Imports: - tidyr + tidyr Remotes: - tidyverse/tidyr@2fd80d5 + tidyverse/tidyr@2fd80d5 You need in your `.pre-commit-config.yaml` - additional_dependencies: - - tidyverse/tidyr@2fd80d5 + additional_dependencies: + - tidyverse/tidyr@2fd80d5 " - )) - } + )) } if (open) { precommit::open_config(root) @@ -318,3 +326,14 @@ snippet_generate_impl_additional_deps_roxygenize <- function(packages, with_vers # roxygen requires loading pkg -> add dependencies from DESCRIPTION additional_dependencies:\n", out) } + +snippet_generate_impl_additional_deps_lintr <- function(packages, with_version = FALSE) { + out <- paste0( + " - ", packages, "\n", + collapse = "" + ) %>% + sort() + paste0(" - id: lintr + # lintr requires loading pkg -> add dependencies from DESCRIPTION + additional_dependencies:\n", out) +} diff --git a/inst/hooks/exported/lintr.R b/inst/hooks/exported/lintr.R index 21eb719a5..32630ff90 100755 --- a/inst/hooks/exported/lintr.R +++ b/inst/hooks/exported/lintr.R @@ -3,11 +3,15 @@ "Run lintr on R files during a precommit. Usage: - lintr [--warn_only] ... + lintr [options] ... + Options: - --warn_only Print lint warnings instead of blocking the commit. Should be - used with `verbose: True` in `.pre-commit-config.yaml`. - Otherwise, lints will never be shown to the user. + --warn_only Print lint warnings instead of blocking the commit. Should be + used with `verbose: True` in `.pre-commit-config.yaml`. + Otherwise, lints will never be shown to the user. + --load_package Use `pkgload::load_all()` to load subject package prior to + running lintr. + " -> doc arguments <- precommit::precommit_docopt(doc) @@ -23,6 +27,11 @@ if (any(lintr_staged)) { ) } +if (arguments$load_package) { + cat("Attempting to load package\n") + pkgload::load_all() +} + for (path in arguments$files) { lints <- lintr::lint(path) if (length(lints) > 0) { diff --git a/man/snippet_generate.Rd b/man/snippet_generate.Rd index d26672eb4..6806fc8ef 100644 --- a/man/snippet_generate.Rd +++ b/man/snippet_generate.Rd @@ -5,7 +5,7 @@ \title{Generate code snippets} \usage{ snippet_generate( - snippet = "", + snippet = "additional-deps-roxygenize", open = rstudioapi::isAvailable(), root = here::here() ) @@ -28,5 +28,8 @@ Currently supported: \item additional-deps-roxygenize: Code to paste into \code{.pre-commit-config.yaml} for the additional dependencies required by the roxygenize hook. +\item additional-deps-lintr: Code to paste into +\code{.pre-commit-config.yaml} for the additional dependencies required by +the lintr hook if you use \code{--load-package}. } } diff --git a/tests/testthat/test-setup.R b/tests/testthat/test-setup.R index a2519755e..18685bb79 100644 --- a/tests/testthat/test-setup.R +++ b/tests/testthat/test-setup.R @@ -1,4 +1,4 @@ -test_that("snippet generation works", { +test_that("snippet generation works for roxygen", { local_test_setup( git = FALSE, use_precommit = FALSE, package = TRUE, install_hooks = FALSE ) @@ -27,6 +27,36 @@ test_that("snippet generation works", { ) }) + +test_that("snippet generation works for lintr", { + local_test_setup( + git = FALSE, use_precommit = FALSE, package = TRUE, install_hooks = FALSE + ) + usethis::use_package("R", "Depends", "3.6.0") + expect_error( + out <- capture_output(snippet_generate("additional-deps-lintr")), + NA, + ) + expect_equal(out, "") + usethis::use_package("styler") + expect_error( + out <- capture_output(snippet_generate("additional-deps-lintr")), + NA, + ) + + expect_match( + out, " - id: lintr\n.* - styler\n$", + ) + desc::desc_set("Remotes", "r-lib/styler") + expect_warning( + out <- capture_output(snippet_generate("additional-deps-roxygenize")), + "you have remote dependencies " + ) + expect_match( + out, " - id: roxygenize\n.* - styler\n$", + ) +}) + test_that("snippet generation only includes hard dependencies", { local_test_setup( git = FALSE, use_precommit = FALSE, package = TRUE, diff --git a/vignettes/available-hooks.Rmd b/vignettes/available-hooks.Rmd index d5d02d11b..7d9a1605c 100644 --- a/vignettes/available-hooks.Rmd +++ b/vignettes/available-hooks.Rmd @@ -71,13 +71,18 @@ for the available settings. ## `style-files` A hook to style files with [styler](https://styler.r-lib.org). Only -commit code corresponding to the tidyverse style guide. You can pass -arguments to +commit code corresponding to the tidyverse style guide. + +**Arguments** + +You can pass arguments to [`style_file(...)`](https://styler.r-lib.org/reference/style_file.html) using the `--key=value` syntax like this: - id: style-files - args: [--scope=spaces, --reindention=specify_reindention('#')] +``` + id: style-files + args: [--scope=spaces, --reindention=specify_reindention('#')] +``` In addition, the hook takes the following arguments that are not passed to @@ -89,8 +94,10 @@ to - id: style-files - args: [--style_pkg=pkg.with.style.guide, --style_fun=exported.style.function] +``` + id: style-files + args: [--style_pkg=pkg.with.style.guide, --style_fun=exported.style.function] +``` - The argument `--no-warn-cache` is deprecated and will be removed in a future release. Please remove it from your @@ -105,8 +112,10 @@ to - id: style-files - args: [--cache-root=styler] +``` + id: style-files + args: [--cache-root=styler] +``` - Argument `ignore-start` and `ignore-stop` is passed to `options()` to set `styler.ignore_start` and `styler.ignore_stop` respectively. @@ -117,8 +126,10 @@ to - id: style-files - args: [--ignore-start="^# styler: on$", --ignore-stop="^# styler: off$"] +``` + id: style-files + args: [--ignore-start="^# styler: on$", --ignore-stop="^# styler: off$"] +``` This hook modifies files unless you specify `--dry=fail` (requires `{styler} > 1.3.2`). @@ -141,31 +152,33 @@ This hook does not modify files. ## `parsable-roxygen` -Checks if roxygen comments within your `.R` files are "valid" by checking if -running `roxygen2::parse_file()` on them returns any messages. +Checks if roxygen comments within your `.R` files are "valid" by +checking if running `roxygen2::parse_file()` on them returns any +messages. -This hook does not modify files. - -**eval** - -By default, each file will be parsed, but code will not be evaluated - neither -any explicit code in the file, nor any `@eval` tags within roxygen comments. +**Arguments** -If your commentary contains `@eval` tags which you would prefer to evaluate, you -can specify the `--eval` flag, which will cause the file's code to be evaluated -in an environment created by `roxygen2::env_file()`. Note that dependencies of -the code to evaluate must be available for pre-commit. You may list these as -`additional_dependencies:` for the `parsable-roxygen` hook in -`.pre-commit-config.yaml`. +``` + id: parsable-roxygen + args: [--eval] +``` -Inline R code within roxygen comments (i.e. within backticks) is **not** -evaluated by this hook, whether or not `--eval` is specified. You would need -to run the `roxygenize` hook for that. +- `eval`: By default, each file will be parsed, but code will not be + evaluated - neither any explicit code in the file, nor any `@eval` + tags within roxygen comments. If your commentary contains `@eval` + tags which you would prefer to evaluate, you can specify the + `--eval` flag, which will cause the file's code to be evaluated in + an environment created by `roxygen2::env_file()`. Note that + dependencies of the code to evaluate must be available for + pre-commit. You may list these as `additional_dependencies:` for the + `parsable-roxygen` hook in `.pre-commit-config.yaml`. - id: parsable-roxygen - args: [--eval] + Inline R code within roxygen comments (i.e. within backticks) is + **not** evaluated by this hook, whether or not `--eval` is + specified. You would need to run the `roxygenize` hook for that. -This hook was added in version 0.4.3.9000. +This hook does not modify files. This hook was added in version +0.4.3.9000. ## `no-browser-statement` @@ -176,8 +189,8 @@ This hook does not modify files. ## `no-print-statement` -Guarantees you that you don't accidentally commit code with a -`print()` statement in it. +Guarantees you that you don't accidentally commit code with a `print()` +statement in it. This hook does not modify files. This hook was added in version 0.3.2.9020. @@ -217,20 +230,23 @@ readLines(system.file("pre-commit-hooks.yaml", package = "precommit")) %>% cat(sep = "\n") ``` -**language** - -The `lang` arg will be passed to `spelling::spell_check_files()`. +**Arguments** - id: spell-check - args: [--lang=] +``` + id: spell-check + args: [--lang=, --read-only] +``` -**read only** +- `language`: The `lang` arg will be passed to + `spelling::spell_check_files()`. -The `--read-only` flag will be passed to spell check. This flag makes -this hook idempotent. +- `read-only`**:** The `--read-only` flag will be passed to spell + check. This flag makes this hook idempotent. - id: spell-check - args: [--read-only] +``` + id: spell-check + args: [--read-only] +``` This hook does not modify input files. It will add all words not found in the dictionary to `inst/WORDLIST`, assuming they were spelled @@ -247,39 +263,50 @@ To opt out of updating `inst/WORDLIST` provide the `--read-only` flag. A hook to run `roxygen2::roxygenize()`. Makes sure you commit your `.Rd` changes with the source changes. To take advantage of caching, you don't -need to run `roxygen2::roxygenize()` manually anymore. The argument -`--no-warn-cache` is deprecated and will be removed in a future release. -Please remove it from your `.pre-commit-config.yaml`. +need to run `roxygen2::roxygenize()` manually anymore. Because the hook will write the version of {roxygen2} to `DESCRIPTON`, you should either make sure the version you use when you call {roxygen2} interactively matches the one from in {precommit} or simply not run {roxygen2} manually. -If you specify additional roclets through the `Roxygen:` field in -`DESCRIPTION`, e.g. from [{pkgapi}](https://github.com/r-lib/pkgapi) you -must specify the dependencies explicitly such that `renv::install()` -understands it, e.g. - - id: roxygenize - additional_dependencies: - - r-lib/pkgapi - -This hook does not modify input files, but writes to `.Rd` files in -`man/`, `NAMESPACE` and potentially others depending on which roxygen -roclets you specified in `DESCRIPTION`. +Note that your package's dependencies must be listed in +`additional_dependencies:` in your `.pre-commit-config.yaml` as shown +below. You can generate these for easy c/p with +`snippet_generate("additional-deps-roxygenize")` . + +Similarly, if you specify additional roclets through the `Roxygen:` +field in `DESCRIPTION`, e.g. from +[{pkgapi}](https://github.com/r-lib/pkgapi) you must specify the +dependencies explicitly such that `renv::install()` understands it. + +``` + id: roxygenize + additional_dependencies: + - r-lib/pkgapi + - dplyr +``` **Arguments** - id: roxygenize - args: [--root=] +``` +id: roxygenize +args: [--root=] +``` - Argument `root` specifies the directory in the git repo that contains the R package. Defaults to `.` since for most R package git repos, the git and R package root coincide. Added in version 0.3.3.00000. +- The argument `--no-warn-cache` is deprecated and will be removed in + a future release. Please remove it from your + `.pre-commit-config.yaml`. + +This hook does not modify input files, but writes to `.Rd` files in +`man/`, `NAMESPACE` and potentially others depending on which roxygen +roclets you specified in `DESCRIPTION`. ## `deps-in-desc` @@ -288,26 +315,23 @@ your DESCRIPTION file. Note that `README.Rmd` is never checked. **Arguments** +``` + id: deps-in-desc + args: [--allow_private_imports, -root=] +``` + - Flag `allow_private_imports` lets the user specify that private imports into the package namespace are tolerable, e.g. `somepkg:::x()`. Flag not set by default, i.e. the hook will fail if such a call is found. - - - id: deps-in-desc - args: [--allow_private_imports] + - Argument `root` specifies the directory in the git repo that contains the R package. Defaults to `.` since for most R package git repos, the git and R package root coincide. Added in version 0.3.2.9000. - - - id: deps-in-desc - args: [--root=] - This hook does not modify the file `DESCRIPTION` because the user should decide for each package if it should go to `Imports:` or `Suggests:`, which can be done easily with `usethis::use_package()`. @@ -323,8 +347,10 @@ This hook does modify the file `DESCRIPTION`. - id: use-tidy-description - args: [--root=] +``` +id: use-tidy-description +args: [--root=] +``` - Argument `root` specifies the directory in the git repo that contains the R package. Defaults to `.` since for most R package git @@ -333,77 +359,89 @@ This hook does modify the file `DESCRIPTION`. ## `lintr` -A hook to run `lintr::lint()` to check that R files are lint free. -Argument `warning_only` changes the behavior of the pre-commit to be -non-blocking. You should set this with the field `verbose: true`. +A hook to run `lintr::lint()` to check that R files are lint free. You +should set this with the field `verbose: true`. + +**Arguments** + +``` + id: lintr + args: [--warn-only, --load-package] + verbose: true +``` + +- `warn-only`: Changes the behavior of the pre-commit to be + non-blocking. When configured this way, lintr prints lint errors as + they appear. - id: lintr - args: [--warn_only] - verbose: true +- `load-package`: When linting a package, {lintr} uses + `pkgload::load_all()` to load the package before running + `lintr::lint()`. Note that you need to list all dependencies of the + package under `additional_dependencies:` for the `lintr:`hook in + your `.pre-commit-config.yaml`. You can generate the code snip o for + c/p with `snippet_generate("additional-deps-lintr")`. This argument + was added in 0.4.3.9005. -When configured this way, lintr prints lint errors as they appear. Other -arguments are not supported. Instead, `lintr` config should be specified -in a `.lintr` config file in Debian Control Field Format as specified in -the [`.lintr` -documentation](https://github.com/r-lib/lintr#project-configuration). +The `.lintr` config file as documented in the [`.lintr` +documentation](https://github.com/r-lib/lintr#project-configuration) is +respected. This hook does not modify any file. ## `codemeta-description-updated` Make sure `DESCRIPTION` hasn't been edited more recently than -`codemeta.json`, - -i.e. remind you to run `codemetar::write_codemeta()` in order to keep -`codemeta.json` in sync with `DESCRIPTION`. - -This hook does not modify any file. +`codemeta.json`, i.e. remind you to run `codemetar::write_codemeta()` in +order to keep `codemeta.json` in sync with `DESCRIPTION`. **Arguments** - id: codemeta-description-updated - args: [--root=] +``` +id: codemeta-description-updated +args: [--root=] +``` - Argument `root` specifies the directory in the git repo that contains the R package. Defaults to `.` since for most R package git - repos, the git and R package root coincide. Added in version - 0.3.3.00000. + repos, the git and R package root coincide. +This hook does not modify any file. Added in version 0.3.3.00000. ## `pkgdown` -Check if your {pkgdown} config file (e.g. `_pkgdown.yml` in your root) has the -correct entries for references and articles. -This hook skips the time-consuming parts of building the index and reference and -only performs the validation. Hence we don't rely on the extensive dependency -graph of {pkgdown} being installed, including packages with heavy build-time -dependencies and system libraries. +Check if your {pkgdown} config file (e.g. `_pkgdown.yml` in your root) +has the correct entries for references and articles. This hook skips the +time-consuming parts of building the index and reference and only +performs the validation. Hence we don't rely on the extensive dependency +graph of {pkgdown} being installed, including packages with heavy +build-time dependencies and system libraries. -For this check, we rely on the the global R package library and require all -development dependencies of the package you want to run this hook for to be -installed, as well as {pkgdown} (without its dependencies). +For this check, we rely on the the global R package library and require +all development dependencies of the package you want to run this hook +for to be installed, as well as {pkgdown} (without its dependencies). This hook does not modify files. Added in version 0.3.2.9003. - ## `renv-lockfile-validate` -Guarantees that you don't accidentally commit an invalid `renv.lock` file. -See [`renv::lockfile_validate()` documentation](https://rstudio.github.io/renv/reference/lockfile_validate.html) -for details. +Guarantees that you don't accidentally commit an invalid `renv.lock` +file. See [`renv::lockfile_validate()` +documentation](https://rstudio.github.io/renv/reference/lockfile_validate.html) +for details. The below config that uses only `--error` should suffice +for most users. -The below config that uses only `--error` should suffice for most users. - - id: renv-lockfile-validate - args: [--error] - -This hook does not modify files. Added in version 0.4.3.9005. +``` +id: renv-lockfile-validate +args: [--error] +``` **Arguments** - +``` +id: renv-lockfile-validate +args: [--schema=] [--greedy --error --verbose --strict] +``` - id: renv-lockfile-validate - args: [--schema=] [--greedy --error --verbose --strict] +This hook does not modify files. Added in version 0.4.3.9005.