Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -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"),
Expand Down Expand Up @@ -33,6 +33,7 @@ Imports:
commonmark,
graph,
httr2,
jsonlite,
knitr,
methods,
rvest,
Expand All @@ -45,7 +46,6 @@ Suggests:
curl,
devtools,
gert,
jsonlite,
rmarkdown,
tinytest,
usethis
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<PackageName>.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

Expand All @@ -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
Expand Down
124 changes: 112 additions & 12 deletions R/BiocCheck-class.R
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@
#'
#' @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 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.
#'
Expand Down Expand Up @@ -74,8 +81,15 @@
#'
#' @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.
#'
#' @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:
Expand All @@ -86,15 +100,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 `<package>.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 `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.
Expand Down Expand Up @@ -122,6 +139,8 @@ NULL
error = "list",
warning = "list",
note = "list",
# flat, machine-readable record of every condition raised
entries = "list",
metadata = "list"
),
methods = list(
Expand All @@ -146,6 +165,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(...)
Expand Down Expand Up @@ -203,6 +226,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
Expand All @@ -227,17 +254,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()
Expand All @@ -254,6 +311,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)
Expand Down
4 changes: 3 additions & 1 deletion R/BiocCheck.R
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@
#'
#' @return `BiocCheck()` is chiefly called for the side effect of the check
#' reporting. The function also creates a `<packageName>.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
#'
Expand Down
8 changes: 4 additions & 4 deletions R/checkRcoding.R
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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]
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
`<PackageName>.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
Expand Down
Loading
Loading