diff --git a/NAMESPACE b/NAMESPACE index cb2cd13bb..1a95c0c02 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -13,6 +13,7 @@ export(build_vignettes) export(check) export(check_built) export(check_dep_version) +export(check_doc_fields) export(check_mac_devel) export(check_mac_release) export(check_man) diff --git a/NEWS.md b/NEWS.md index 906b1f724..86767d119 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ * `bash()`, `create()`, `missing_s3()`, `reload()`, `show_news()`, and `wd()` are now deprecated. These functions are all historical parts of our workflow that we no longer use or recommend. `create()` is superseded by `usethis::create_package()`. * `build_manual()` reports more details on failure (#2586). +* `check_doc_fields()` is a new function that checks for missing `\value` and `\examples` fields in Rd files, which are commonly flagged by CRAN (#2525). * `build_vignettes()` and `clean_vignettes()` are now deprecated. We no longer recommend building vignettes in this way; instead use `pkgdown::build_article()` to render articles locally (#2488). * `build_site()` now just calls `pkgdown::build_site()`, meaning that you will get more (informative) output by default (#2578). * New `check_mac_devel()` function to check a package using the macOS builder at https://mac.r-project.org/macbuilder/submit.html (@nfrerebeau, #2507) diff --git a/R/check-doc.R b/R/check-doc.R index c9ac9d9f4..8191fc921 100644 --- a/R/check-doc.R +++ b/R/check-doc.R @@ -62,3 +62,54 @@ man_message <- function(x) { FALSE } } + +#' Check for missing documentation fields +#' +#' Checks all Rd files in `man/` and looks for any that have a `\usage` section +#' (i.e. a function) but that *don't* have `\value` and `\examples` sections. +#' These missing fields are flagged by CRAN on initial submission. +#' +#' @template devtools +#' @param fields A character vector of Rd field names to check for. +#' @returns A named list of character vectors, one for each field, containing +#' the names of Rd files missing that field. Returned invisibly. +#' @export +#' @examples +#' \dontrun{ +#' check_doc_fields(".") +#' } +check_doc_fields <- function(pkg = ".", fields = c("value", "examples")) { + pkg <- as.package(pkg) + fields <- stats::setNames(fields, fields) + + paths <- dir_ls(path(pkg$path, "man"), regexp = "\\.Rd$") + names(paths) <- path_rel(paths, pkg$path) + rd <- lapply(paths, tools::parse_Rd, permissive = TRUE) + rd_tags <- lapply(rd, \(x) unlist(lapply(x, attr, "Rd_tag"))) + + has_tag <- function(tags, this) { + any(paste0("\\", this) %in% tags) + } + + has_usage <- vapply(rd_tags, has_tag, logical(1), this = "usage") + rd_tags <- rd_tags[has_usage] + + results <- lapply(fields, function(field) { + missing <- !vapply(rd_tags, has_tag, logical(1), this = field) + names(rd_tags)[missing] + }) + + for (field in fields) { + missing <- results[[field]] + if (length(missing) > 0) { + cli::cli_inform(c( + "!" = "Missing {.code \\{field}} section in {length(missing)} file{?s}:", + stats::setNames(missing, rep("*", length(missing))) + )) + } else { + cli::cli_inform(c("v" = "All Rd files have a {.code \\{field}} section.")) + } + } + + invisible(results) +} diff --git a/man/check_doc_fields.Rd b/man/check_doc_fields.Rd new file mode 100644 index 000000000..d33ea336f --- /dev/null +++ b/man/check_doc_fields.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/check-doc.R +\name{check_doc_fields} +\alias{check_doc_fields} +\title{Check for missing documentation fields} +\usage{ +check_doc_fields(pkg = ".", fields = c("value", "examples")) +} +\arguments{ +\item{pkg}{The package to use, can be a file path to the package or a +package object. See \code{\link[=as.package]{as.package()}} for more information.} + +\item{fields}{A character vector of Rd field names to check for.} +} +\value{ +A named list of character vectors, one for each field, containing +the names of Rd files missing that field. Returned invisibly. +} +\description{ +Checks all Rd files in \verb{man/} and looks for any that have a \verb{\\usage} section +(i.e. a function) but that \emph{don't} have \verb{\\value} and \verb{\\examples} sections. +These missing fields are flagged by CRAN on initial submission. +} +\examples{ +\dontrun{ +check_doc_fields(".") +} +} diff --git a/tests/testthat/_snaps/check-doc.md b/tests/testthat/_snaps/check-doc.md index 8366b09e9..f54630d95 100644 --- a/tests/testthat/_snaps/check-doc.md +++ b/tests/testthat/_snaps/check-doc.md @@ -1,3 +1,21 @@ +# check_doc_fields output - missing fields + + Code + check_doc_fields(pkg) + Message + ! Missing `\value` section in 1 file: + * man/foo.Rd + ! Missing `\examples` section in 1 file: + * man/bar.Rd + +# check_doc_fields output - all present + + Code + check_doc_fields(pkg) + Message + v All Rd files have a `\value` section. + v All Rd files have a `\examples` section. + # check_man works Code diff --git a/tests/testthat/test-check-doc.R b/tests/testthat/test-check-doc.R index 0429176a3..65de04134 100644 --- a/tests/testthat/test-check-doc.R +++ b/tests/testthat/test-check-doc.R @@ -1,3 +1,31 @@ +test_that("check_doc_fields output - missing fields", { + pkg <- local_package_create() + dir.create(file.path(pkg, "man")) + + writeLines( + "\\name{foo}\\title{Foo}\\usage{foo()}\\description{A function.}\\examples{foo()}", + file.path(pkg, "man", "foo.Rd") + ) + writeLines( + "\\name{bar}\\title{Bar}\\usage{foo()}\\description{A function.}\\value{Something.}", + file.path(pkg, "man", "bar.Rd") + ) + + expect_snapshot(check_doc_fields(pkg)) +}) + +test_that("check_doc_fields output - all present", { + pkg <- local_package_create() + dir.create(file.path(pkg, "man")) + + writeLines( + "\\name{foo}\\title{Foo}\\usage{foo()}\\description{A function.}\\value{A value.}\\examples{foo()}", + file.path(pkg, "man", "foo.Rd") + ) + + expect_snapshot(check_doc_fields(pkg)) +}) + test_that("check_man works", { # tools:::.check_Rd_xrefs which is called by `check_man()` assumes the base # and recommended packages will all be in the library path, which is not the