| Title: | Advice on R Package Building |
|---|---|
| Description: | Give advice about good practices when building R packages. Advice includes functions and syntax to avoid, package structure, code complexity, code formatting, etc. |
| Authors: | Mark Padgham [aut, cre] (ORCID: <https://orcid.org/0000-0003-2172-5265>), Ascent Digital Services UK Limited [cph] (GitHub: MangoTheCat), Karina Marks [aut] (GitHub: KarinaMarks), Daniel de Bortoli [aut] (GitHub: ddbortoli), Gabor Csardi [aut], Hannah Frick [aut], Owen Jones [aut] (GitHub: owenjonesuob), Hannah Alexander [aut], Ana Simmons [ctb] (GitHub: anasimmons), Fabian Scheipl [ctb] (GitHub: fabian-s), Athanasia Mo Mowinckel [aut] (GitHub: drmowinckels, ORCID: <https://orcid.org/0000-0002-5756-0223>) |
| Maintainer: | Mark Padgham <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 1.1.0.001 |
| Built: | 2026-07-01 08:28:40 UTC |
| Source: | https://github.com/ropensci-review-tools/goodpractice |
Give advice about good practices when building R packages. Advice includes functions and syntax to avoid, package structure, code complexity, code formatting, etc.
Maintainer: Mark Padgham [email protected] (ORCID)
Authors:
Mark Padgham [email protected] (ORCID)
Karina Marks [email protected] (GitHub: KarinaMarks)
Daniel de Bortoli (GitHub: ddbortoli)
Gabor Csardi [email protected]
Hannah Frick [email protected]
Owen Jones [email protected] (GitHub: owenjonesuob)
Hannah Alexander [email protected]
Athanasia Mo Mowinckel [email protected] (ORCID) (GitHub: drmowinckels)
Other contributors:
Ascent Digital Services UK Limited (GitHub: MangoTheCat) [copyright holder]
Ana Simmons [email protected] (GitHub: anasimmons) [contributor]
Fabian Scheipl (GitHub: fabian-s) [contributor]
Useful links:
Report bugs at https://github.com/ropensci-review-tools/goodpractice/issues
Returns the names of all registered check groups.
Use these names with checks_by_group() to select checks by group,
or with options(goodpractice.exclude_check_groups = ...) to skip
groups.
Full descriptions of each check group are return by
describe_check_groups().
all_check_groups()all_check_groups()
A character vector of check group names.
# Names of all check groups: all_check_groups() # List individual checks by group: chks <- lapply(all_check_groups(), checks_by_group) names(chks) <- all_check_groups() chks# Names of all check groups: all_check_groups() # List individual checks by group: chks <- lapply(all_check_groups(), checks_by_group) names(chks) <- all_check_groups() chks
List the names of all checks
all_checks()all_checks()
Character vector of checks
all_checks()all_checks()
List all checks performed
checks(gp)checks(gp)
gp |
|
Character vector of check names.
Other API:
failed_checks(),
results()
path <- system.file("bad1", package = "goodpractice") # Run a subset of all checks available g <- gp(path, checks = all_checks()[9:16]) checks(g) # Or run with named check groups g <- gp(path, checks = checks_by_group("description", "namespace")) checks(g)path <- system.file("bad1", package = "goodpractice") # Run a subset of all checks available g <- gp(path, checks = all_checks()[9:16]) checks(g) # Or run with named check groups g <- gp(path, checks = checks_by_group("description", "namespace")) checks(g)
Returns the names of all checks that belong to the given group(s). This makes it easy to run or inspect a specific category of checks without knowing individual check names.
checks_by_group(...)checks_by_group(...)
... |
Group names as character strings. Use |
Character vector of check names
# run only DESCRIPTION and namespace checks checks_by_group("description", "namespace") # see what the lintr group covers checks_by_group("lintr") # See all checks by group: lapply(all_check_groups(), checks_by_group) # use directly in gp() ## Not run: gp(".", checks = checks_by_group("description", "lintr")) ## End(Not run)# run only DESCRIPTION and namespace checks checks_by_group("description", "namespace") # see what the lintr group covers checks_by_group("lintr") # See all checks by group: lapply(all_check_groups(), checks_by_group) # use directly in gp() ## Not run: gp(".", checks = checks_by_group("description", "lintr")) ## End(Not run)
Defining custom preparations and checks
make_prep(name, func) make_check(description, check, gp, ...)make_prep(name, func) make_check(description, check, gp, ...)
name |
Name of the preparation function. |
func |
A function that takes two arguments:
The |
description |
A description of the check. |
check |
A function that takes the |
gp |
A short description of what is good practice. |
... |
Further arguments. Most important: A |
For make_prep: a preparation function. For
make_check: a check object of class "check".
make_prep(): Create a preparation function
make_check(): Create a check function
# make a preparation function url_prep <- make_prep( name = "desc", func = function(path, quiet) desc::description$new(path) ) # and the corresponding check function url_chk <- make_check( description = "URL field in DESCRIPTION", tags = character(), preps = "desc", gp = "have a URL field in DESCRIPTION", check = function(state) state$desc$has_fields("URL") ) # use together in gp(): # (note that you have to list the name of your custom check in # the checks-argument as well....) bad1 <- system.file("bad1", package = "goodpractice") res <- gp(bad1, checks = c("url", "no_description_depends"), extra_preps = list("desc" = url_prep), extra_checks = list("url" = url_chk))# make a preparation function url_prep <- make_prep( name = "desc", func = function(path, quiet) desc::description$new(path) ) # and the corresponding check function url_chk <- make_check( description = "URL field in DESCRIPTION", tags = character(), preps = "desc", gp = "have a URL field in DESCRIPTION", check = function(state) state$desc$has_fields("URL") ) # use together in gp(): # (note that you have to list the name of your custom check in # the checks-argument as well....) bad1 <- system.file("bad1", package = "goodpractice") res <- gp(bad1, checks = c("url", "no_description_depends"), extra_preps = list("desc" = url_prep), extra_checks = list("url" = url_chk))
List the names of default checks (excludes optional check sets)
default_checks()default_checks()
Character vector of default check names
default_checks()default_checks()
Describe one or more checks
describe_check(check_name = NULL)describe_check(check_name = NULL)
check_name |
Names of checks to be described. |
List of character descriptions for each check_name
describe_check("rcmdcheck_non_portable_makevars") check_name <- c("no_description_depends", "lintr_assignment_linter", "no_import_package_as_a_whole", "rcmdcheck_missing_docs") describe_check(check_name) # Or to see all checks: ## Not run: describe_check(all_checks()) ## End(Not run)describe_check("rcmdcheck_non_portable_makevars") check_name <- c("no_description_depends", "lintr_assignment_linter", "no_import_package_as_a_whole", "rcmdcheck_missing_docs") describe_check(check_name) # Or to see all checks: ## Not run: describe_check(all_checks()) ## End(Not run)
Returns full descriptions of all registered check groups.
describe_check_groups()describe_check_groups()
A named list of each check group defined in all_check_groups(),
with text descriptions of each group.
# Names of all check groups: all_check_groups() # And corresponding descriptions: describe_check_groups()# Names of all check groups: all_check_groups() # And corresponding descriptions: describe_check_groups()
Export failed checks to JSON
export_json(gp, file, pretty = FALSE)export_json(gp, file, pretty = FALSE)
gp |
|
file |
Output connection or file. |
pretty |
Whether to pretty-print the JSON. |
Invisibly returns the path to the output file.
path <- system.file("bad1", package = "goodpractice") g <- gp(path, checks = "description_url") tmp <- tempfile(fileext = ".json") export_json(g, tmp) unlink(tmp)path <- system.file("bad1", package = "goodpractice") g <- gp(path, checks = "description_url") tmp <- tempfile(fileext = ".json") export_json(g, tmp) unlink(tmp)
Names of the failed checks
failed_checks(gp)failed_checks(gp)
gp |
|
Names of the failed checks.
Other API:
checks(),
results()
path <- system.file("bad1", package = "goodpractice") # run a subset of all checks available g <- gp(path, checks = all_checks()[9:16]) failed_checks(g) # Or run with named check groups g <- gp(path, checks = checks_by_group("description", "namespace")) failed_checks(g)path <- system.file("bad1", package = "goodpractice") # run a subset of all checks available g <- gp(path, checks = all_checks()[9:16]) failed_checks(g) # Or run with named check groups g <- gp(path, checks = checks_by_group("description", "namespace")) failed_checks(g)
Note that not all checks refer to the source code.
For these the result will be NULL.
failed_positions(gp)failed_positions(gp)
gp |
|
For the ones that do, the results is a list, one for each failure.
Since the same check can fail multiple times. A single failure
is a list with entries: filename, line_number,
column_number, ranges. ranges is a list of
pairs of start and end positions for each line involved in the
check.
A list of lists of positions. See details below.
path <- system.file("bad1", package = "goodpractice") g <- gp(path, checks = "description_url") failed_positions(g)path <- system.file("bad1", package = "goodpractice") g <- gp(path, checks = "description_url") failed_positions(g)
To see the results, just print it to the screen.
gp( path = ".", checks = default_checks(), extra_preps = NULL, extra_checks = NULL, quiet = TRUE )gp( path = ".", checks = default_checks(), extra_preps = NULL, extra_checks = NULL, quiet = TRUE )
path |
Path to a package root. |
checks |
Character vector, the checks to run. Defaults to
|
extra_preps |
Custom preparation functions. See
|
extra_checks |
Custom checks. See |
quiet |
Whether to suppress output from the preparation
functions. Note that not all preparation functions produce output,
even if this option is set to |
A goodpractice object that you can query
with a simple API. See results to start.
When using the default checks = all_checks(), entire groups of
checks can be excluded by group name via the
goodpractice.exclude_check_groups option or the
GP_EXCLUDE_CHECK_GROUPS environment variable (comma-separated).
The option takes precedence.
# Skip URL and coverage checks:
options(goodpractice.exclude_check_groups = c("urlchecker", "covr"))
# Or via environment variable:
Sys.setenv(GP_EXCLUDE_CHECK_GROUPS = "urlchecker,covr")
Exclusion only applies when checks = NULL (the default).
Explicit checks arguments are never filtered.
Specific files can be excluded from checks via the
goodpractice.exclude_path option or the GP_EXCLUDE_PATH
environment variable (comma-separated). Paths are relative to the
package root.
options(goodpractice.exclude_path = c("R/RcppExports.R", "R/generated.R"))
# Or via environment variable:
Sys.setenv(GP_EXCLUDE_PATH = "R/RcppExports.R,R/generated.R")
Excluded files are skipped by lintr, treesitter, expression, and roxygen2 checks.
Preparation steps run sequentially by default. To run them in
parallel, install future.apply and set a
plan:
future::plan("multisession")
gp(".")
Preps run in parallel only when a non-sequential plan is active. Prep functions must be independent: in parallel mode each prep receives the initial state snapshot, so a prep cannot read another prep's output. Only new state fields are merged back; if two preps write the same field, the second is dropped with a warning.
path <- system.file("bad1", package = "goodpractice") # Run a subset of all checks available g <- gp(path, checks = all_checks()[9:16]) g # Or run with named check groups g <- gp(path, checks = checks_by_group("description", "namespace"))path <- system.file("bad1", package = "goodpractice") # Run a subset of all checks available g <- gp(path, checks = all_checks()[9:16]) g # Or run with named check groups g <- gp(path, checks = checks_by_group("description", "namespace"))
You tell agents to directly use this function in order for them to learn the skill. If you want to use the skill yourself to guide an agent, then use the use_skill_gp function.
learn_skill_gp()learn_skill_gp()
The content of the file system.file("skills",
"goodpractice4agents.md", package = "goodpractice"))
## Not run: learn_gp_skill() ## End(Not run)## Not run: learn_gp_skill() ## End(Not run)
Print goodpractice results
## S3 method for class 'goodPractice' print(x, groups = NULL, positions_limit = 5, ...)## S3 method for class 'goodPractice' print(x, groups = NULL, positions_limit = 5, ...)
x |
Object of class |
groups |
Name of check groups for which to print results, as vector of
one or more of |
positions_limit |
How many positions to print at most. |
... |
Unused, for compatibility with |
Return all check results in a data frame
results(gp)results(gp)
gp |
|
Data frame, with columns:
check |
The name of the check. |
passed |
Logical, whether the check passed. |
Other API:
checks(),
failed_checks()
path <- system.file("bad1", package = "goodpractice") # Run a subset of all checks available g <- gp(path, checks = all_checks()[9:16]) results(g) # Or run with named check groups g <- gp(path, checks = checks_by_group("description", "namespace")) results(g)path <- system.file("bad1", package = "goodpractice") # Run a subset of all checks available g <- gp(path, checks = all_checks()[9:16]) results(g) # Or run with named check groups g <- gp(path, checks = checks_by_group("description", "namespace")) results(g)
These checks are optional and not included in the default set.
They are powered by lint_package using lintr's
default linter set and respect any .lintr configuration file
in the package root (e.g. to disable specific linters or add exclusions).
Add them via checks = c(default_checks(), tidyverse_checks()).
tidyverse_checks()tidyverse_checks()
Character vector of tidyverse check names
tidyverse_checks()tidyverse_checks()
The 'goodpractice4agents.md' file contains sufficient instructions for most AI agents to edit package code so that it passes most 'goodpractice' checks. The file can be directly edited and tweaked for personal use cases. An agent can be instructed to simply "run the file goodpractice4agents.md". Alternatively, the file can be directly downloaded from GitHub at https://github.com/ropensci-review-tools/goodpractice/tree/main/inst/skills/goodpractice4agents.md.
use_skill_gp( path = "goodpractice4agents.md", use_skills_subdir = FALSE, overwrite = FALSE )use_skill_gp( path = "goodpractice4agents.md", use_skills_subdir = FALSE, overwrite = FALSE )
path |
Local path where the file should be written. If |
use_skills_subdir |
If |
overwrite |
If |
You can also instruct agents to call the companion function, learn_skill_gp, directly in order to learn and use the skill itself.
(Invisibly) the full path to the written file.
path <- file.path(tempdir(), "skill.md") f <- use_skill_gp(path) file.exists(f) unlink(f)path <- file.path(tempdir(), "skill.md") f <- use_skill_gp(path) file.exists(f) unlink(f)