diff --git a/NAMESPACE b/NAMESPACE index 46884560e..d56a27d7c 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 3d9740ba0..2cb7bbb4a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,7 @@ # devtools (development version) * `build_manual()` reports more details on failure (#2586). +* New `check_doc_fields()` checks for missing `\value` and `\examples` fields in Rd files, which are commonly flagged by CRAN (#2525). * New `check_mac_devel()` function to check a package using the macOS builder at https://mac.r-project.org/macbuilder/submit.html (@nfrerebeau, #2507) * `is_loading()` is now re-exported from pkgload (#2556). * `load_all()` now errors if called recursively, i.e. if you accidentally include a `load_all()` call in one of your R source files (#2617). diff --git a/R/check-doc.R b/R/check-doc.R index e809e3992..8e1939468 100644 --- a/R/check-doc.R +++ b/R/check-doc.R @@ -62,3 +62,48 @@ man_message <- function(x) { FALSE } } + +#' Check for missing documentation fields +#' +#' Checks all Rd files in `man/` for missing `\value` and `\examples` sections. +#' These are flagged by CRAN on initial submission. +#' +#' @template devtools +#' @param fields A character vector of Rd field names to check for. +#' @return 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) + + paths <- dir(path(pkg$path, "man"), pattern = "\\.Rd$", full.names = TRUE) + rd <- lapply(paths, tools::parse_Rd, permissive = TRUE) + + has_tag <- function(x, tag) { + tags <- unlist(lapply(x, attr, "Rd_tag")) + any(tags %in% paste0("\\", tag)) + } + + results <- lapply(stats::setNames(fields, fields), function(field) { + missing <- !vapply(rd, has_tag, logical(1), tag = field) + path_rel(paths[missing], pkg$path) + }) + + 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 {.code \\{field}} sections.")) + } + } + + invisible(results) +} diff --git a/man/check_doc_fields.Rd b/man/check_doc_fields.Rd new file mode 100644 index 000000000..22848cc5c --- /dev/null +++ b/man/check_doc_fields.Rd @@ -0,0 +1,27 @@ +% 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/} for missing \verb{\\value} and \verb{\\examples} sections. +These are commonly flagged by CRAN, particularly for initial submissions. +} +\examples{ +\dontrun{ +check_doc_fields(".") +} +} diff --git a/tests/testthat/_snaps/check-doc.md b/tests/testthat/_snaps/check-doc.md new file mode 100644 index 000000000..b8209aa41 --- /dev/null +++ b/tests/testthat/_snaps/check-doc.md @@ -0,0 +1,18 @@ +# 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 `\value` sections. + v All Rd files have `\examples` sections. + diff --git a/tests/testthat/test-check-doc.R b/tests/testthat/test-check-doc.R index e6b28cdd3..485ea67a9 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}\\description{A function.}\\examples{foo()}", + file.path(pkg, "man", "foo.Rd") + ) + writeLines( + "\\name{bar}\\title{Bar}\\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}\\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