From 7693a236f4580a9fd0d4127cc9074c31b929fd51 Mon Sep 17 00:00:00 2001 From: Sean Davis Date: Mon, 31 Aug 2026 14:08:39 -0600 Subject: [PATCH 1/2] Add machine-readable JSON report Write '00BiocCheck.json' alongside '00BiocCheck.log' in the '.BiocCheck' folder. The report carries the full text output plus structured fields so that CI jobs, editors, and other tools can act on BiocCheck results without parsing text: * 'summary' with the error, warning, and note counts and an overall 'status' ('error', 'warning', 'note', or 'ok') * 'entries', one record per condition raised, with the originating check function ('checkFun') as the stable identifier, the check title, the message, the reported files, and parsed file 'locations' * 'text', the plain report line by line The check function name and the check title were already recorded; they are now exposed rather than being flattened into the text log. Also fix the doubly encoded 'toJSON'/'fromJSON' methods, have 'toJSON' return the JSON when no file is given, and report package-relative file paths in the coding practice checks so that locations resolve consistently. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0153nSC2TGmUwxcXt8iCvweV --- DESCRIPTION | 6 +- NAMESPACE | 2 + NEWS | 11 +++ R/BiocCheck-class.R | 123 +++++++++++++++++++++++++--- R/BiocCheck.R | 4 +- R/checkRcoding.R | 8 +- README.md | 14 ++++ inst/tinytest/test_json.R | 168 ++++++++++++++++++++++++++++++++++++++ man/BiocCheck-class.Rd | 23 ++++-- man/BiocCheck.Rd | 4 +- vignettes/BiocCheck.Rmd | 61 +++++++++++++- 11 files changed, 396 insertions(+), 28 deletions(-) create mode 100644 inst/tinytest/test_json.R diff --git a/DESCRIPTION b/DESCRIPTION index 0462a33..1518a16 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: BiocCheck Title: Bioconductor-specific package checks -Version: 1.49.30 -Date: 2026-07-27 +Version: 1.49.31 +Date: 2026-08-31 Authors@R: c( person("Bioconductor", "Package Maintainer", , "maintainer@bioconductor.org", "aut"), @@ -33,6 +33,7 @@ Imports: commonmark, graph, httr2, + jsonlite, knitr, methods, rvest, @@ -45,7 +46,6 @@ Suggests: curl, devtools, gert, - jsonlite, rmarkdown, tinytest, usethis diff --git a/NAMESPACE b/NAMESPACE index 0228615..b7b9ec2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -34,6 +34,8 @@ importFrom(httr2,request) importFrom(httr2,resp_body_html) importFrom(httr2,resp_body_json) importFrom(httr2,resp_status) +importFrom(jsonlite,read_json) +importFrom(jsonlite,toJSON) importFrom(knitr,purl) importFrom(stringdist,stringdistmatrix) importFrom(tools,Rd2ex) diff --git a/NEWS b/NEWS index b2f51a0..75d2fca 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,12 @@ NEW FEATURES non-standard fields in the `DESCRIPTION` file. o Add check for S4 classes to verify they provide a non-derived default `show()` method. + o Write a machine-readable `00BiocCheck.json` report next to + `00BiocCheck.log` in the `.BiocCheck` folder. It includes the + session metadata, a `summary` count of the errors, warnings, and notes, the + overall `status`, one structured `entries` record per condition raised + (with the originating check function and file locations where reported), + and the plain text report. See the vignette for the schema. BUG FIXES AND MINOR IMPROVEMENTS @@ -28,6 +34,11 @@ BUG FIXES AND MINOR IMPROVEMENTS via `.gitignore` in `BiocCheckGitClone`. o Optimize `getFunctionLengths` and `checkFunctionLengths` using vectorized operations for faster execution + o Coding practice checks now report file paths relative to the package + directory, e.g., `R/foo.R`, rather than the file name alone. + o The `toJSON` and `fromJSON` methods of the `BiocCheck` class no longer + doubly encode the report; `toJSON` returns the JSON when no `file` is + given. o Support and update testing for roxygen2 version 8 o Improve ORCID checker to correctly validate invalid check-sum characters o Resolve path linter false positives and use `file.path` for portable diff --git a/R/BiocCheck-class.R b/R/BiocCheck-class.R index f0cc59f..954a9d4 100644 --- a/R/BiocCheck-class.R +++ b/R/BiocCheck-class.R @@ -42,6 +42,12 @@ #' #' @field error,warning,note `list()` Finer extraction of each condition type #' +#' @field entries `list()` A flat list of records, one per condition raised, +#' each with the `severity`, the originating check function (`checkFun`), the +#' check title (`check`), the `message`, any `help_text` and `details`, and +#' the file `locations` when the check reports them. This is the +#' machine-readable form written to `00BiocCheck.json`. +#' #' @field metadata `list()` A list of additional information relevant to the #' package and its state. See details. #' @@ -73,9 +79,14 @@ #' of folders in the BBS. #' #' @param file `character(1)` A path to a JSON file for writing or reading as -#' created by `toJSON` and `fromJSON` `BiocCheck` methods. +#' created by `toJSON` and `fromJSON` `BiocCheck` methods. When `NULL`, +#' `toJSON` returns the JSON as a character string instead of writing it. +#' +#' @param text `character()` The plain text report, as included in the `text` +#' element of the JSON output. Defaults to the output of `composeReport`. #' #' @importFrom BiocBaseUtils checkInstalled +#' @importFrom jsonlite read_json toJSON #' @importFrom utils tail #' #' @section methods: @@ -86,15 +97,18 @@ #' * `setCheck`: Create a new element in the internal list for a check #' * `get`: Extract the list of conditions raised by `BiocCheck` #' * `getNum`: Tally the number of condition provided by the input +#' * `getStatus`: The worst condition raised, i.e., one of `error`, +#' `warning`, `note`, or `ok` when nothing was raised #' * `zero`: Reset the internal log of the condition provided #' * `getBiocCheckDir`: Report and create the `.BiocCheck` #' directory as obtained from the metadata #' * `composeReport`: Simplify the list structure from the `log` and #' provide a character vector of conditions raised -#' * `report`: Write the `00BiocCheck.log` report into the `BiocCheck` -#' folder -#' * `toJSON`: Write a JSON file to the location indicated with the -#' conditions raised +#' * `report`: Write the `00BiocCheck.log` and `00BiocCheck.json` reports +#' into the `BiocCheck` folder +#' * `toJSON`: Write (or return) the machine-readable report: the +#' `metadata`, a `summary` count of each condition, the overall `status`, +#' the `entries`, and the plain `text` report #' * `fromJSON`: Read a JSON file from the location indicated with the #' output of previous conditions raised in the check #' * `show`: Display the information in the class. Currently empty. @@ -122,6 +136,8 @@ NULL error = "list", warning = "list", note = "list", + # flat, machine-readable record of every condition raised + entries = "list", metadata = "list" ), methods = list( @@ -146,6 +162,10 @@ NULL .messages$setMessage(nist, condition = condition) .self[[condition]] <- append(.self[[condition]], nist) .self$log[[checkName]] <- append(.self$log[[checkName]], nist) + .self$entries <- c( + .self$entries, + list(.entry(mlist, checkName, condition, help_text, messages)) + ) }, addMetadata = function(BiocPackage, ...) { args <- list(...) @@ -203,6 +223,10 @@ NULL for (condition in conditions) { .self[[condition]] <- list() } + .self$entries <- Filter( + function(entry) !entry[["severity"]] %in% conditions, + .self$entries + ) }, getBiocCheckDir = function() { bioccheck_dir <- .self$metadata$BiocCheckDir @@ -227,17 +251,47 @@ NULL writeLines( outputs, con = file.path(bioccheck_dir, "00BiocCheck.log") ) + .self$toJSON( + file = file.path(bioccheck_dir, "00BiocCheck.json"), + text = outputs + ) }, - toJSON = function(file) { - out <- Filter(length, .self$log) - checkInstalled("jsonlite") - jlog <- jsonlite::toJSON(out, auto_unbox = FALSE) - jsonlite::write_json(jlog, file) + getStatus = function() { + counts <- .self$getNum() + worst <- names(counts)[counts > 0L] + if (length(worst)) worst[[1L]] else "ok" + }, + toJSON = function(file = NULL, text = .self$composeReport()) { + payload <- list( + ## an empty list would serialize as '[]' rather than '{}' + metadata = if (length(.self$metadata)) + .self$metadata + else + structure(list(), names = character(0L)), + summary = as.list(.self$getNum()), + status = .self$getStatus(), + entries = .self$entries, + ## some conditions embed newlines; split so that 'text' + ## matches the '00BiocCheck.log' file line for line + text = as.list( + strsplit( + paste(text, collapse = "\n"), "\n", fixed = TRUE + )[[1L]] + ) + ) + json <- jsonlite::toJSON( + payload, auto_unbox = TRUE, pretty = TRUE, null = "null" + ) + if (is.null(file)) + json + else + writeLines(json, con = file) }, fromJSON = function(file) { - checkInstalled("jsonlite") - infile <- jsonlite::read_json(file)[[1]] - .self[["log"]] <- jsonlite::fromJSON(infile, simplifyVector = FALSE) + payload <- jsonlite::read_json(file, simplifyVector = FALSE) + .self$metadata <- payload[["metadata"]] + .self$entries <- payload[["entries"]] + payload }, show = function() { invisible() @@ -254,6 +308,49 @@ NULL ) ) +## The two location formats emitted by the checks, i.e., '.lineReport' and +## sprintf("%s (line %d, column %d)"), the latter optionally prefixed with the +## symbol found, e.g., "sapply() in R/foo.R (line 3, column 5)". Parsed once +## here so that consumers of the JSON report never have to parse them. Chunk +## locations in vignettes are skipped: their lines are relative to the chunk. +.locPatterns <- c( + "^([^[:space:]]+)#L([0-9]+)", + "([^[:space:]]+) \\(line ([0-9]+), column ([0-9]+)\\)$" +) + +.parseLocations <- function(messages) { + messages <- as.character(messages) + for (pattern in .locPatterns) { + hits <- grepl(pattern, messages) + if (!any(hits)) + next + parts <- regmatches(messages[hits], regexec(pattern, messages[hits])) + parts <- do.call(rbind, parts) + res <- data.frame( + file = parts[, 2L], line = as.integer(parts[, 3L]) + ) + if (ncol(parts) > 3L) + res[["column"]] <- as.integer(parts[, 4L]) + return(res) + } + NULL +} + +## one flat, machine-readable record per condition raised. 'checkFun' is the +## stable identifier for downstream tools; 'check' is the human-readable title. +.entry <- function(mlist, checkName, condition, help_text, messages) { + list( + severity = condition, + checkFun = names(mlist), + check = checkName, + message = paste(unlist(mlist, use.names = FALSE), collapse = " "), + help_text = if (length(help_text)) + paste(help_text, collapse = " "), + details = I(as.character(messages)), + locations = .parseLocations(messages) + ) +} + .flattenElement <- function(listElem) { debugFun <- names(listElem) lowerElem <- unlist(listElem, use.names = FALSE) diff --git a/R/BiocCheck.R b/R/BiocCheck.R index e606fc4..bb68144 100644 --- a/R/BiocCheck.R +++ b/R/BiocCheck.R @@ -107,7 +107,9 @@ #' #' @return `BiocCheck()` is chiefly called for the side effect of the check #' reporting. The function also creates a `.BiocCheck` folder -#' and returns a `BiocCheck` reference class with three main list elements: +#' with the `00BiocCheck.log` text report and the `00BiocCheck.json` +#' machine-readable report (see the vignette for the JSON schema), and +#' returns a `BiocCheck` reference class with three main list elements: #' #' * **error**: Items to address before the package can be accepted #' diff --git a/R/checkRcoding.R b/R/checkRcoding.R index 59e3321..10a7cb7 100644 --- a/R/checkRcoding.R +++ b/R/checkRcoding.R @@ -222,7 +222,7 @@ check1toN <- function(.BiocPackage) { tokens <- tokens[ tokens[,"text"] == "1", , drop=FALSE] sprintf( "%s (line %d, column %d)", - basename(rfile), tokens[,"line1"], tokens[,"col1"] + .getDirFiles(rfile), tokens[,"line1"], tokens[,"col1"] ) }) msg_seq <- unlist(msg_seq) @@ -231,7 +231,7 @@ check1toN <- function(.BiocPackage) { checkSingleColon <- function(.BiocPackage, avail_pkgs = character(0L)) { rfiles <- .BiocPackage$RSources - names(rfiles) <- basename(rfiles) + names(rfiles) <- .getDirFiles(rfiles) colon_pres <- lapply(rfiles, function(rfile) { tokens <- getParseData(parse(rfile, keep.source = TRUE)) tokens <- tokens[tokens[,"token"] != "expr", ,drop=FALSE] @@ -502,7 +502,7 @@ getClassNEEQLookup <- function(rfile) { checkClassNEEQLookup <- function(.BiocPackage) { rfiles <- .BiocPackage$RSources - names(rfiles) <- basename(rfiles) + names(rfiles) <- .getDirFiles(rfiles) NEEQ_pres <- lapply(rfiles, getClassNEEQLookup) NEEQ_pres <- Filter(nrow, NEEQ_pres) msg_neeq <- lapply(names(NEEQ_pres), function(rfile, framelist) { @@ -532,7 +532,7 @@ checkExternalData <- function(.BiocPackage) { sprintf( "%s (line %d, column %d)", - basename(rfile), tokens[,"line1"], tokens[,"col1"] + .getDirFiles(rfile), tokens[,"line1"], tokens[,"col1"] ) }) unlist(msg_eda) diff --git a/README.md b/README.md index e5b0f52..30260ef 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,20 @@ BiocManager::install("Bioconductor/BiocAddins") Then, in RStudio, click on the "Addins" menu, and select "Run BiocCheck". +## Machine-readable output + +Each run writes both `00BiocCheck.log` and `00BiocCheck.json` to the +`.BiocCheck` folder. The JSON report contains the text output +along with a `summary` count of the errors, warnings, and notes, an overall +`status`, and one structured record per condition raised, so that continuous +integration jobs and other tools do not have to parse the text output: + +```sh +jq -e '.summary.error == 0' MyPackage.BiocCheck/00BiocCheck.json +``` + +See `vignette("BiocCheck")` for the schema. + ## Documentation The `BiocCheck` package contains a vignette that describes the package diff --git a/inst/tinytest/test_json.R b/inst/tinytest/test_json.R new file mode 100644 index 0000000..62d51f7 --- /dev/null +++ b/inst/tinytest/test_json.R @@ -0,0 +1,168 @@ +source("helpers.R") + +# machine-readable report ------------------------------------------------- +cli::cli_h3("JSON report") + +## conditions are raised from within a check function; 'checkFun' is taken +## from the calling function's name +noteCheck <- function() { + BiocCheck:::handleNote("A note") +} +warnCheck <- function() { + BiocCheck:::handleWarningFiles( + "A warning", messages = "R/foo.R (line 3, column 5)" + ) +} +errorCheck <- function() { + BiocCheck:::handleErrorFiles( + "An error", messages = "R/bar.R#L42 x <- 1 ..." + ) +} + +.BiocCheck$zero() +BiocCheck:::handleCheck("Checking JSON output...") +noteCheck() +warnCheck() +errorCheck() + +json <- .BiocCheck$toJSON() +expect_true(jsonlite::validate(json)) + +payload <- jsonlite::fromJSON(json, simplifyVector = FALSE) +expect_identical( + names(payload), c("metadata", "summary", "status", "entries", "text") +) + +## the summary is the tally of the conditions raised +expect_equal( + unlist(payload[["summary"]]), c(error = 1, warning = 1, note = 1) +) +expect_identical(payload[["status"]], "error") +expect_identical( + length(payload[["entries"]]), sum(.BiocCheck$getNum()) +) + +entries <- payload[["entries"]] +expect_identical( + vapply(entries, `[[`, character(1L), "severity"), + c("note", "warning", "error") +) +expect_identical( + vapply(entries, `[[`, character(1L), "checkFun"), + c("noteCheck", "warnCheck", "errorCheck") +) +expect_identical( + vapply(entries, `[[`, character(1L), "check"), + rep("Checking JSON output...", 3L) +) +expect_identical(entries[[1L]][["message"]], "A note") + +## 'help_text' is absent as 'null', not as an empty array +expect_null(entries[[1L]][["help_text"]]) +expect_identical(entries[[2L]][["help_text"]], "Found in files:") + +## 'details' is always an array, even when a single message is reported +expect_true(is.list(entries[[1L]][["details"]])) +expect_identical(length(entries[[1L]][["details"]]), 0L) +expect_identical( + entries[[3L]][["details"]], list("R/bar.R#L42 x <- 1 ...") +) + +## locations are parsed from both formats emitted by the checks +expect_null(entries[[1L]][["locations"]]) +expect_identical( + entries[[2L]][["locations"]], + list(list(file = "R/foo.R", line = 3L, column = 5L)) +) +expect_identical( + entries[[3L]][["locations"]], list(list(file = "R/bar.R", line = 42L)) +) + +# .parseLocations --------------------------------------------------------- +cli::cli_h3(".parseLocations") + +expect_null(BiocCheck:::.parseLocations(character(0L))) +expect_null(BiocCheck:::.parseLocations("no location here")) +expect_identical( + BiocCheck:::.parseLocations( + c("R/a.R#L1 code ...", "vignettes/b.Rmd#L20 more code ...") + ), + data.frame(file = c("R/a.R", "vignettes/b.Rmd"), line = c(1L, 20L)) +) +expect_identical( + BiocCheck:::.parseLocations( + c("a.R (line 1, column 2)", "b.R (line 30, column 4)") + ), + data.frame( + file = c("a.R", "b.R"), line = c(1L, 30L), column = c(2L, 4L) + ) +) +## the symbol found is not part of the file name +expect_identical( + BiocCheck:::.parseLocations( + c( + "sapply() in R/a.R (line 1, column 2)", + "update.packages() in R/b.R (line 3, column 4)" + ) + ), + data.frame( + file = c("R/a.R", "R/b.R"), line = c(1L, 3L), column = c(2L, 4L) + ) +) +## chunk-relative lines are not reported as file locations +expect_null( + BiocCheck:::.parseLocations("a.Rmd (chunk no. 2, line 1, column 2)") +) + +# round trip -------------------------------------------------------------- +cli::cli_h3("toJSON / fromJSON") + +jsonfile <- tempfile(fileext = ".json") +.BiocCheck$toJSON(file = jsonfile) +expect_true(file.exists(jsonfile)) + +messages <- vapply(.BiocCheck$entries, `[[`, character(1L), "message") +.BiocCheck$zero() +expect_identical(.BiocCheck$entries, list()) + +roundtrip <- .BiocCheck$fromJSON(jsonfile) +expect_identical(roundtrip[["status"]], "error") +expect_identical( + vapply(.BiocCheck$entries, `[[`, character(1L), "message"), messages +) + +## an empty report is still valid and reports an 'ok' status +.BiocCheck$entries <- list() +payload <- jsonlite::fromJSON(.BiocCheck$toJSON(), simplifyVector = FALSE) +expect_identical(payload[["status"]], "ok") +expect_identical(payload[["entries"]], list()) +expect_equal( + unlist(payload[["summary"]]), c(error = 0, warning = 0, note = 0) +) + +# report ------------------------------------------------------------------ +cli::cli_h3("report") + +bioccheck_dir <- file.path(tempfile(), "test.BiocCheck") +.BiocCheck$metadata <- list(Package = "test", BiocCheckDir = bioccheck_dir) +BiocCheck:::handleCheck("Checking report output...") +noteCheck() + +.BiocCheck$report(debug = FALSE, isOnBBS = TRUE) +expect_false(dir.exists(bioccheck_dir)) + +.BiocCheck$report(debug = FALSE, isOnBBS = FALSE) +expect_true(file.exists(file.path(bioccheck_dir, "00BiocCheck.log"))) +expect_true(file.exists(file.path(bioccheck_dir, "00BiocCheck.json"))) + +payload <- jsonlite::read_json(file.path(bioccheck_dir, "00BiocCheck.json")) +expect_identical(payload[["status"]], "note") +expect_identical(payload[["metadata"]][["Package"]], "test") +## the JSON is a superset of the plain text report +expect_identical( + unlist(payload[["text"]]), + readLines(file.path(bioccheck_dir, "00BiocCheck.log")) +) + +unlink(dirname(bioccheck_dir), recursive = TRUE) +.BiocCheck$zero() diff --git a/man/BiocCheck-class.Rd b/man/BiocCheck-class.Rd index b3c2271..1170148 100644 --- a/man/BiocCheck-class.Rd +++ b/man/BiocCheck-class.Rd @@ -32,7 +32,11 @@ Bioconductor Build System (BBS). This is helpful for avoiding the creation of folders in the BBS.} \item{file}{\code{character(1)} A path to a JSON file for writing or reading as -created by \code{toJSON} and \code{fromJSON} \code{BiocCheck} methods.} +created by \code{toJSON} and \code{fromJSON} \code{BiocCheck} methods. When \code{NULL}, +\code{toJSON} returns the JSON as a character string instead of writing it.} + +\item{text}{\code{character()} The plain text report, as included in the \code{text} +element of the JSON output. Defaults to the output of \code{composeReport}.} } \value{ An internal \code{BiocCheck} R5 Reference Class used to document @@ -80,6 +84,12 @@ purposes.} \item{\code{error,warning,note}}{\code{list()} Finer extraction of each condition type} +\item{\code{entries}}{\code{list()} A flat list of records, one per condition raised, +each with the \code{severity}, the originating check function (\code{checkFun}), the +check title (\code{check}), the \code{message}, any \code{help_text} and \code{details}, and +the file \code{locations} when the check reports them. This is the +machine-readable form written to \verb{00BiocCheck.json}.} + \item{\code{metadata}}{\code{list()} A list of additional information relevant to the package and its state. See details.} }} @@ -94,15 +104,18 @@ package and its state. See details.} \item \code{setCheck}: Create a new element in the internal list for a check \item \code{get}: Extract the list of conditions raised by \code{BiocCheck} \item \code{getNum}: Tally the number of condition provided by the input +\item \code{getStatus}: The worst condition raised, i.e., one of \code{error}, +\code{warning}, \code{note}, or \code{ok} when nothing was raised \item \code{zero}: Reset the internal log of the condition provided \item \code{getBiocCheckDir}: Report and create the \verb{.BiocCheck} directory as obtained from the metadata \item \code{composeReport}: Simplify the list structure from the \code{log} and provide a character vector of conditions raised -\item \code{report}: Write the \verb{00BiocCheck.log} report into the \code{BiocCheck} -folder -\item \code{toJSON}: Write a JSON file to the location indicated with the -conditions raised +\item \code{report}: Write the \verb{00BiocCheck.log} and \verb{00BiocCheck.json} reports +into the \code{BiocCheck} folder +\item \code{toJSON}: Write (or return) the machine-readable report: the +\code{metadata}, a \code{summary} count of each condition, the overall \code{status}, +the \code{entries}, and the plain \code{text} report \item \code{fromJSON}: Read a JSON file from the location indicated with the output of previous conditions raised in the check \item \code{show}: Display the information in the class. Currently empty. diff --git a/man/BiocCheck.Rd b/man/BiocCheck.Rd index 0ba1a86..2a17959 100644 --- a/man/BiocCheck.Rd +++ b/man/BiocCheck.Rd @@ -36,7 +36,9 @@ relevant to developers and contributors to \code{BiocCheck}.} \value{ \code{BiocCheck()} is chiefly called for the side effect of the check reporting. The function also creates a \verb{.BiocCheck} folder -and returns a \code{BiocCheck} reference class with three main list elements: +with the \verb{00BiocCheck.log} text report and the \verb{00BiocCheck.json} +machine-readable report (see the vignette for the JSON schema), and +returns a \code{BiocCheck} reference class with three main list elements: \itemize{ \item \strong{error}: Items to address before the package can be accepted \item \strong{warning}: Strongly suggested items that may require attention diff --git a/vignettes/BiocCheck.Rmd b/vignettes/BiocCheck.Rmd index aec73cb..a570266 100644 --- a/vignettes/BiocCheck.Rmd +++ b/vignettes/BiocCheck.Rmd @@ -50,6 +50,64 @@ BiocCheck("") Note that the `--new-package` option is turned on in the Single Package Builder (SPB) during the new package submission process. +# Machine-readable output + +Alongside the `00BiocCheck.log` text report, `BiocCheck` writes a +`00BiocCheck.json` file to the `.BiocCheck` folder. It contains +everything in the text report plus structured fields, so that continuous +integration jobs, editors, and other tools can act on the results without +parsing the text output: + +```json +{ + "metadata": { "Package": "MyPackage", "PackageVersion": "0.99.0", + "BiocVersion": "3.23", "BiocCheckVersion": "1.50.0", "...": "" }, + "summary": { "error": 1, "warning": 2, "note": 5 }, + "status": "error", + "entries": [ + { + "severity": "warning", + "checkFun": "checkFormatting", + "check": "Checking formatting of DESCRIPTION, NAMESPACE, ...", + "message": "Consider shorter lines; 3 lines (1%) are > 80 characters long.", + "help_text": "First few lines:", + "details": ["R/foo.R#L12 x <- some_very_long_call( ..."], + "locations": [ { "file": "R/foo.R", "line": 12 } ] + } + ], + "text": ["* Checking formatting of DESCRIPTION, NAMESPACE, ...", "..."] +} +``` + +* `summary` tallies the conditions raised and `status` is the most severe of + them, i.e., one of `error`, `warning`, `note`, or `ok`. +* `entries` has one record per condition raised. `checkFun` is the name of the + function that raised it and is the stable identifier to key on; `check` is + the human-readable title, which may be reworded between releases. +* `help_text` and `locations` are `null` when the check does not report them. + `locations` are file paths relative to the package directory; the `column` + element is present only for checks that report one. +* `text` is the `00BiocCheck.log` report, line by line. + +The same information is available from the object returned by `BiocCheck()`, +without reading the file: + +```{r json, eval = FALSE} +bc <- BiocCheck("") +bc$getStatus() +bc$getNum() +bc$entries[[1]] +jsonlite::fromJSON(bc$toJSON()) +``` + +To fail a job when a package raises errors, either use the JSON summary or the +`quit-with-status` option, which exits with a non-zero status when errors are +present: + +```sh +jq -e '.summary.error == 0' MyPackage.BiocCheck/00BiocCheck.json +``` + # When should `BiocCheck` be run `BiocCheck` should always be run after `R CMD check`. @@ -634,7 +692,8 @@ The output of this check includes the first few lines of offending code for many of the various checks. To see the full output, users are encouraged to run the check locally and browse to the `BiocCheck` output folder that was created with the `.BiocCheck` naming convention. The full output -log file is named `00BiocCheck.log`. +log file is named `00BiocCheck.log` and its machine-readable counterpart +`00BiocCheck.json`. There are several helpful packages that can be used for formatting of R code to particular coding standards such as [formatR][formatR] and From 4edf283b0b0312ff00dacfdcfa02657b58cb72be Mon Sep 17 00:00:00 2001 From: Sean Davis Date: Mon, 31 Aug 2026 14:23:39 -0600 Subject: [PATCH 2/2] Keep documentation lines within 80 characters The added roxygen prose and vignette example exceeded 80 characters once expanded into Rd, which BiocCheck flags on its own source. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0153nSC2TGmUwxcXt8iCvweV --- R/BiocCheck-class.R | 31 +++++++++++++++++-------------- man/BiocCheck-class.Rd | 31 +++++++++++++++++-------------- vignettes/BiocCheck.Rmd | 4 ++-- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/R/BiocCheck-class.R b/R/BiocCheck-class.R index 954a9d4..348fd23 100644 --- a/R/BiocCheck-class.R +++ b/R/BiocCheck-class.R @@ -42,11 +42,12 @@ #' #' @field error,warning,note `list()` Finer extraction of each condition type #' -#' @field entries `list()` A flat list of records, one per condition raised, -#' each with the `severity`, the originating check function (`checkFun`), the -#' check title (`check`), the `message`, any `help_text` and `details`, and -#' the file `locations` when the check reports them. This is the -#' machine-readable form written to `00BiocCheck.json`. +#' @field entries `list()` A flat list of records, one per +#' condition raised, each with the `severity`, the +#' originating check function (`checkFun`), the check title +#' (`check`), the `message`, any `help_text` and `details`, +#' and the file `locations` when reported by the check. This +#' is the machine-readable form written to `00BiocCheck.json`. #' #' @field metadata `list()` A list of additional information relevant to the #' package and its state. See details. @@ -79,11 +80,13 @@ #' of folders in the BBS. #' #' @param file `character(1)` A path to a JSON file for writing or reading as -#' created by `toJSON` and `fromJSON` `BiocCheck` methods. When `NULL`, -#' `toJSON` returns the JSON as a character string instead of writing it. +#' created by `toJSON` and `fromJSON` `BiocCheck` methods. +#' When `NULL`, `toJSON` returns the JSON as a character +#' string instead of writing it. #' -#' @param text `character()` The plain text report, as included in the `text` -#' element of the JSON output. Defaults to the output of `composeReport`. +#' @param text `character()` The plain text report, as included +#' in the `text` element of the JSON output. Defaults to the +#' output of `composeReport`. #' #' @importFrom BiocBaseUtils checkInstalled #' @importFrom jsonlite read_json toJSON @@ -104,11 +107,11 @@ #' directory as obtained from the metadata #' * `composeReport`: Simplify the list structure from the `log` and #' provide a character vector of conditions raised -#' * `report`: Write the `00BiocCheck.log` and `00BiocCheck.json` reports -#' into the `BiocCheck` folder -#' * `toJSON`: Write (or return) the machine-readable report: the -#' `metadata`, a `summary` count of each condition, the overall `status`, -#' the `entries`, and the plain `text` report +#' * `report`: Write the `00BiocCheck.log` and +#' `00BiocCheck.json` reports into the `BiocCheck` folder +#' * `toJSON`: Write (or return) the machine-readable report: +#' the `metadata`, a `summary` count of each condition, the +#' overall `status`, the `entries`, and the `text` report #' * `fromJSON`: Read a JSON file from the location indicated with the #' output of previous conditions raised in the check #' * `show`: Display the information in the class. Currently empty. diff --git a/man/BiocCheck-class.Rd b/man/BiocCheck-class.Rd index 1170148..33cc501 100644 --- a/man/BiocCheck-class.Rd +++ b/man/BiocCheck-class.Rd @@ -32,11 +32,13 @@ Bioconductor Build System (BBS). This is helpful for avoiding the creation of folders in the BBS.} \item{file}{\code{character(1)} A path to a JSON file for writing or reading as -created by \code{toJSON} and \code{fromJSON} \code{BiocCheck} methods. When \code{NULL}, -\code{toJSON} returns the JSON as a character string instead of writing it.} +created by \code{toJSON} and \code{fromJSON} \code{BiocCheck} methods. +When \code{NULL}, \code{toJSON} returns the JSON as a character +string instead of writing it.} -\item{text}{\code{character()} The plain text report, as included in the \code{text} -element of the JSON output. Defaults to the output of \code{composeReport}.} +\item{text}{\code{character()} The plain text report, as included +in the \code{text} element of the JSON output. Defaults to the +output of \code{composeReport}.} } \value{ An internal \code{BiocCheck} R5 Reference Class used to document @@ -84,11 +86,12 @@ purposes.} \item{\code{error,warning,note}}{\code{list()} Finer extraction of each condition type} -\item{\code{entries}}{\code{list()} A flat list of records, one per condition raised, -each with the \code{severity}, the originating check function (\code{checkFun}), the -check title (\code{check}), the \code{message}, any \code{help_text} and \code{details}, and -the file \code{locations} when the check reports them. This is the -machine-readable form written to \verb{00BiocCheck.json}.} +\item{\code{entries}}{\code{list()} A flat list of records, one per +condition raised, each with the \code{severity}, the +originating check function (\code{checkFun}), the check title +(\code{check}), the \code{message}, any \code{help_text} and \code{details}, +and the file \code{locations} when reported by the check. This +is the machine-readable form written to \verb{00BiocCheck.json}.} \item{\code{metadata}}{\code{list()} A list of additional information relevant to the package and its state. See details.} @@ -111,11 +114,11 @@ package and its state. See details.} directory as obtained from the metadata \item \code{composeReport}: Simplify the list structure from the \code{log} and provide a character vector of conditions raised -\item \code{report}: Write the \verb{00BiocCheck.log} and \verb{00BiocCheck.json} reports -into the \code{BiocCheck} folder -\item \code{toJSON}: Write (or return) the machine-readable report: the -\code{metadata}, a \code{summary} count of each condition, the overall \code{status}, -the \code{entries}, and the plain \code{text} report +\item \code{report}: Write the \verb{00BiocCheck.log} and +\verb{00BiocCheck.json} reports into the \code{BiocCheck} folder +\item \code{toJSON}: Write (or return) the machine-readable report: +the \code{metadata}, a \code{summary} count of each condition, the +overall \code{status}, the \code{entries}, and the \code{text} report \item \code{fromJSON}: Read a JSON file from the location indicated with the output of previous conditions raised in the check \item \code{show}: Display the information in the class. Currently empty. diff --git a/vignettes/BiocCheck.Rmd b/vignettes/BiocCheck.Rmd index a570266..3037545 100644 --- a/vignettes/BiocCheck.Rmd +++ b/vignettes/BiocCheck.Rmd @@ -61,7 +61,7 @@ parsing the text output: ```json { "metadata": { "Package": "MyPackage", "PackageVersion": "0.99.0", - "BiocVersion": "3.23", "BiocCheckVersion": "1.50.0", "...": "" }, + "BiocVersion": "3.23", "...": "" }, "summary": { "error": 1, "warning": 2, "note": 5 }, "status": "error", "entries": [ @@ -69,7 +69,7 @@ parsing the text output: "severity": "warning", "checkFun": "checkFormatting", "check": "Checking formatting of DESCRIPTION, NAMESPACE, ...", - "message": "Consider shorter lines; 3 lines (1%) are > 80 characters long.", + "message": "Consider shorter lines; 3 lines (1%) are > 80 chars.", "help_text": "First few lines:", "details": ["R/foo.R#L12 x <- some_very_long_call( ..."], "locations": [ { "file": "R/foo.R", "line": 12 } ]