├── inst ├── testpackages │ ├── testpkg1 │ │ ├── NAMESPACE │ │ ├── src │ │ │ └── testpkg1.c │ │ ├── R │ │ │ └── zzz.R │ │ ├── DESCRIPTION │ │ └── man │ │ │ ├── internaltoo.Rd │ │ │ └── internal.Rd │ ├── testpkg2 │ │ ├── vignettes │ │ │ ├── testpkg0.html │ │ │ └── testpkg0.Rmd │ │ └── DESCRIPTION │ └── testpkg0 │ │ ├── NAMESPACE │ │ ├── tests │ │ └── testthat │ │ │ └── a_test.R │ │ ├── vignettes │ │ ├── testpkg0_child.Rmd │ │ ├── evalfalse.Rmd │ │ ├── testpkg0.Rnw │ │ ├── testpkg0.qmd │ │ ├── dupChunks.Rmd │ │ └── testpkg0.Rmd │ │ ├── R │ │ ├── baddeps.R │ │ ├── morecode.R │ │ ├── zzz.R │ │ ├── TFindex.R │ │ ├── morebadcode.R │ │ ├── requireme.R │ │ ├── has-devel.r │ │ ├── parseme.R │ │ └── bad_coding.R │ │ ├── DESCRIPTION │ │ └── man │ │ ├── has-devel.Rd │ │ ├── baddep.Rd │ │ └── a.Rd ├── testfiles │ ├── noDirectSlotAccess.Rmd │ └── directSlotAccess.Rmd └── tinytest │ ├── test_checkNAMESPACE.R │ ├── test_checkDESCRIPTION.R │ ├── helpers.R │ └── test_checkVignettes.R ├── tests └── tinytest.R ├── .Rbuildignore ├── .gitignore ├── TODO ├── R ├── checkNAMESPACE.R ├── Context.R ├── parseFiles.R ├── Message-class.R ├── findSymbols.R ├── BiocCheckGitClone.R ├── checkManDocumentation.R ├── checkDESCRIPTION.R ├── BiocCheck-class.R ├── BiocPackage-class.R ├── util.R ├── checkRcoding.R ├── BiocCheck.R └── checkVignettes.R ├── man ├── Context.Rd ├── Message-class.Rd ├── BiocCheckGitClone.Rd ├── BiocPackage-class.Rd ├── BiocCheck-class.Rd └── BiocCheck.Rd ├── .github ├── pull_request_template.md └── workflows │ ├── test-coverage.yml │ └── basic_checks.yml ├── DESCRIPTION ├── NAMESPACE ├── README.md └── NEWS /inst/testpackages/testpkg1/NAMESPACE: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg2/vignettes/testpkg0.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/NAMESPACE: -------------------------------------------------------------------------------- 1 | import(tools) 2 | export(baddep) -------------------------------------------------------------------------------- /tests/tinytest.R: -------------------------------------------------------------------------------- 1 | if (requireNamespace("tinytest", quietly = TRUE)) 2 | tinytest::test_package("BiocCheck") 3 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/tests/testthat/a_test.R: -------------------------------------------------------------------------------- 1 | context("Test") 2 | 3 | test_that("skip", 4 | skip_on_bioc() 5 | ) 6 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/vignettes/testpkg0_child.Rmd: -------------------------------------------------------------------------------- 1 | Hi, I'm a child Rmd. 2 | 3 | ```{r} 4 | a <- 2 + 1 5 | class(a) == "character" 6 | ``` 7 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg1/src/testpkg1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | SEXP nsreg() { 5 | return R_NamespaceRegistry; 6 | } 7 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | inst/testpackages/testpkg1/src/testpkg1.so 4 | inst/testpackages/testpkg1/src/testpkg1.o 5 | ^\.github$ 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | BiocCheck.Rproj 5 | vignettes/*.md 6 | vignettes/*.R 7 | vignettes/*.html 8 | vignettes/sync.sh 9 | *.so 10 | *.o 11 | -------------------------------------------------------------------------------- /inst/testfiles/noDirectSlotAccess.Rmd: -------------------------------------------------------------------------------- 1 | 2 | ```{r} 3 | track <- setClass("track", 4 | slots = c(x="numeric", y="numeric")) 5 | t1 <- track(x = 1:10, y = 1:10 + rnorm(10)) 6 | ``` 7 | 8 | -------------------------------------------------------------------------------- /inst/testfiles/directSlotAccess.Rmd: -------------------------------------------------------------------------------- 1 | 2 | ```{r} 3 | track <- setClass("track", 4 | slots = c(x="numeric", y="numeric")) 5 | t1 <- track(x = 1:10, y = 1:10 + rnorm(10)) 6 | t1@x 7 | ``` 8 | 9 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/R/baddeps.R: -------------------------------------------------------------------------------- 1 | baddep <- function() 2 | { 3 | validate('{"foo": "bar"}') 4 | } 5 | 6 | iambad <- function() 7 | { 8 | df <- data.frame(colone=1,coltwo=2) 9 | with(df, colone) 10 | } 11 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/R/morecode.R: -------------------------------------------------------------------------------- 1 | if (FALSE) browser() 2 | 3 | if (FALSE) cat("some code") 4 | 5 | if (FALSE) parse_Rd("some_file") 6 | 7 | if (F) print("howdy!") 8 | 9 | if (FALSE) 10 | if(T) print("halloooo") -------------------------------------------------------------------------------- /inst/testpackages/testpkg1/R/zzz.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(libname, pkgname) 2 | { 3 | library.dynam(pkgname, pkgname, NULL) 4 | if (FALSE) .C() 5 | } 6 | 7 | .onUnload <- function(libpath) 8 | { 9 | library.dynam.unload("testpkg1", libpath) 10 | } 11 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg1/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: testpkg1 2 | Title: bla 3 | Description: blsa 4 | Version: 0.1 5 | Author: Joe Blow 6 | Maintainer: Joe Blow 7 | Depends: R (>= 3.1.0), jsonlite 8 | License: GPL-2 9 | LazyData: false 10 | VignetteBuilder: knitr 11 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/vignettes/evalfalse.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Test vignette" 3 | author: "BiocCheck" 4 | date: "`r format(Sys.time(), '%B %d, %Y')`" 5 | output: 6 | BiocStyle::html_document 7 | --- 8 | 9 | ```{r setup, include=FALSE} 10 | knitr::opts_chunk$set(echo = TRUE, eval = FALSE) 11 | ``` 12 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg2/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: testpkg0 2 | Title: bla 3 | Description: blsa 4 | Version: 0.1 5 | Author: Joe Blow 6 | Maintainer: Joe Blow 7 | Depends: R (>= 3.1.0), jsonlite 8 | License: GPL-2 9 | LazyData: false 10 | Imports: devtools, tools 11 | VignetteBuilder: FailBuilder 12 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: testpkg0 2 | Title: bla 3 | Description: blsa 4 | Version: 0.1 5 | Author: Joe Blow 6 | Maintainer: Joe Blow 7 | Depends: R (>= 3.1.0), jsonlite 8 | License: GPL-2 9 | LazyData: true 10 | Imports: devtools, tools 11 | Remotes: 12 | VignetteBuilder: knitr 13 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | + description namespace consistency (??? look at FIXME in code ???) 2 | 3 | + Coding Practice 4 | - if/while () & vs &&, | vs || ? (if/while/etc... should be || / && not single) 5 | - ??Make sure every (non-trivial) class has a show() method or at least display the #/% of classes that don't. 6 | 7 | + NEWS 8 | o formatting (??? needs work ???) 9 | 10 | + unit test coverage amount? 11 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/R/zzz.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(libname, pkgname) { 2 | ## download.file should not be used here 3 | if (FALSE) { 4 | download.file("https://httpbin.org/get", destfile = tempfile()) 5 | } 6 | } 7 | 8 | .onAttach <- function(libname, pkgname) { 9 | if (FALSE) { 10 | downloader::download("https://httpbin.org/get", destfile = tempfile()) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/R/TFindex.R: -------------------------------------------------------------------------------- 1 | ## should not trigger T / F check 2 | test1 <- function() { 3 | mat1 <- matrix(seq_len(20), nrow=5) 4 | x <- list(orig = mat1, T = t(mat1)) 5 | x$T 6 | } 7 | 8 | ## should trigger T / F check 9 | test2 <- function() { 10 | x <- T 11 | x 12 | } 13 | 14 | test3 <- function(){ 15 | T <- "whatsinaname" 16 | myX <- seq_len(2) 17 | names(myX) <- c(T , "rose") 18 | myX[ T ] 19 | } 20 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg2/vignettes/testpkg0.Rmd: -------------------------------------------------------------------------------- 1 | %\VignetteIndexEntry{A new Title} 2 | %\VignetteEngine{ThisWillFail} 3 | %\VignetteEngine{MultipleVigEngineLines} 4 | 5 | ```{r} 6 | knitr::opts_chunk$set( 7 | cache = TRUE, 8 | eval = FALSE 9 | ) 10 | ``` 11 | 12 | ```{r echo=FALSE} 13 | vl <- T 14 | ``` 15 | 16 | ```R 17 | ``` 18 | 19 | ```{r,eval=FALSE} 20 | # this is a test 21 | ``` 22 | 23 | ```{r,eval=FALSE} 24 | # to increase proportion of eval = FALSE 25 | ``` 26 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/R/morebadcode.R: -------------------------------------------------------------------------------- 1 | a <<- 1 2 | # this is a really long line with many characters in it. too many characters! So many that a NOTE is triggered. 3 | #ooh, this has a tab here: that is so terrible! 4 | 5 | # the following line is terrible because it is indented by 6 | # something other than a multiple of 5 spaces! 7 | is(a) == "numeric" 8 | # good code (sort of) 9 | is(a, "numeric") 10 | 11 | is(a) != "character" 12 | class(a) == "character" 13 | class(a) != "character" 14 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/vignettes/testpkg0.Rnw: -------------------------------------------------------------------------------- 1 | %\VignetteIndexEntry{HowTo} 2 | %\VignetteDepends{BiocCheck} 3 | %\VignetteKeywords{example, vignette} 4 | %\VignettePackage{testpkg0} 5 | 6 | \documentclass{article} 7 | 8 | <>= 9 | BiocStyle::latex(use.unsrturl=FALSE) 10 | @ 11 | 12 | \title{testpkg0 How-To} 13 | \author{Bioconductor Core Team} 14 | \date{Edited: April 2023; Compiled: \today} 15 | 16 | \begin{document} 17 | 18 | \maketitle 19 | 20 | \tableofcontents 21 | 22 | \end{document} 23 | -------------------------------------------------------------------------------- /R/checkNAMESPACE.R: -------------------------------------------------------------------------------- 1 | checkNAMESPACE <- function(.BiocPackage) { 2 | handleCheck("Checking NAMESPACE...") 3 | namespace <- file.path(.BiocPackage$sourceDir, "NAMESPACE") 4 | ns_lines <- readLines(namespace, warn = FALSE) 5 | hasEP <- grepl( 6 | 'exportPattern.*\\[\\[:alpha:\\]\\]', ns_lines 7 | ) 8 | if (any(hasEP)) { 9 | handleError( 10 | "'NAMESPACE' contains exportPattern with '[[:alpha:]]'; ", 11 | "use explicit exports instead." 12 | ) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg1/man/internaltoo.Rd: -------------------------------------------------------------------------------- 1 | \name{internal_too} 2 | \alias{internal_too} 3 | \title{ 4 | internal function too 5 | } 6 | \description{ 7 | desc 8 | } 9 | \usage{ 10 | internal_too() 11 | } 12 | %- maybe also 'usage' for other objects documented here. 13 | \details{ 14 | %% ~~ If necessary, more details than the description above ~~ 15 | } 16 | \examples{ 17 | x <- 1:10 18 | } 19 | % Add one or more standard keywords, see file 'KEYWORDS' in the 20 | % R documentation directory. 21 | \keyword{ internal } 22 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/R/requireme.R: -------------------------------------------------------------------------------- 1 | if (FALSE) 2 | { 3 | library(testpkg0) 4 | require(testpkg0) 5 | library("testpkg0") 6 | require("testpkg0") 7 | library(package="testpkg0") 8 | library(package=testpkg0) 9 | require(package="testpkg0") 10 | require(package=testpkg0) 11 | require(package = teskpkg0) 12 | require('testpkg0') 13 | library('testpkg0') 14 | require(lib.loc=NULL, 15 | package=testpkg0) 16 | #bad: 17 | require(help=testpkg0) 18 | require(lib.loc=NULL, help = 19 | testpkg0) 20 | } -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/vignettes/testpkg0.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "testpkg0" 3 | vignette: > 4 | %\VignetteIndexEntry{Test Qmd Format} 5 | %\VignetteEngine{quarto} 6 | %\VignetteEncoding{UTF-8} 7 | --- 8 | 9 | ```{r} 10 | #| label: calculate 11 | 12 | 1 + 1 13 | ``` 14 | 15 | ```{r} 16 | #| echo: true 17 | 18 | 2 * 2 19 | ``` 20 | 21 | 22 | 23 | ```{r} 24 | #| echo: false 25 | #| label: mtcars-scatterplot 26 | #| fig.cap: "A scatterplot of mtcars" 27 | 28 | data("mtcars", package = "datasets") 29 | 30 | plot( 31 | x = mtcars$wt, y = mtcars$mpg, 32 | xlab = "Weight", ylab = "Milage", 33 | xlim = c(1,6), ylim = c(10,35), 34 | main = "Weight vs Milage" 35 | ) 36 | ``` 37 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg1/man/internal.Rd: -------------------------------------------------------------------------------- 1 | \name{internal_function} 2 | \alias{internal_function} 3 | \title{ 4 | internal_function 5 | } 6 | \description{ 7 | desc 8 | } 9 | \usage{ 10 | internal_function() 11 | } 12 | %- maybe also 'usage' for other objects documented here. 13 | \details{ 14 | %% ~~ If necessary, more details than the description above ~~ 15 | } 16 | \value{ 17 | %% ~Describe the value returned 18 | %% If it is a LIST, use 19 | %% \item{comp1 }{Description of 'comp1'} 20 | %% \item{comp2 }{Description of 'comp2'} 21 | %% ... 22 | } 23 | \examples{ 24 | \dontrun{ 25 | x <- 1:10 26 | } 27 | } 28 | % Add one or more standard keywords, see file 'KEYWORDS' in the 29 | % R documentation directory. 30 | \keyword{ internal } 31 | -------------------------------------------------------------------------------- /inst/tinytest/test_checkNAMESPACE.R: -------------------------------------------------------------------------------- 1 | source("helpers.R") 2 | 3 | .BiocCheck$zero() 4 | temp_dir <- tempfile() 5 | 6 | ## exportPattern with [[:alpha:]] 7 | .BiocPackage <- create_test_package( 8 | test_dir = temp_dir, 9 | extraActions = function(pkgdir) { 10 | cat("exportPattern('[[:alpha:]]')", 11 | file=file.path(pkgdir, "NAMESPACE")) 12 | } 13 | ) 14 | BiocCheck:::checkNAMESPACE(.BiocPackage) 15 | checkCounter("has exportPattern", "error") 16 | 17 | ## valid NAMESPACE 18 | .BiocPackage <- create_test_package( 19 | test_dir = temp_dir, 20 | extraActions = function(pkgdir) { 21 | cat("export(a, b, c)", 22 | file=file.path(pkgdir, "NAMESPACE")) 23 | } 24 | ) 25 | BiocCheck:::checkNAMESPACE(.BiocPackage) 26 | expect_true(stillZero()) 27 | 28 | unlink(temp_dir, recursive = TRUE) 29 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/R/has-devel.r: -------------------------------------------------------------------------------- 1 | #Stolen from devtools: 2 | 3 | #' Check if you have a development environment installed. 4 | #' 5 | #' Thanks to the suggestion of Simon Urbanek. 6 | #' 7 | #' @return TRUE if your development environment is correctly set up, otherwise 8 | #' returns an error. 9 | #' @export 10 | #' @examples 11 | #' has_devel() 12 | has_devel <- function() { 13 | foo_path <- file.path(tempdir(), "foo.c") 14 | 15 | cat("void foo(int *bar) { *bar=1; }\n", file = foo_path) 16 | on.exit(unlink(foo_path)) 17 | 18 | R("CMD SHLIB foo.c", tempdir()) 19 | dylib <- file.path(tempdir(), paste("foo", .Platform$dynlib.ext, sep='')) 20 | on.exit(unlink(dylib), add = TRUE) 21 | 22 | dll <- dyn.load(dylib) 23 | on.exit(dyn.unload(dylib), add = TRUE) 24 | 25 | stopifnot(.C(dll$bar, 0L)[[1]] == 1L) 26 | 27 | TRUE 28 | } 29 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/R/parseme.R: -------------------------------------------------------------------------------- 1 | function() { 2 | } 3 | fa = function() TRUE 4 | 5 | # a bracketless function with <- 6 | f2 <- function() TRUE 7 | 8 | # a bracketed function with = 9 | f3 = function() 10 | { 11 | TRUE 12 | 13 | } 14 | 15 | # a bracketed function with <- 16 | f4 <- function() 17 | { 18 | TRUE 19 | } 20 | 21 | # an lapply with an anonymous function 22 | lapply(LETTERS, 23 | function(x) { 24 | print(x) 25 | 26 | 27 | 28 | }) 29 | 30 | # a function with arguments 31 | f5 <- function(a, b, c) {} 32 | 33 | f6 <- function(a=1, b, c) {} 34 | 35 | f7 <- function(x, ...) { 36 | 37 | 38 | 39 | 40 | } 41 | 42 | # function with comments and blank lines 43 | ## DO NOT move without modifying unit test 44 | f8 <- function(a) { 45 | # this is a comment 46 | b <- a + 1 47 | # blank space below 48 | 49 | return(b) 50 | } 51 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/man/has-devel.Rd: -------------------------------------------------------------------------------- 1 | \name{has_devel} 2 | \alias{has_devel} 3 | \title{Check if you have a development environment installed.} 4 | \usage{ 5 | has_devel() 6 | } 7 | \value{ 8 | TRUE if your development environment is correctly set up, 9 | otherwise returns an error. 10 | } 11 | \description{ 12 | Thanks to the suggestion of Simon Urbanek. 13 | } 14 | \examples{ 15 | foo_path <- file.path(tempdir(), "foo.c") 16 | 17 | cat("void foo(int *bar) { *bar=1; }\n", file = foo_path) 18 | on.exit(unlink(foo_path)) 19 | 20 | devtools:::R("CMD SHLIB foo.c", tempdir()) 21 | dylib <- file.path(tempdir(), paste("foo", .Platform$dynlib.ext, sep='')) 22 | on.exit(unlink(dylib), add = TRUE) 23 | 24 | dll <- dyn.load(dylib) 25 | on.exit(dyn.unload(dylib), add = TRUE) 26 | 27 | stopifnot(.C(dll$bar, 0L)[[1]] == 1L) 28 | 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/vignettes/dupChunks.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Test Duplicate Chunks Vignette" 3 | author: "BiocCheck Maintainer" 4 | date: "`r format(Sys.time(), '%B %d, %Y')`" 5 | output: 6 | BiocStyle::html_document 7 | --- 8 | 9 | ```{r setup, include=FALSE} 10 | knitr::opts_chunk$set(echo = TRUE) 11 | ``` 12 | 13 | ```{r,dupname} 14 | if (!requireNamespace("BiocCheck", quietly = TRUE)) 15 | BiocManager::install("BiocCheck") 16 | ``` 17 | 18 | ```{r,dupname} 19 | ## This should trigger checkVigInstalls (2x) 20 | if (!requireNamespace("BiocCheck", quietly = TRUE)) 21 | BiocManager::install("BiocCheck") 22 | ## trigger with grep 23 | install_bioc("BiocCheck") 24 | ``` 25 | 26 | ```{r,eval=FALSE} 27 | ## adding more eval=FALSE chunks 28 | ``` 29 | 30 | ```{r,eval=FALSE} 31 | print("hello world") 32 | ``` 33 | 34 | ```{r, eval=FALSE} 35 | ## This should trigger checkVigChunkEval 36 | ``` 37 | 38 | 39 | -------------------------------------------------------------------------------- /man/Context.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/Context.R 3 | \name{Context} 4 | \alias{Context} 5 | \title{Report context of events to user with a data.frame of events and locations} 6 | \usage{ 7 | Context(file = "", lines = character(), idx = logical(), offset = 0L) 8 | } 9 | \arguments{ 10 | \item{file}{character(1) full path (including package name) of file being 11 | summarized.} 12 | 13 | \item{lines}{character() vector of text lines in file} 14 | 15 | \item{idx}{logical() same length as \code{lines} indicating lines in which 16 | event occurs} 17 | 18 | \item{offset}{\code{integer(1)} The number of lines to add to the 'Line' column 19 | calculation. It is mainly used to account for the number of lines that the 20 | YAML header occupies.} 21 | } 22 | \value{ 23 | Context: a data.frame() with columns File, Line, and Context 24 | } 25 | \description{ 26 | Report context of events to user with a data.frame of events and locations 27 | } 28 | \keyword{internal} 29 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | If you are submitting a pull request to `BiocCheck` please follow the 2 | [instructions](https://docs.google.com/presentation/d/1DkN2WVPOMVGqUtlSSrWbx6IMjtGP_cEHoE3nfOEnD68/edit#slide=id.p). 3 | The presentation includes steps for forking, creating a working branch, and 4 | useful related information. 5 | 6 | For a successful merge, the following steps are required: 7 | 8 | * [ ] Update the NEWS file 9 | * [ ] Update the vignette file 10 | * [ ] Add unit tests (optional but highly recommended) 11 | * [ ] Passing `R CMD build` & `R CMD check` on Bioconductor devel 12 | 13 | List a reviewer in the Pull Request (either @LiNk-NY, @lshep). 14 | Reviewers will make sure to `Comment`, `Approve` or `Request changes`. 15 | 16 | We _highly_ recommend the use of the Bioconductor devel docker image described 17 | [on the Bioconductor website](https://www.bioconductor.org/help/docker/). 18 | 19 | If you have any questions, please get in touch with the Bioconductor core team 20 | on the [Bioconductor Community Slack](https://bioc-community.herokuapp.com/). 21 | 22 | 23 | -------------------------------------------------------------------------------- /R/Context.R: -------------------------------------------------------------------------------- 1 | #' Report context of events to user with a data.frame of events and locations 2 | #' 3 | #' Report context of events to user with a data.frame of events and locations 4 | #' 5 | #' 6 | #' @param file character(1) full path (including package name) of file being 7 | #' summarized. 8 | #' 9 | #' @param lines character() vector of text lines in file 10 | #' 11 | #' @param idx logical() same length as `lines` indicating lines in which 12 | #' event occurs 13 | #' 14 | #' @param offset `integer(1)` The number of lines to add to the 'Line' column 15 | #' calculation. It is mainly used to account for the number of lines that the 16 | #' YAML header occupies. 17 | #' 18 | #' @return Context: a data.frame() with columns File, Line, and Context 19 | #' @keywords internal 20 | Context <- function(file="", lines=character(), idx=logical(), offset = 0L) { 21 | stopifnot( 22 | BiocBaseUtils::isScalarCharacter(file, zchar = TRUE), 23 | BiocBaseUtils::isScalarInteger(offset) 24 | ) 25 | data.frame( 26 | File = rep(.getDirFiles(file), sum(idx)), 27 | Line = which(idx) + offset, 28 | Context = lines[idx], 29 | stringsAsFactors=FALSE 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/vignettes/testpkg0.Rmd: -------------------------------------------------------------------------------- 1 | %\VignetteIndexEntry{Vignette Title} 2 | 3 | ```{r echo=FALSE} 4 | session_info() 5 | install_bioc <- function() { 6 | message("Installing...") 7 | } 8 | ``` 9 | 10 | ```{r eval=FALSE} 11 | if (!requireNamespace("BiocCheck", quietly = TRUE)) 12 | BiocManager::install("BiocCheck") 13 | ``` 14 | 15 | ```{r eval=TRUE} 16 | ## This should trigger checkVigInstalls (2x) 17 | if (!requireNamespace("BiocCheck", quietly = TRUE)) 18 | BiocManager::install("BiocCheck") 19 | ## trigger with grep 20 | install_bioc("BiocCheck") 21 | ``` 22 | 23 | ```{r} 24 | ## This should trigger checkVigBiocInst 25 | if (!requireNamespace("BiocCheck", quietly = TRUE)) 26 | BiocInstaller::biocLite("BiocCheck") 27 | ``` 28 | 29 | ```{r eval=FALSE} 30 | ## This should not trigger checkVigInstalls 31 | if (!requireNamespace("BiocCheck", quietly = TRUE)) 32 | BiocInstaller::biocLite("BiocCheck") 33 | check_install("BiocCheck") 34 | ``` 35 | 36 | ```{r eval=FALSE} 37 | ``` 38 | 39 | ```{r eval=FALSE} 40 | ``` 41 | 42 | ```{r eval=FALSE} 43 | ``` 44 | 45 | ```{r, eval=TRUE} 46 | if (T) { 47 | cat("bad practice") 48 | } 49 | ``` 50 | 51 | ```{r eval=FALSE} 52 | ``` 53 | 54 | ```{r eval=FALSE} 55 | ``` 56 | 57 | ```{r, child = "testpkg0_child.Rmd"} 58 | ``` 59 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/man/baddep.Rd: -------------------------------------------------------------------------------- 1 | \name{baddep} 2 | \alias{baddep} 3 | \title{ 4 | baddep 5 | } 6 | \description{ 7 | desc 8 | } 9 | \usage{ 10 | baddep() 11 | } 12 | %- maybe also 'usage' for other objects documented here. 13 | \details{ 14 | %% ~~ If necessary, more details than the description above ~~ 15 | } 16 | \value{ 17 | %% ~Describe the value returned 18 | %% If it is a LIST, use 19 | %% \item{comp1 }{Description of 'comp1'} 20 | %% \item{comp2 }{Description of 'comp2'} 21 | %% ... 22 | } 23 | \references{ 24 | %% ~put references to the literature/web site here ~ 25 | } 26 | \author{ 27 | %% ~~who you are~~ 28 | } 29 | \note{ 30 | %% ~~further notes~~ 31 | } 32 | 33 | %% ~Make other sections like Warning with \section{Warning }{....} ~ 34 | 35 | \seealso{ 36 | %% ~~objects to See Also as \code{\link{help}}, ~~~ 37 | } 38 | \examples{ 39 | ##---- Should be DIRECTLY executable !! ---- 40 | ##-- ==> Define data, use random, 41 | ##-- or do help(data=index) for the standard data sets. 42 | 43 | ## The function is currently defined as 44 | \dontrun{ 45 | function () 46 | { 47 | validate("{\"foo\": \"bar\"}") 48 | } 49 | } 50 | } 51 | % Add one or more standard keywords, see file 'KEYWORDS' in the 52 | % R documentation directory. 53 | \keyword{ ~kwd1 } 54 | \keyword{ ~kwd2 }% __ONLY ONE__ keyword per line 55 | -------------------------------------------------------------------------------- /R/parseFiles.R: -------------------------------------------------------------------------------- 1 | parseFile <- function(.BiocPackage, infile) { 2 | dir.create(parse_dir <- tempfile()) 3 | outfile <- file.path(parse_dir, "parseFile.tmp") 4 | if (grepl("\\.R$", infile, TRUE)) 5 | outfile <- infile 6 | if (grepl("\\.Rnw$|\\.Rmd|\\.qmd|\\.Rrst|\\.Rhtml$|\\.Rtex", infile, TRUE)) 7 | { 8 | vigBuilder <- .BiocPackage$VigBuilder 9 | if ("knitr" %in% vigBuilder) 10 | checkInstalled("knitr") 11 | suppressWarnings(suppressMessages( 12 | capture.output({ 13 | try_purl_or_tangle( 14 | input=infile, output=outfile, quiet = TRUE, documentation=0L 15 | ) 16 | }) 17 | )) 18 | } else if (grepl("\\.Rd$", infile, TRUE)) { 19 | rd <- .parse_Rd_pack(infile, usesRdpack = .BiocPackage$usesRdpack) 20 | code <- capture.output(tools::Rd2ex(rd)) 21 | writeLines(code, con=outfile, sep="\n") 22 | } 23 | p <- parse(outfile, keep.source=TRUE) 24 | getParseData(p) 25 | } 26 | 27 | parseFiles <- function(.BiocPackage) 28 | { 29 | rfiles <- .BiocPackage$RSources 30 | manfiles <- .BiocPackage$manSources 31 | vigfiles <- .BiocPackage$VigSources 32 | files <- c(rfiles, manfiles, vigfiles) 33 | parsedCode <- structure(vector("list", length(files)), .Names = files) 34 | for (file in files) 35 | { 36 | df <- parseFile(.BiocPackage, file) 37 | if (nrow(df)) 38 | parsedCode[[file]] <- df 39 | } 40 | Filter(Negate(is.null), parsedCode) 41 | } 42 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/man/a.Rd: -------------------------------------------------------------------------------- 1 | \name{a} 2 | \alias{a} 3 | %- Also NEED an '\alias' for EACH other topic documented here. 4 | \title{ bla 5 | %% ~~function to do ... ~~ 6 | } 7 | \description{ bla 8 | %% ~~ A concise (1-5 lines) description of what the function does. ~~ 9 | } 10 | \usage{ 11 | a(x) 12 | } 13 | %- maybe also 'usage' for other objects documented here. 14 | \arguments{ 15 | \item{x}{ 16 | %% ~~Describe \code{x} here~~ 17 | } 18 | } 19 | \details{ 20 | %% ~~ If necessary, more details than the description above ~~ 21 | } 22 | \value{ 23 | %% ~Describe the value returned 24 | %% If it is a LIST, use 25 | %% \item{comp1 }{Description of 'comp1'} 26 | %% \item{comp2 }{Description of 'comp2'} 27 | %% ... 28 | } 29 | \references{ 30 | %% ~put references to the literature/web site here ~ 31 | } 32 | \author{ 33 | %% ~~who you are~~ 34 | } 35 | \note{ 36 | %% ~~further notes~~ 37 | } 38 | 39 | %% ~Make other sections like Warning with \section{Warning }{....} ~ 40 | 41 | \seealso{ 42 | %% ~~objects to See Also as \code{\link{help}}, ~~~ 43 | } 44 | \examples{ 45 | #syntaks errir 46 | ##---- Should be DIRECTLY executable !! ---- 47 | ##-- ==> Define data, use random, 48 | ##-- or do help(data=index) for the standard data sets. 49 | \donttest{ 50 | #valid syntax 51 | d2 = 2 52 | } 53 | 54 | 55 | ## The function is currently defined as 56 | function (x) 57 | { 58 | 59 | 60 | 61 | } 62 | } 63 | 64 | % Add one or more standard keywords, see file 'KEYWORDS' in the 65 | % R documentation directory. 66 | \keyword{ ~kwd1 } 67 | \keyword{ ~kwd2 }% __ONLY ONE__ keyword per line 68 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: 6 | - devel 7 | paths: 8 | - 'DESCRIPTION' 9 | - '**test-coverage.yml' 10 | pull_request: 11 | branches: 12 | - devel 13 | 14 | name: test-coverage.yaml 15 | 16 | permissions: read-all 17 | 18 | jobs: 19 | test-coverage: 20 | runs-on: ubuntu-latest 21 | container: bioconductor/bioconductor_docker:devel 22 | env: 23 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - uses: r-lib/actions/setup-r@v2 29 | with: 30 | use-public-rspm: true 31 | 32 | - uses: r-lib/actions/setup-r-dependencies@v2 33 | with: 34 | extra-packages: any::covr, any::xml2 35 | needs: coverage 36 | 37 | - name: Test coverage 38 | run: | 39 | cov <- covr::package_coverage( 40 | quiet = FALSE, 41 | clean = FALSE, 42 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 43 | ) 44 | covr::to_cobertura(cov) 45 | shell: Rscript {0} 46 | 47 | - uses: codecov/codecov-action@v4 48 | with: 49 | fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }} 50 | file: ./cobertura.xml 51 | plugin: noop 52 | disable_search: true 53 | token: ${{ secrets.CODECOV_TOKEN }} 54 | 55 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: BiocCheck 2 | Title: Bioconductor-specific package checks 3 | Version: 1.47.7 4 | Date: 2025-12-10 5 | Authors@R: c( 6 | person("Bioconductor", "Package Maintainer", , 7 | "maintainer@bioconductor.org", "aut"), 8 | person("Lori", "Shepherd", role = "aut"), 9 | person("Daniel", "von Twisk", role = "ctb"), 10 | person("Kevin", "Rue", role = "ctb"), 11 | person("Marcel", "Ramos", , "marcel.ramos@sph.cuny.edu", 12 | c("aut", "cre"), c(ORCID = "0000-0002-3242-0582")), 13 | person("Leonardo", "Collado-Torres", role = "ctb"), 14 | person("Federico", "Marini", role = "ctb") 15 | ) 16 | Description: BiocCheck guides maintainers through Bioconductor best 17 | practicies. It runs Bioconductor-specific package checks by searching 18 | through package code, examples, and vignettes. Maintainers are 19 | required to address all errors, warnings, and most notes produced. 20 | License: Artistic-2.0 21 | URL: https://github.com/Bioconductor/BiocCheck 22 | BugReports: https://github.com/Bioconductor/BiocCheck/issues 23 | Depends: 24 | R (>= 4.4.0) 25 | Imports: 26 | BiocBaseUtils, 27 | BiocFileCache, 28 | BiocManager, 29 | biocViews, 30 | callr, 31 | cli, 32 | codetools, 33 | graph, 34 | httr2, 35 | knitr, 36 | methods, 37 | rvest, 38 | stringdist, 39 | tools, 40 | utils 41 | Suggests: 42 | BiocStyle, 43 | devtools, 44 | gert, 45 | jsonlite, 46 | rmarkdown, 47 | tinytest, 48 | usethis 49 | VignetteBuilder: 50 | knitr 51 | biocViews: Infrastructure 52 | Encoding: UTF-8 53 | Roxygen: list(markdown = TRUE) 54 | RoxygenNote: 7.3.3 55 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(.BiocCheck) 4 | export(.BiocPackage) 5 | export(BiocCheck) 6 | export(BiocCheckGitClone) 7 | exportClasses(BiocCheck) 8 | exportClasses(BiocPackage) 9 | import(biocViews) 10 | import(methods) 11 | importFrom(BiocBaseUtils,checkInstalled) 12 | importFrom(BiocBaseUtils,isScalarCharacter) 13 | importFrom(BiocBaseUtils,selectSome) 14 | importFrom(BiocFileCache,BiocFileCache) 15 | importFrom(BiocFileCache,bfcdownload) 16 | importFrom(BiocFileCache,bfcneedsupdate) 17 | importFrom(BiocFileCache,bfcquery) 18 | importFrom(BiocFileCache,bfcrpath) 19 | importFrom(BiocManager,available) 20 | importFrom(BiocManager,install) 21 | importFrom(BiocManager,repositories) 22 | importFrom(BiocManager,version) 23 | importFrom(cli,symbol) 24 | importFrom(codetools,findGlobals) 25 | importFrom(codetools,walkCode) 26 | importFrom(graph,acc) 27 | importFrom(graph,nodes) 28 | importFrom(httr2,req_body_form) 29 | importFrom(httr2,req_perform) 30 | importFrom(httr2,request) 31 | importFrom(httr2,resp_body_html) 32 | importFrom(httr2,resp_body_json) 33 | importFrom(httr2,resp_status) 34 | importFrom(knitr,purl) 35 | importFrom(stringdist,stringdistmatrix) 36 | importFrom(tools,Rd2ex) 37 | importFrom(tools,file_ext) 38 | importFrom(tools,file_path_sans_ext) 39 | importFrom(tools,parse_Rd) 40 | importFrom(utils,Stangle) 41 | importFrom(utils,capture.output) 42 | importFrom(utils,data) 43 | importFrom(utils,getParseData) 44 | importFrom(utils,globalVariables) 45 | importFrom(utils,head) 46 | importFrom(utils,packageDescription) 47 | importFrom(utils,packageVersion) 48 | importFrom(utils,readCitationFile) 49 | importFrom(utils,tail) 50 | importFrom(utils,untar) 51 | -------------------------------------------------------------------------------- /man/Message-class.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/Message-class.R 3 | \docType{class} 4 | \name{Message-class} 5 | \alias{Message-class} 6 | \alias{.MessageCondition} 7 | \alias{setMessage,Message-method} 8 | \alias{setCondition,Message-method} 9 | \alias{getCondition,Message-method} 10 | \title{A lower level Message helper class for BiocCheck} 11 | \arguments{ 12 | \item{condition}{\code{character(1)} One of the three conditions handled: \code{error}, 13 | \code{warning}, or \code{note}} 14 | 15 | \item{\dots}{\code{list()} A nested list with the check name as the top level 16 | layer. Second level lists include any \code{help_text} and \code{messages} that are 17 | part of the check.} 18 | } 19 | \value{ 20 | \code{.MessageCondition}: An internal \code{R5} Reference Class to handle 21 | messages and their conditions, e.g., for errors, warnings, or notes. 22 | } 23 | \description{ 24 | A lower level Message helper class for BiocCheck 25 | } 26 | \section{Fields}{ 27 | 28 | \describe{ 29 | \item{\code{msg}}{\code{list()} A list of character messages usually grown with \code{append} 30 | with conditions raised by a check} 31 | 32 | \item{\code{condition}}{\code{character(1)} One of the three conditions handled: \code{error}, 33 | \code{warning}, or \code{note}} 34 | }} 35 | 36 | 37 | \section{methods}{ 38 | 39 | \itemize{ 40 | \item \code{setMessage}: Set the message and condition (\code{error}, \code{warning}, or 41 | \code{note}) for the check 42 | \item \code{setCondition}: Change the condition for the check 43 | \item \code{getCondition}: Get the condition from the \code{Message} class 44 | } 45 | } 46 | 47 | \seealso{ 48 | \link{BiocCheck-class} \link{BiocPackage-class} 49 | } 50 | \keyword{internal} 51 | -------------------------------------------------------------------------------- /inst/tinytest/test_checkDESCRIPTION.R: -------------------------------------------------------------------------------- 1 | source("helpers.R") 2 | 3 | library(devtools) 4 | library(tinytest) 5 | 6 | .bioctest <- read_test_package("testpkg1") 7 | dcf <- .bioctest$DESCRIPTION 8 | BiocCheck:::checkFndPerson(dcf) 9 | checkCounter( 10 | "No 'fnd' role", "note" 11 | ) 12 | 13 | 14 | temp_dir <- tempfile() 15 | .bioctest <- create_test_package( 16 | test_dir = temp_dir, 17 | extraActions = function(pkgdir) { 18 | badfile <- file.path(pkgdir, "DESCRIPTION") 19 | writeLines(c( 20 | "Package: foo", 21 | "Version: 0.99.0", 22 | "Authors@R: person('Foo', 'Bar', email = 'foo.bar@email.com')", 23 | "License: GPL-2", 24 | "Depends: R (>= 4.0.0)" 25 | ), badfile) 26 | } 27 | ) 28 | dcf <- .bioctest$DESCRIPTION 29 | expect_identical( 30 | BiocCheck:::.PersonsFromDCF(dcf), 31 | person('Foo', 'Bar', email = 'foo.bar@email.com') 32 | ) 33 | BiocCheck:::checkFndPerson(dcf) 34 | checkCounter( 35 | "No 'fnd' role", "note" 36 | ) 37 | 38 | .bioctest <- create_test_package( 39 | test_dir = temp_dir, 40 | extraActions = function(pkgdir) { 41 | badfile <- file.path(pkgdir, "DESCRIPTION") 42 | writeLines(c( 43 | "Package: foo", 44 | "Version: 0.99.0", 45 | paste0( 46 | "Authors@R: person(", 47 | "'Foo', 'Bar', email = 'foo.bar@email.com', ", 48 | "role = c('aut', 'cre', 'fnd'))" 49 | ), 50 | "License: GPL-2", 51 | "Depends: R (>= 4.0.0)" 52 | ), badfile) 53 | } 54 | ) 55 | dcf <- .bioctest$DESCRIPTION 56 | BiocCheck:::checkFndPerson(dcf) 57 | expect_true( 58 | stillZero() 59 | ) 60 | 61 | unlink(temp_dir, recursive = TRUE) 62 | -------------------------------------------------------------------------------- /inst/tinytest/helpers.R: -------------------------------------------------------------------------------- 1 | UNIT_TEST_PKG <- "unitTestTempDir" 2 | UNIT_TEST_TEMPDIR <- file.path(tempdir(), UNIT_TEST_PKG) 3 | 4 | create_test_package <- 5 | function( 6 | test_dir = tempfile(), 7 | description = list(), 8 | pkgpath = tempfile(tmpdir = test_dir), 9 | extraActions = function(path = NULL) {}, 10 | use.canned = TRUE 11 | ) 12 | { 13 | canned <- list() 14 | if (use.canned) { 15 | canned <- list( 16 | Author = "Test Author", 17 | Maintainer = "Test Maintainer ", 18 | "Authors@R" = NULL 19 | ) 20 | } 21 | for (name in names(description)) 22 | canned[[name]] <- description[[name]] 23 | 24 | if (!dir.exists(pkgpath)) 25 | dir.create(pkgpath, recursive = TRUE) 26 | capture.output({ 27 | suppressWarnings( 28 | suppressMessages( 29 | usethis::create_package( 30 | path = pkgpath, 31 | fields = canned, 32 | rstudio = FALSE, 33 | open = FALSE 34 | ) 35 | ) 36 | ) 37 | }) 38 | cat("#", file = file.path(pkgpath, "NAMESPACE")) 39 | extraActions(pkgpath) 40 | .BiocPackage$initialize(pkgpath) 41 | } 42 | 43 | checkCounter <- function(msg, type = "error") { 44 | if (missing(msg)) 45 | stop(" Provide message input") 46 | conds <- c("note", "warning", "error") 47 | res <- as.integer(conds %in% type) 48 | names(res) <- conds 49 | expect_identical( 50 | .BiocCheck$getNum(c("note", "warning", "error")), res, 51 | msg 52 | ) 53 | .BiocCheck$zero() 54 | } 55 | 56 | read_test_package <- function(pkgname) { 57 | pkgdir <- system.file( 58 | "testpackages", pkgname, package="BiocCheck", mustWork = TRUE 59 | ) 60 | .BiocPackage$initialize(pkgdir) 61 | } 62 | 63 | stillZero <- function() 64 | { 65 | identical( 66 | .BiocCheck$getNum(c("note", "warning", "error")), 67 | c(note = 0L, warning = 0L, error = 0L) 68 | ) 69 | } 70 | -------------------------------------------------------------------------------- /man/BiocCheckGitClone.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/BiocCheckGitClone.R 3 | \name{BiocCheckGitClone} 4 | \alias{BiocCheckGitClone} 5 | \title{Checks specific to a Git clone of a package repository} 6 | \usage{ 7 | BiocCheckGitClone(package = ".", ...) 8 | } 9 | \arguments{ 10 | \item{package}{A directory containing an R source package. Not a package tar 11 | ball.} 12 | 13 | \item{\dots}{Currently, only \code{quit-with-status} is available. See 14 | \code{BiocCheck}} 15 | } 16 | \value{ 17 | \code{BiocCheckGitClone()} is chiefly called for the side effect of the 18 | check reporting. The function returns a \code{BiocCheck} reference class with 19 | three main list elements: 20 | \itemize{ 21 | \item error: Items to address before the package can be accepted 22 | \item warning: Strongly suggested items that may require attention 23 | \item note: Items to consider, though not required, before acceptance 24 | } 25 | } 26 | \description{ 27 | Analyzes an R package for adherence with Bioconductor package guidelines and 28 | best practices. The check outputs are categorized into ERROR, WARNING, and 29 | NOTE. This function is typically used in the Bioconductor Build System (BBS) 30 | and not intended for general use. 31 | } 32 | \details{ 33 | \code{BiocCheckGitClone()} reviews R packages for adherence with 34 | Bioconductor package guidelines and best practices. See 35 | \url{https://contributions.bioconductor.org} for the latest guidance for 36 | writing Bioconductor software. This function should only be run on a source 37 | directory and not on a tarball. 38 | 39 | \code{BiocCheckGitClone} is called within R with, as \preformatted{ 40 | BiocCheckGitClone() } where \code{package} is the source directory 41 | containing the \code{R} package. 42 | } 43 | \examples{ 44 | 45 | packageDir <- system.file("testpackages", "testpkg0", package="BiocCheck") 46 | BiocCheckGitClone(packageDir, `quit-with-status`=FALSE) 47 | 48 | } 49 | \references{ 50 | \url{https://contributions.bioconductor.org} 51 | } 52 | \seealso{ 53 | \link{BiocCheck-class} 54 | } 55 | \author{ 56 | Lori Shepherd 57 | } 58 | -------------------------------------------------------------------------------- /.github/workflows/basic_checks.yml: -------------------------------------------------------------------------------- 1 | name: R CMD check 2 | 3 | on: 4 | push: 5 | branches: 6 | - devel 7 | paths: 8 | - 'DESCRIPTION' 9 | - '**basic_checks.yml' 10 | workflow_dispatch: 11 | pull_request: 12 | branches: 13 | - devel 14 | 15 | env: 16 | cache-version: v1 17 | 18 | jobs: 19 | r-build-and-check: 20 | runs-on: ubuntu-latest 21 | container: bioconductor/bioconductor_docker:devel 22 | 23 | env: 24 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: TRUE 25 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 26 | CRAN: https://packagemanager.posit.co/cran/__linux__/noble/latest 27 | 28 | steps: 29 | - name: Checkout Repository 30 | uses: actions/checkout@v4 31 | 32 | - name: Query dependencies and update old packages 33 | run: | 34 | BiocManager::install(ask=FALSE) 35 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 36 | shell: Rscript {0} 37 | 38 | - name: Cache R packages 39 | if: runner.os != 'Windows' 40 | uses: actions/cache@v4 41 | with: 42 | path: /usr/local/lib/R/site-library 43 | key: ${{ env.cache-version }}-${{ runner.os }}-r-${{ hashFiles('.github/depends.Rds') }} 44 | restore-keys: ${{ env.cache-version }}-${{ runner.os }}-r- 45 | 46 | - name: Install dependencies 47 | run: | 48 | BiocManager::repositories() 49 | remotes::install_local(dependencies = TRUE, repos = BiocManager::repositories()) 50 | remotes::install_cran(c("rcmdcheck", "covr")) 51 | shell: Rscript {0} 52 | 53 | - name: Run rcmdcheck 54 | env: 55 | _R_CHECK_CRAN_INCOMING_REMOTE_: false 56 | run: rcmdcheck::rcmdcheck(args = "--no-manual", error_on = "warning", check_dir = "check") 57 | shell: Rscript {0} 58 | 59 | - name: Run BiocCheck 60 | env: 61 | DISPLAY: ':99.0' 62 | run: | 63 | BiocCheck::BiocCheck( 64 | dir('check', 'tar.gz$', full.names = TRUE), 65 | `quit-with-status` = FALSE, 66 | `no-check-R-ver` = TRUE, 67 | `no-check-bioc-help` = TRUE 68 | ) 69 | shell: Rscript {0} 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [BiocCheck](https://bioconductor.org/packages/BiocCheck) 2 | 3 | 4 | [![BioC status](http://www.bioconductor.org/shields/build/devel/bioc/BiocCheck.svg)](https://bioconductor.org/checkResults/devel/bioc-LATEST/BiocCheck) 5 | [![Platforms](http://www.bioconductor.org/shields/availability/devel/BiocCheck.svg)](https://www.bioconductor.org/packages/devel/bioc/html/BiocCheck.html#archives) 6 | [![Codecov test coverage](https://codecov.io/gh/Bioconductor/BiocCheck/graph/badge.svg)](https://app.codecov.io/gh/Bioconductor/BiocCheck) 7 | [![Downloads](http://www.bioconductor.org/shields/downloads/devel/BiocCheck.svg)](https://bioconductor.org/packages/stats/bioc/BiocCheck) 8 | 9 | 10 | The `BiocCheck` package provides a set of tools for checking a package 11 | against the current version of Bioconductor coding and style standards. 12 | 13 | ## Installation 14 | 15 | To install this package, start R and enter: 16 | 17 | ```r 18 | if (!requireNamespace("BiocManager", quietly = TRUE)) 19 | install.packages("BiocManager") 20 | 21 | BiocManager::install("BiocCheck") 22 | ``` 23 | 24 | ## Bioconductor Guidelines 25 | 26 | The Bioconductor guidelines are available at 27 | https://contributions.bioconductor.org/. This site provides the basis for 28 | many of the checks performed by `BiocCheck`. We encourage packages to 29 | follow these guidelines to ensure that they are of high quality and 30 | interoperate well with other Bioconductor packages. 31 | 32 | ## Usage 33 | 34 | To check a package, use the `BiocCheck::BiocCheck()` function. For 35 | example, to check the `BiocCheck` package itself, use: 36 | 37 | ```r 38 | BiocCheck::BiocCheck("BiocCheck") 39 | ``` 40 | 41 | in the directory above the source package directory. 42 | 43 | Note that the `BiocCheck` package must be installed to use this function. 44 | 45 | If you are using RStudio, you can use the `BiocCheck` addin to check a 46 | package. First, install the BiocAddins package: 47 | 48 | ```r 49 | BiocManager::install("Bioconductor/BiocAddins") 50 | ``` 51 | 52 | Then, in RStudio, click on the "Addins" menu, and select "Run BiocCheck". 53 | 54 | ## Documentation 55 | 56 | The `BiocCheck` package contains a vignette that describes the package 57 | in more detail. To view the vignette, start R and enter: 58 | 59 | ```r 60 | vignette("BiocCheck") 61 | ``` 62 | 63 | -------------------------------------------------------------------------------- /inst/testpackages/testpkg0/R/bad_coding.R: -------------------------------------------------------------------------------- 1 | bad_fun <- function(){ 2 | 3 | update.packages("base") 4 | 5 | sapply(letters, function(x) x) 6 | } 7 | 8 | pasteTest <- function() { 9 | 10 | message(paste0("A", "B")) 11 | message ( paste("A", "B")) 12 | 13 | warning(paste("A", "B"), "C") 14 | warning(paste0("A", "B"), "C") 15 | 16 | stop("A", paste0("B", "C")) 17 | stop("A", paste("B", "C")) 18 | } 19 | 20 | invalid_ref <- function() { 21 | 22 | bcheck <- BiocCheck:BiocCheck 23 | red <- 1 24 | xbluex <- 3 25 | bcheck <- xbluex:red 26 | 27 | } 28 | 29 | bad_dl <- function() { 30 | 31 | dataurl <- "https://raw.githubusercontent.com/file.csv" 32 | githurl <- "https://github.com/user/package/" 33 | githurl <- "https://dropbox.com/data?dl=1" 34 | laburl <- "https://gitlab.com/raw/master/data.Rda" 35 | bucketurl <- "https://bitbucket.org/test/raw/file.sav" 36 | download.file(dataurl, destfile = tempfile()) 37 | 38 | } 39 | 40 | bad_install <- function(pkg) { 41 | BiocManager::install(pkg) 42 | } 43 | 44 | bad_cat <- function() { 45 | cat("There is a cat here") 46 | ## except in show methods 47 | setMethod("show", signature = "character", function(obj) { 48 | cat("Cat is allowed here") 49 | }) 50 | } 51 | 52 | bad_print <- function() { 53 | for (i in 1:10) { 54 | print("test") 55 | } 56 | print("Using print instead of message") 57 | lapply(c("A", "B"), print) 58 | } 59 | 60 | bad_assignment <- function() { 61 | # using = assignment 62 | value = "there is a equals assignment operator" 63 | } 64 | 65 | ## This should not trigger 66 | check_install <- function(pkg) { 67 | instPkgs <- installed.packages() 68 | pkg %in% rownames(instPkgs) 69 | } 70 | 71 | running_system <- function() { 72 | system("whoami") 73 | } 74 | 75 | check_inst_pkg <- function(pkg = "getPass") { 76 | check_install(pkg = pkg) 77 | } 78 | 79 | check_Dep_Defunct <- function() { 80 | .Deprecated("This function is deprecated") 81 | .Defunct("This function is defunct") 82 | } 83 | 84 | ## test functions > 50 lines are reported 85 | really_long_function <- function() { 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | return(TRUE) 137 | } -------------------------------------------------------------------------------- /R/Message-class.R: -------------------------------------------------------------------------------- 1 | cli_warning <- function(...) { 2 | cli::cli_div(theme = list(.warning = list(color = "orange"))) 3 | cli::cli_alert_warning(paste0("{.warning WARNING: ", ..., "}"), wrap = TRUE) 4 | } 5 | 6 | cli_error <- function(...) { 7 | cli::cli_div(theme = list(.error = list(color = "red"))) 8 | cli::cli_alert_danger(paste0("{.error ERROR: ", ..., "}"), wrap = TRUE) 9 | } 10 | 11 | cli_note <- function(...) { 12 | cli::cli_div(theme = list(.note = list(color = "blue"))) 13 | cli::cli_alert_info(paste0("{.note NOTE: ", ..., "}"), wrap = TRUE) 14 | } 15 | 16 | # Message-class and methods------------------------------------------------ 17 | 18 | #' @name Message-class 19 | #' 20 | #' @docType class 21 | #' 22 | #' @title A lower level Message helper class for BiocCheck 23 | #' 24 | #' @aliases setMessage,Message-method setCondition,Message-method 25 | #' getCondition,Message-method 26 | #' 27 | #' @field msg `list()` A list of character messages usually grown with `append` 28 | #' with conditions raised by a check 29 | #' 30 | #' @field condition `character(1)` One of the three conditions handled: `error`, 31 | #' `warning`, or `note` 32 | #' 33 | #' @param condition `character(1)` One of the three conditions handled: `error`, 34 | #' `warning`, or `note` 35 | #' 36 | #' @param \dots `list()` A nested list with the check name as the top level 37 | #' layer. Second level lists include any `help_text` and `messages` that are 38 | #' part of the check. 39 | #' 40 | #' @section methods: 41 | #' * `setMessage`: Set the message and condition (`error`, `warning`, or 42 | #' `note`) for the check 43 | #' * `setCondition`: Change the condition for the check 44 | #' * `getCondition`: Get the condition from the `Message` class 45 | #' 46 | #' @importFrom BiocBaseUtils selectSome 47 | #' 48 | #' @seealso \link{BiocCheck-class} \link{BiocPackage-class} 49 | #' 50 | #' @return `.MessageCondition`: An internal `R5` Reference Class to handle 51 | #' messages and their conditions, e.g., for errors, warnings, or notes. 52 | #' 53 | #' @keywords internal 54 | .MessageCondition <- setRefClass("Message", 55 | fields = list( 56 | msg = "list", 57 | condition = "character" 58 | ), 59 | methods = list( 60 | setMessage = function(..., condition) { 61 | text <- list(...)[[1L]] 62 | .self$setCondition(condition) 63 | clifun <- switch( 64 | condition, 65 | error = cli_error, 66 | warning = cli_warning, 67 | note = cli_note 68 | ) 69 | .self$msg <- append(.self$msg, text) 70 | comps <- unlist(text, recursive = FALSE, use.names = FALSE) 71 | if (identical(length(comps), 1L)) { 72 | clifun(unlist(comps, use.names = FALSE)) 73 | } else { 74 | dotlist <- selectSome(tail(comps, 1L)[[1L]], maxToShow = 3L) 75 | names(dotlist) <- rep("*", length(dotlist)) 76 | dotlist <- gsub("{", "{{", dotlist, fixed = TRUE) |> 77 | gsub("}", "}}", x = _, fixed = TRUE) 78 | alerttitle <- head( 79 | unlist(text, recursive = FALSE, use.names = FALSE), -1L 80 | ) 81 | clifun( 82 | head(unlist(alerttitle, use.names = FALSE), 1L) 83 | ) 84 | if (identical(length(comps), 3L)) 85 | cli::cli_text(unlist(alerttitle, use.names = FALSE)[2L]) 86 | cli::cli_bullets(dotlist) 87 | } 88 | .self$msg 89 | }, 90 | setCondition = function(condition) { 91 | stopifnot( 92 | "'condition' must be one of 'error', 'warning', 'note'" = 93 | condition %in% c("error", "warning", "note") 94 | ) 95 | .self$condition <- append(.self$condition, condition) 96 | }, 97 | getCondition = function() { 98 | .self$condition 99 | } 100 | ) 101 | ) 102 | 103 | .messages <- .MessageCondition() 104 | -------------------------------------------------------------------------------- /man/BiocPackage-class.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/BiocPackage-class.R 3 | \docType{class} 4 | \name{BiocPackage-class} 5 | \alias{BiocPackage-class} 6 | \alias{.BiocPackage} 7 | \alias{BiocPackage} 8 | \title{A class for representing files in a Bioconductor package} 9 | \format{ 10 | An object of class \code{BiocPackage} of length 1. 11 | } 12 | \usage{ 13 | .BiocPackage 14 | } 15 | \value{ 16 | An object of class \code{BiocPackage} 17 | } 18 | \description{ 19 | The BiocPackage class is used to represent a Bioconductor 20 | package. It is used by BiocCheck to store information about the package 21 | being checked. The class has several methods to identify the type of 22 | package, check for common issues, and store metadata about the package. 23 | } 24 | \section{Fields}{ 25 | 26 | \describe{ 27 | \item{\code{isValid}}{\code{logical} indicating whether the package's \code{DESCRIPTION} file 28 | was able to be read without any errors} 29 | 30 | \item{\code{isTar}}{\code{logical} indicating whether the package is a tarball} 31 | 32 | \item{\code{isSourceDir}}{\code{logical} indicating whether the package being checked is 33 | from a source directory} 34 | 35 | \item{\code{isInfrastructure}}{\code{logical} indicating whether the package is an 36 | Bioconductor infrastructure package based on the \code{biocViews} field} 37 | 38 | \item{\code{usesRoxygen}}{\code{logical} indicating whether the package uses \code{roxygen2} 39 | documentation} 40 | 41 | \item{\code{usesRdpack}}{\code{logical} indicating whether the package uses \code{Rdpack} 42 | package} 43 | 44 | \item{\code{DESCRIPTION}}{\code{matrix} containing the DCF contents of the \code{DESCRIPTION} 45 | file} 46 | 47 | \item{\code{dependencies}}{\code{character} vector of package dependencies} 48 | 49 | \item{\code{readError}}{\code{character} error message if the \code{DESCRIPTION} file could 50 | not be read} 51 | 52 | \item{\code{packageVersion}}{\code{character} version of the package} 53 | 54 | \item{\code{packageType}}{\code{character} indicating the type of package based on the 55 | \code{biocViews} field; can be \code{NA_character_} there are invalid \code{biocViews} 56 | terms} 57 | 58 | \item{\code{sourceDir}}{\code{character} path to the source directory} 59 | 60 | \item{\code{vignettesDir}}{\code{character} path to the vignettes directory} 61 | 62 | \item{\code{RSources}}{\code{character} vector of R source files} 63 | 64 | \item{\code{VigSources}}{\code{character} vector of vignette source files} 65 | 66 | \item{\code{manSources}}{\code{character} vector of Rd source files} 67 | 68 | \item{\code{BiocCheckDir}}{\code{character} path to the directory where the package 69 | BiocCheck logs are written} 70 | 71 | \item{\code{packageName}}{\code{character} name of the package} 72 | 73 | \item{\code{tarFilename}}{\code{character} filename of the tarball} 74 | 75 | \item{\code{metadata}}{\code{list} containing metadata about the package} 76 | }} 77 | 78 | \section{methods}{ 79 | 80 | \itemize{ 81 | \item \code{initialize}: Initialize a \code{BiocPackage} object 82 | \item \code{getPackageDir}: Get the package directory 83 | \item \code{getRSources}: Get the R source files 84 | \item \code{getVigSources}: Get the vignette source files 85 | \item \code{getManSources}: Get the Rd source files 86 | \item \code{getBiocCheckDir}: Get the directory where the BiocCheck logs are written 87 | \item \code{getBiocViews}: Get the \code{biocViews} field from the \code{DESCRIPTION} file 88 | \item \code{getPackageType}: Get the package type based on the \code{biocViews} field 89 | \item \code{readDESCRIPTION}: Read the \code{DESCRIPTION} file 90 | \item \code{getVigBuilder}: Get the vignette builder 91 | \item \code{getAllDependencies}: Get all dependencies from the \code{DESCRIPTION} file 92 | \item \code{findInfrastructure}: Is the package an infrastructure package? 93 | \item \code{findRoxygen}: Does the package use \code{roxygen2}? 94 | \item \code{getPackageVersion}: Get the package version 95 | \item \code{untarTarball}: Untar the source tarball 96 | } 97 | } 98 | 99 | \examples{ 100 | 101 | # Create a BiocPackage object 102 | packageDirectory <- "path/to/package" 103 | if (dir.exists(packageDirectory)) 104 | .bioctest <- .BiocPackage$initialize(packageDirectory) 105 | 106 | .bioctest <- BiocCheck:::.BiocPackage 107 | 108 | .bioctest$DESCRIPTION 109 | 110 | } 111 | \seealso{ 112 | \link{BiocCheck-class}, \link{Message-class} 113 | } 114 | \keyword{internal} 115 | -------------------------------------------------------------------------------- /man/BiocCheck-class.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/BiocCheck-class.R 3 | \docType{class} 4 | \name{BiocCheck-class} 5 | \alias{BiocCheck-class} 6 | \title{An internal class for composing BiocCheck reports} 7 | \arguments{ 8 | \item{...}{\code{character()} A vector that makes up the \code{BiocCheck} exception 9 | message (e.g., 'Vignette must be built by R CMD build'). The character 10 | vector is handled with \code{paste0} and made into a list and appended with 11 | \code{help_text} and \code{messages}.} 12 | 13 | \item{help_text}{\code{character(1)} Additional text prompting a list of files 14 | (e.g,. "Found in files:")} 15 | 16 | \item{condition}{\code{character(1)} One of the three conditions handled: \code{error}, 17 | \code{warning}, or \code{note}} 18 | 19 | \item{messages}{\code{character()} Often a vector of file names where the check 20 | was triggered.} 21 | 22 | \item{debug}{\code{logical(1)} Whether to append the name of the originating check 23 | name into for trace-ability} 24 | 25 | \item{checkName}{\code{character(1)} The title of the current group of checks. It 26 | can be set with \code{handleCheck}, e.g., 27 | \code{handleCheck("Checking for version number mismatch...")}. Internally, it 28 | ' is saved with \code{setCheck} and obtained with \code{getLastCheck}.} 29 | 30 | \item{isOnBBS}{\code{logical(1)} Indicates whether the checks are being run on the 31 | Bioconductor Build System (BBS). This is helpful for avoiding the creation 32 | of folders in the BBS.} 33 | 34 | \item{file}{\code{character(1)} A path to a JSON file for writing or reading as 35 | created by \code{toJSON} and \code{fromJSON} \code{BiocCheck} methods.} 36 | } 37 | \value{ 38 | An internal \code{BiocCheck} R5 Reference Class used to document 39 | conditions such as errors, warnings, and notes 40 | } 41 | \description{ 42 | The \code{BiocCheck} class provides a framework for reporting checks 43 | based on Bioconductor guidelines. The class has several methods for working 44 | with the provided checks that handle and display messages and the display 45 | of the metadata. These methods also record the output of the \code{BiocCheck()} 46 | report in both plain text and JSON formats. 47 | 48 | \strong{Note} that currently, multiple \code{BiocCheck} runs will interfere with 49 | each other given that they are implemented via a reference class semantic. 50 | When running multiple checks in the same session, you can separate these 51 | instances by running them in separate processes (e.g., via \code{BiocParallel}). 52 | } 53 | \details{ 54 | The metadata includes a number of standard fields to allow easier 55 | troubleshooting and display of potentially relevant information. Currently, 56 | the fields included are: 57 | \itemize{ 58 | \item BiocCheckVersion: The version of the BiocCheck package 59 | \item BiocVersion: The version of Bioconductor 60 | \item Package: The name of the package in check 61 | \item PackageVersion: The version of the package in check 62 | \item sourceDir: The directory of the package source or tarball in check 63 | \item installDir: The directory where the package is installed for 64 | testing, a temporary location by default 65 | \item BiocCheckDir: The directory where the \verb{.BiocCheck} folder 66 | is saved. Usually the same folder as the package in check 67 | \item platform: The platform/OS where the check is taking place 68 | \item isTarBall: Whether the package in check is a source directory or a 69 | tarball 70 | } 71 | } 72 | \section{Fields}{ 73 | 74 | \describe{ 75 | \item{\code{log}}{\code{list()} A running list of all conditions raised (i.e., notes, 76 | warnings, errors)} 77 | 78 | \item{\code{check}}{\code{character(1)} The title of the last check used for logging 79 | purposes.} 80 | 81 | \item{\code{error,warning,note}}{\code{list()} Finer extraction of each condition type} 82 | 83 | \item{\code{metadata}}{\code{list()} A list of additional information relevant to the 84 | package and its state. See details.} 85 | }} 86 | 87 | \section{methods}{ 88 | 89 | \itemize{ 90 | \item \code{add}: Include a condition to the \code{BiocCheck} report 91 | \item \code{addMetadata}: Add metadata to the \code{BiocCheck} object from a 92 | \code{BiocPackage} object 93 | \item \code{getLastCheck}: Obtain the name of the last check run 94 | \item \code{setCheck}: Create a new element in the internal list for a check 95 | \item \code{get}: Extract the list of conditions raised by \code{BiocCheck} 96 | \item \code{getNum}: Tally the number of condition provided by the input 97 | \item \code{zero}: Reset the internal log of the condition provided 98 | \item \code{getBiocCheckDir}: Report and create the \verb{.BiocCheck} 99 | directory as obtained from the metadata 100 | \item \code{composeReport}: Simplify the list structure from the \code{log} and 101 | provide a character vector of conditions raised 102 | \item \code{report}: Write the \verb{00BiocCheck.log} report into the \code{BiocCheck} 103 | folder 104 | \item \code{toJSON}: Write a JSON file to the location indicated with the 105 | conditions raised 106 | \item \code{fromJSON}: Read a JSON file from the location indicated with the 107 | output of previous conditions raised in the check 108 | \item \code{show}: Display the information in the class. Currently empty. 109 | \item \code{show_meta}: Display the metadata information stored in the \code{metadata} 110 | field 111 | } 112 | } 113 | 114 | \examples{ 115 | 116 | bc <- BiocCheck:::.BiocCheck 117 | 118 | } 119 | \seealso{ 120 | \link{Message-class}, \link{BiocPackage-class} 121 | } 122 | -------------------------------------------------------------------------------- /R/findSymbols.R: -------------------------------------------------------------------------------- 1 | .filter_lookback <- function(index, parsedDF, lookback, text) { 2 | vapply(index, function(idx) { 3 | rangeLookback <- seq(idx - length(lookback), idx) 4 | setequal( 5 | parsedDF$text[rangeLookback], c(lookback, text) 6 | ) 7 | }, logical(1L)) 8 | } 9 | 10 | .getTokenTextCode <- function( 11 | parsedf, token, text, notLookback = character(0), hasLookback = character(0) 12 | ) { 13 | cond <- parsedf$token %in% token & parsedf$text %in% text 14 | if (length(notLookback) && any(cond)) { 15 | cond[cond] <- !.filter_lookback(which(cond), parsedf, notLookback, text) 16 | } 17 | if (length(hasLookback) && any(cond)) { 18 | cond[cond] <- .filter_lookback(which(cond), parsedf, hasLookback, text) 19 | } 20 | parsedf[ 21 | cond, 22 | c("line1", "col1", "token", "text"), 23 | drop = FALSE 24 | ] 25 | } 26 | 27 | .grepTokenTextCode <- function(parsedf, token, text) { 28 | parsedf[ 29 | parsedf$token %in% token & grepl(text, parsedf$text), 30 | c("line1", "col1", "token", "text"), 31 | drop = FALSE 32 | ] 33 | } 34 | 35 | findSymbolInParsedCode <- 36 | function(parsedCode, pkgname, symbolName, token, silent=FALSE) 37 | { 38 | matches <- list() 39 | for (filename in names(parsedCode)) 40 | { 41 | df <- parsedCode[[filename]] 42 | matchedrows <- df[which(df$token == token & df$text == symbolName),] 43 | if (nrow(matchedrows) > 0) 44 | { 45 | matches[[filename]] <- matchedrows[, c(1,2)] 46 | } 47 | } 48 | if (token == "SYMBOL_FUNCTION_CALL") 49 | parens <- "()" 50 | else 51 | parens <- "" 52 | for (name in names(matches)) 53 | { 54 | x <- matches[[name]] 55 | for (i in nrow(x)) 56 | { 57 | if (!silent) 58 | { 59 | if (grepl("\\.R$", name, ignore.case=TRUE)) 60 | handleMessage(sprintf( 61 | "Found %s%s in %s (line %s, column %s)", symbolName, 62 | parens, .getDirFiles(name), x[i,1], x[i,2])) 63 | else 64 | handleMessage(sprintf( 65 | "Found %s%s in %s", symbolName, parens, 66 | .getDirFiles(name))) # FIXME test this 67 | } 68 | } 69 | } 70 | length(matches) # for tests 71 | } 72 | 73 | findSymbolsInParsedCode <- 74 | function( 75 | parsedCodeList, symbolNames, tokenTypes, 76 | FUN = .getTokenTextCode, fun = TRUE, ... 77 | ) 78 | { 79 | matches <- structure(vector("list", length(parsedCodeList)), 80 | .Names = names(parsedCodeList)) 81 | allcombos <- expand.grid( 82 | tokenTypes = tokenTypes, 83 | symbolNames = symbolNames, 84 | stringsAsFactors = FALSE 85 | ) 86 | tokenTypes <- allcombos[["tokenTypes"]] 87 | symbolNames <- allcombos[["symbolNames"]] 88 | 89 | for (filename in names(parsedCodeList)) { 90 | df <- parsedCodeList[[filename]] 91 | res <- Map( 92 | function(x, y) { 93 | FUN(parsedf = df, token = x, text = y, ...) 94 | }, 95 | x= tokenTypes, y = symbolNames 96 | ) 97 | res <- do.call(rbind.data.frame, res) 98 | matches[[filename]] <- res 99 | } 100 | 101 | matches <- Filter(nrow, matches) 102 | if (!length(matches)) 103 | return(character(0L)) 104 | 105 | matches <- lapply(names(matches), function(nm) { 106 | dframe <- matches[[nm]] 107 | dframe[["text"]] <- paste0(dframe$text, 108 | ifelse(dframe$token == "SYMBOL_FUNCTION_CALL", "()", "")) 109 | dframe[["filename"]] <- nm 110 | dframe 111 | }) 112 | 113 | matches <- do.call( 114 | function(...) rbind.data.frame(..., make.row.names = FALSE), 115 | matches 116 | ) 117 | matches[] <- lapply(matches, as.character) 118 | apply(matches, 1L, function(rowdf) { 119 | fmttxt <- "%s (line %s, column %s)" 120 | formt <- if (fun) paste0(rowdf["text"], " in ", fmttxt) else fmttxt 121 | sprintf(formt, .getDirFiles(rowdf["filename"]), 122 | rowdf["line1"], rowdf["col1"] 123 | ) 124 | }) 125 | } 126 | 127 | findSymbolsInRFiles <- 128 | function(.BiocPackage, Symbols, tokenType, fun = TRUE, ...) 129 | { 130 | rfiles <- .BiocPackage$RSources 131 | parsedCodes <- lapply( 132 | structure(rfiles, .Names = rfiles), parseFile, 133 | .BiocPackage = .BiocPackage 134 | ) 135 | msg_res <- findSymbolsInParsedCode( 136 | parsedCodeList = parsedCodes, 137 | symbolNames = Symbols, 138 | tokenTypes = tokenType, 139 | fun = fun, ... 140 | ) 141 | unlist(msg_res) 142 | } 143 | 144 | .VigCodeChunkNo <- function(parsedf, tokens) { 145 | if (!nrow(tokens)) 146 | return(integer(0L)) 147 | sChunksInd <- .grepTokenTextCode( 148 | parsedf, "COMMENT", "^## -|^### code chunk" 149 | )[["line1"]] 150 | chunkNo <- vector("integer", nrow(tokens)) 151 | for (i in seq_len(nrow(tokens))) { 152 | numrow <- tokens[["line1"]][i] 153 | chunkInd <- max(sChunksInd[sChunksInd < numrow]) 154 | chunkNo[[i]] <- match(chunkInd, sChunksInd, nomatch = 0L) 155 | } 156 | chunkNo 157 | } 158 | 159 | findSymbolsInVignettes <- 160 | function(.BiocPackage, Symbols, tokenTypes, FUN = .getTokenTextCode, ...) 161 | { 162 | vigfiles <- .BiocPackage$VigSources 163 | shortnames <- .getDirFiles(vigfiles) 164 | viglist <- structure(vector("list", length(vigfiles)), .Names = shortnames) 165 | for (i in seq_along(vigfiles)) { 166 | shortName <- shortnames[i] 167 | tempR <- tempfile(fileext=".R") 168 | try_purl_or_tangle( 169 | input = vigfiles[i], 170 | output = tempR, 171 | quiet = TRUE, 172 | documentation = 2L 173 | ) 174 | pfile <- parseFile(.BiocPackage, tempR) 175 | tokens <- FUN(pfile, tokenTypes, Symbols, ...) 176 | chunkNos <- .VigCodeChunkNo(pfile, tokens) 177 | viglist[[shortName]] <- sprintf( 178 | "%s (chunk no. %d, line %d, column %d)", 179 | shortName, chunkNos, tokens[,"line1"], tokens[,"col1"] 180 | ) 181 | } 182 | Filter(length, viglist) 183 | } 184 | -------------------------------------------------------------------------------- /man/BiocCheck.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/BiocCheck.R 3 | \name{BiocCheck} 4 | \alias{BiocCheck} 5 | \title{Check a package's adherence with the Bioconductor Package Guidelines} 6 | \usage{ 7 | BiocCheck( 8 | package = getwd(), 9 | checkDir = dirname(package), 10 | debug = FALSE, 11 | callr = FALSE, 12 | ... 13 | ) 14 | } 15 | \arguments{ 16 | \item{package}{The path to an R package directory or tarball (\code{.tar.gz}). 17 | The \code{BiocCheck} function is intended to be run from the package 18 | directory; therefore, the current working directory (given by \code{getwd()}) 19 | is the default.} 20 | 21 | \item{checkDir}{The directory where the \code{BiocCheck} output directory will be 22 | stored. By default, it will be placed in the same directory as the package 23 | directory i.e., \code{dirname(pkg_dir)}.} 24 | 25 | \item{debug}{Whether to append the names of functions that correspond to 26 | each condition raised by \code{BiocCheck} in the written log (i.e., in the 27 | \code{'.BiocCheck'} folder). This option is only relevant to 28 | developers and contributors to \code{BiocCheck}.} 29 | 30 | \item{callr}{logical(1) Whether to use the \code{callr} package to run \code{BiocCheck} 31 | in an isolated R session to prevent namespace collisions.} 32 | 33 | \item{\dots}{See the \code{dot-options} details section for available options.} 34 | } 35 | \value{ 36 | \code{BiocCheck()} is chiefly called for the side effect of the check 37 | reporting. The function also creates a \verb{.BiocCheck} folder 38 | and returns a \code{BiocCheck} reference class with three main list elements: 39 | \itemize{ 40 | \item \strong{error}: Items to address before the package can be accepted 41 | \item \strong{warning}: Strongly suggested items that may require attention 42 | \item \strong{note}: Items to consider, though not required, before acceptance 43 | } 44 | } 45 | \description{ 46 | Analyzes an R package for adherence with Bioconductor package guidelines and 47 | best practices. The check outputs are categorized into ERROR, WARNING, and 48 | NOTE. See the vignette for more details. \code{BiocCheck} is complementary 49 | to \verb{R CMD check}, which should always be run first. 50 | } 51 | \details{ 52 | \code{BiocCheck()} reviews R packages for adherence with Bioconductor 53 | package guidelines and best practices. See 54 | \url{https://contributions.bioconductor.org} for the latest guidance for 55 | writing Bioconductor software. Some rationale behind these best practices 56 | can be seen in the vignette and pages in the \code{references} section. The 57 | vignette also provides detailed explanations of all the checks performed by 58 | \code{BiocCheck}. 59 | 60 | \code{BiocCheck} is called within R with \preformatted{ BiocCheck() 61 | } where \code{package} points to the source directory or the \code{.tar.gz} 62 | tarball that was created using \verb{R CMD build}. 63 | 64 | \emph{Note} that \code{BiocCheck} is complementary to \verb{R CMD check}. 65 | \verb{R CMD check} should always be run first for best results. 66 | 67 | To skip installation of the package during the check, set the 68 | \code{install} option to \code{FALSE} or \code{NULL}: 69 | \preformatted{ 70 | BiocCheck(package, install=FALSE) 71 | ## OR 72 | BiocCheck(package, install=NULL) 73 | } 74 | To re-use an existing installation log file, set the \code{install} option 75 | to the name of the installation log file. 76 | For example, the following will put the \code{install_out.txt} log file in the 77 | \verb{.BiocCheck} directory: 78 | \preformatted{ BiocCheck(package, install="check:install_out.txt") } 79 | } 80 | \section{dot-options}{ 81 | 82 | To use the dot-options, \code{BiocCheck} can be called with named arguments 83 | corresponding to the options below. Typically, these options are set to 84 | \code{TRUE} to disable specific checks, e.g., 85 | \preformatted{ BiocCheck(package, `no-check-vignettes`=TRUE) }. Unless 86 | otherwise stated, these options can be left unset (i.e., \code{NULL}) to enable 87 | checks but \code{FALSE} can also be used to explicitly enable them. The available 88 | options are: 89 | \itemize{ 90 | \item \code{build-output-file}: file containing \verb{R CMD build} output, for 91 | additional analysis 92 | \item \code{new-package}: enable checks specific to new packages 93 | \item \code{no-check-bbs}: disable BBS-specific checks (for non-BioC packages). 94 | Valid DESCRIPTION 95 | \item \code{no-check-bioc-help}: disable check for registration on Bioconductor 96 | \item \code{no-check-bioc-views}: disable biocViews-specific checks (for non-BioC 97 | packages) 98 | mailing list and support site 99 | \item \code{no-check-coding-practices}: disable check for some common best coding 100 | practices 101 | \item \code{no-check-CRAN}: disable check for if package exists in CRAN 102 | \item \code{no-check-dependencies}: disable check for bad dependencies 103 | \item \code{no-check-deprecated}: disable check for usage of deprecated packages 104 | \item \code{no-check-description}: disable DESCRIPTION file checks 105 | \item \code{no-check-file-size}: disable check for individual file size 106 | \item \code{no-check-formatting}: disable checks for file formatting 107 | \item \verb{no-check-function-len}: disable check for function length 108 | \item \code{no-check-install-self}: disable check for require or library of 109 | itself 110 | \item \code{no-check-library-calls}: disable check usage of functions that 111 | install or update packages 112 | \item \code{no-check-man-doc}: disable checks for man page documentation 113 | \item \code{no-check-namespace}: disable NAMESPACE file checks 114 | \item \code{no-check-news}: disable checks for NEWS file 115 | \item \code{no-check-pkg-size}: disable check for package tarball size 116 | \item \code{no-check-R-ver}: disable check for valid R version 117 | \item \code{no-check-remotes}: disable check for usage of remote packages other 118 | than those hosted on CRAN or Bioconductor 119 | \item \code{no-check-skip-bioc-tests}: disable check for tests that skip when on 120 | bioc 121 | \item \code{no-check-unit-tests}: disable checks for unit tests 122 | \item \code{no-check-version-num}: disable check for valid version number 123 | \item \code{no-check-vignettes}: disable vignette checks 124 | \item \code{quit-with-status}: enable exit code option when performing check 125 | \item \code{install}: if \code{FALSE}, the package is not installed; otherwise, if not 126 | specified, the package is installed by default. Optionally, a 127 | \verb{check:} key-value pair is provided to identify the name of the 128 | installation output file which will be copied to the 129 | \verb{.BiocCheck} directory. 130 | \item \code{libloc}: when \code{install} is specified, the library location where the 131 | package is installed. By default, this is \code{.libPaths()[1]}. 132 | } 133 | } 134 | 135 | \examples{ 136 | 137 | packageDir <- system.file("testpackages", "testpkg0", package="BiocCheck") 138 | BiocCheck(packageDir, `quit-with-status`=FALSE) 139 | 140 | } 141 | \references{ 142 | \url{https://contributions.bioconductor.org} 143 | } 144 | \seealso{ 145 | \link{BiocCheck-class}, \link{Message-class} 146 | } 147 | \author{ 148 | Dan Tenenbaum, Lori Shepherd, and Marcel Ramos 149 | } 150 | -------------------------------------------------------------------------------- /R/BiocCheckGitClone.R: -------------------------------------------------------------------------------- 1 | .HIDDEN_FILE_EXTS <- c( 2 | ".renviron", ".rprofile", ".rproj", ".rproj.user", ".rhistory", 3 | ".rapp.history", ".o", ".sl", ".so", ".dylib", ".a", ".dll", ".def", 4 | ".ds_store", "unsrturl.bst", ".log", ".aux", ".backups", ".cproject", 5 | ".directory", ".dropbox", ".exrc", ".gdb.history", ".gitattributes", 6 | ".gitmodules", ".hgtags", ".project", ".seed", ".settings", 7 | ".tm_properties", ".rdata" 8 | ) 9 | 10 | # taken from 11 | # https://github.com/wch/r-source/blob/trunk/src/library/tools/R/build.R#L462 12 | # https://github.com/wch/r-source/blob/trunk/src/library/tools/R/check.R#L4025 13 | hidden_file_data <- data.frame( 14 | file_ext = .HIDDEN_FILE_EXTS, 15 | hidden_only = c(TRUE, TRUE, FALSE, TRUE, TRUE, 16 | TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, 17 | TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, 18 | FALSE, FALSE, FALSE, FALSE, TRUE, 19 | TRUE, FALSE, TRUE, FALSE, FALSE, 20 | FALSE, TRUE) 21 | ) 22 | 23 | #' Checks specific to a Git clone of a package repository 24 | #' 25 | #' Analyzes an R package for adherence with Bioconductor package guidelines and 26 | #' best practices. The check outputs are categorized into ERROR, WARNING, and 27 | #' NOTE. This function is typically used in the Bioconductor Build System (BBS) 28 | #' and not intended for general use. 29 | #' 30 | #' `BiocCheckGitClone()` reviews R packages for adherence with 31 | #' Bioconductor package guidelines and best practices. See 32 | #' \url{https://contributions.bioconductor.org} for the latest guidance for 33 | #' writing Bioconductor software. This function should only be run on a source 34 | #' directory and not on a tarball. 35 | #' 36 | #' `BiocCheckGitClone` is called within R with, as \preformatted{ 37 | #' BiocCheckGitClone() } where `package` is the source directory 38 | #' containing the `R` package. 39 | #' 40 | #' @param package A directory containing an R source package. Not a package tar 41 | #' ball. 42 | #' 43 | #' @param \dots Currently, only `quit-with-status` is available. See 44 | #' `BiocCheck` 45 | #' 46 | #' 47 | #' @return `BiocCheckGitClone()` is chiefly called for the side effect of the 48 | #' check reporting. The function returns a `BiocCheck` reference class with 49 | #' three main list elements: 50 | #' 51 | #' * error: Items to address before the package can be accepted 52 | #' 53 | #' * warning: Strongly suggested items that may require attention 54 | #' 55 | #' * note: Items to consider, though not required, before acceptance 56 | #' 57 | #' @author Lori Shepherd 58 | #' 59 | #' @references \url{https://contributions.bioconductor.org} 60 | #' @seealso \link{BiocCheck-class} 61 | #' 62 | #' @examples 63 | #' 64 | #' packageDir <- system.file("testpackages", "testpkg0", package="BiocCheck") 65 | #' BiocCheckGitClone(packageDir, `quit-with-status`=FALSE) 66 | #' 67 | #' @export BiocCheckGitClone 68 | BiocCheckGitClone <- function(package=".", ...) 69 | { 70 | .BiocCheck$zero() 71 | package <- normalizePath(package) 72 | if (!dir.exists(package)) 73 | .stop("Package directory does not exist") 74 | .BiocPackage <- .BiocPackage$initialize(package) 75 | if (.BiocPackage$isTar) 76 | .stop("Run 'BiocCheckGitClone' on the Git-cloned package directory.") 77 | # be careful here: 78 | if (identical(.Platform$OS.type, "windows")) 79 | package <- gsub("\\\\", "/", package) 80 | 81 | dots <- list(...) 82 | if (length(dots) == 1L && is.list(dots[[1]])) 83 | dots <- dots[[1]] # command line args come as list 84 | 85 | oldwarn <- getOption("warn") 86 | oldwidth <- getOption("cli.width") 87 | on.exit({ 88 | options(warn = oldwarn, cli.width = oldwidth) 89 | }) 90 | options(warn = 1, cli.width = 80) 91 | 92 | .BiocCheck$addMetadata(.BiocPackage) 93 | .BiocCheck$show_meta() 94 | 95 | # BiocCheck checks -------------------------------------------------------- 96 | handleCheck("Checking valid files...") 97 | checkBadFiles(.BiocPackage) 98 | 99 | handleCheck("Checking individual file sizes...") 100 | checkIndivFileSizes(.BiocPackage) 101 | checkDataFileSizes(.BiocPackage) 102 | 103 | handleCheck("Checking for stray BiocCheck output folders...") 104 | checkBiocCheckOutputFolder(.BiocPackage) 105 | 106 | handleCheck("Checking for inst/doc folders...") 107 | checkInstDocFolder(.BiocPackage) 108 | 109 | checkDESCRIPTION(.BiocPackage) 110 | checkNAMESPACE(.BiocPackage) 111 | validMaintainer(.BiocPackage) 112 | 113 | handleCheck("Checking CITATION...") 114 | checkForCitationFile(.BiocPackage) 115 | 116 | # BiocCheck results ------------------------------------------------------- 117 | cli::cli_rule( 118 | left = paste0("BiocCheck v", packageVersion("BiocCheck"), " results") 119 | ) 120 | cli::cli_text( 121 | paste0( 122 | "{symbol$cross} { .BiocCheck$getNum('error') } ERRORS | ", 123 | "{symbol$warning} { .BiocCheck$getNum('warning') } WARNINGS | ", 124 | "{symbol$info} { .BiocCheck$getNum('note') } NOTES\n" 125 | ) 126 | ) 127 | cli::cli_alert_info( 128 | "\nFor more details, run\n browseVignettes(package = 'BiocCheck')" 129 | ) 130 | 131 | if (isTRUE(dots[["quit-with-status"]])) { 132 | errcode <- as.integer(.BiocCheck$getNum("error") > 0) 133 | q("no", errcode) 134 | } 135 | 136 | return(.BiocCheck) 137 | } 138 | 139 | # Checks for BiocCheckGitClone -------------------------------------------- 140 | 141 | checkBadFiles <- function(.BiocPackage) { 142 | package_dir <- .BiocPackage$sourceDir 143 | swith <- ifelse(hidden_file_data[["hidden_only"]], .Platform$file.sep, "") 144 | ext_expr <- paste0( 145 | swith, "\\", hidden_file_data[["file_ext"]], "$", collapse = "|" 146 | ) 147 | 148 | fls <- dir(package_dir, recursive=TRUE, all.files=TRUE) 149 | flist <- split(fls, startsWith(fls, "inst")) 150 | warns <- grep(ext_expr, ignore.case = TRUE, flist[['TRUE']], value = TRUE) 151 | errs <- grep(ext_expr, ignore.case = TRUE, flist[['FALSE']], value = TRUE) 152 | 153 | ## use gitignore to filter out false positives 154 | gitignore <- file.path(package_dir, ".gitignore") 155 | if (file.exists(gitignore)) { 156 | gitignore <- readLines(gitignore) 157 | filter_expr <- paste0(utils::glob2rx(gitignore), collapse = "|") 158 | ignored <- grep( 159 | filter_expr, ignore.case = TRUE, flist[["FALSE"]], value = TRUE 160 | ) 161 | errs <- errs[!errs %in% ignored] 162 | } 163 | if (length(warns)) { 164 | handleWarning( 165 | "System files in '/inst' should not be Git tracked.", 166 | messages = warns 167 | ) 168 | } 169 | 170 | if (length(errs)) { 171 | handleError( 172 | "System files found that should not be Git tracked.", 173 | messages = errs 174 | ) 175 | } 176 | } 177 | 178 | checkForCitationFile <- function(.BiocPackage) { 179 | package_dir <- .BiocPackage$sourceDir 180 | citfile_location <- file.path(package_dir, "inst", "CITATION") 181 | if (file.exists(citfile_location)) { 182 | handleCheck( 183 | "Checking that provided CITATION file is correctly formatted..." 184 | ) 185 | cit <- try(readCitationFile(citfile_location), silent = TRUE) 186 | if (is(cit, "try-error")) 187 | handleWarning( 188 | "Unable to read CITATION file with 'utils::readCitationFile()'" 189 | ) 190 | else if (is.null(cit$doi)) 191 | handleWarning( 192 | "The 'doi' argument is missing or empty in the CITATION's ", 193 | "'bibentry()'. Only include a CITATION file if there is a ", 194 | "preprint or publication associated with this Bioconductor ", 195 | "package." 196 | ) 197 | } else { 198 | handleNote( 199 | "(Optional) CITATION file not found. Only include a CITATION ", 200 | "file if there is a preprint or publication for this Bioconductor ", 201 | "package. Note that Bioconductor packages are not required to ", 202 | "have a CITATION file but it is useful both for users and for ", 203 | "tracking Bioconductor project-wide metrics. When including a ", 204 | "CITATION file, add the publication using the 'doi' argument ", 205 | "of 'bibentry()'." 206 | ) 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /R/checkManDocumentation.R: -------------------------------------------------------------------------------- 1 | checkManDocumentation <- function(.BiocPackage, libloc) { 2 | package_dir <- .BiocPackage$sourceDir 3 | package_name <- .BiocPackage$packageName 4 | # canned man prompts 5 | checkForPromptComments(.BiocPackage) 6 | 7 | # non empty value section exists 8 | checkForValueSection(.BiocPackage) 9 | 10 | # exports are documented and 80% runnable 11 | checkExportsAreDocumented(.BiocPackage, lib.loc = libloc) 12 | 13 | # usage of donttest and dontrun 14 | checkUsageOfDont(.BiocPackage) 15 | } 16 | 17 | checkForPromptComments <- function(.BiocPackage) { 18 | manfiles <- .BiocPackage$manSources 19 | bad <- vapply( 20 | manfiles, 21 | function(manpage) { 22 | lines <- readLines(manpage, warn = FALSE) 23 | any(grepl("^%%\\s+~", lines)) 24 | }, 25 | logical(1L) 26 | ) 27 | 28 | if (any(bad)) 29 | handleNote( 30 | "Auto-generated '%% ~' comments found in Rd man pages.", 31 | messages = names(bad)[bad] 32 | ) 33 | } 34 | 35 | .tagListExtract <- function(rd, tags, Tag) { 36 | if (missing(tags)) 37 | tags <- .RdTags(rd) 38 | if (!Tag %in% tags) 39 | character(0L) 40 | else 41 | unlist(rd[tags == Tag], recursive = FALSE) 42 | } 43 | 44 | .tagsExtract <- function(rd, tags, Tag) { 45 | tagList <- .tagListExtract(rd = rd, tags = tags, Tag = Tag) 46 | as.character(tagList) 47 | } 48 | 49 | .valueInParsedRd <- function(rd, tags) { 50 | tagList <- .tagListExtract(rd, tags, "\\value") 51 | values <- Filter(function(x) attr(x, "Rd_tag") != "COMMENT", tagList) 52 | value <- paste(values, collapse = "") 53 | nzchar(trimws(value)) && length(values) 54 | } 55 | 56 | .parse_Rd_pack <- function(manpage, usesRdpack) { 57 | sysfile_rdpack <- system.file(package = "Rdpack") 58 | rdpack_avail <- nzchar(sysfile_rdpack) 59 | if (usesRdpack && rdpack_avail) 60 | rdmacros <- file.path( 61 | sysfile_rdpack, "help", "macros", "refmacros.Rd" 62 | ) 63 | else 64 | rdmacros <- file.path(R.home("share"), "Rd", "macros", "system.Rd") 65 | 66 | tools::parse_Rd(manpage, macros = rdmacros) 67 | } 68 | 69 | .read_all_rds <- function(manpages, usesRdpack) { 70 | lapply( 71 | manpages, 72 | function(manpage, usesRdpack) { 73 | .parse_Rd_pack(manpage, usesRdpack) 74 | }, 75 | usesRdpack = usesRdpack 76 | ) 77 | } 78 | 79 | .formatsInParsedRd <- function(rd, tags) { 80 | formats <- .tagsExtract(rd, tags, "\\format") 81 | value <- paste(formats, collapse = "") 82 | nzchar(trimws(value)) && length(formats) 83 | } 84 | 85 | .skipRdCheck <- function(rd, tags) TRUE 86 | 87 | .whichRdCheck <- function(docType) { 88 | switch( 89 | docType, 90 | internal = , 91 | package = , 92 | class = .skipRdCheck, 93 | data = .formatsInParsedRd, 94 | fun = , 95 | .valueInParsedRd 96 | ) 97 | } 98 | 99 | checkForValueSection <- function(.BiocPackage) { 100 | all_rds <- .read_all_rds(.BiocPackage$manSources, .BiocPackage$usesRdpack) 101 | all_tags <- lapply(all_rds, .RdTags) 102 | docTypes <- mapply(docType, rd = all_rds, tags = all_tags, SIMPLIFY = FALSE) 103 | hasUsage <- vapply( 104 | all_tags, 105 | function(dtag) { 106 | "\\usage" %in% dtag 107 | }, 108 | logical(1L) 109 | ) 110 | isInternal <- mapply( 111 | function(rd, tags, keyword) { 112 | if ("\\keyword" %in% tags) 113 | "internal" %in% unlist(.tagListExtract(rd, tags, "\\keyword")) 114 | else 115 | FALSE 116 | }, 117 | rd = all_rds, 118 | tags = all_tags, 119 | SIMPLIFY = TRUE 120 | ) 121 | docTypes[isInternal] <- "internal" 122 | isData <- docTypes == "data" 123 | docTypes[!lengths(docTypes) | (hasUsage & !isInternal & !isData)] <- "fun" 124 | funs <- lapply(docTypes, .whichRdCheck) 125 | ok <- mapply( 126 | function(afun, rds, atags) { 127 | afun(rds, atags) 128 | }, 129 | afun = funs, 130 | rds = all_rds, 131 | atags = all_tags, 132 | SIMPLIFY = TRUE 133 | ) 134 | dataOK <- ok[isData] 135 | elseOK <- ok[!isData] 136 | if (!all(dataOK)) { 137 | not_oks <- names(dataOK[!dataOK]) 138 | handleWarningFiles( 139 | "Empty or missing \\format sections found in data man page(s).", 140 | messages = not_oks 141 | ) 142 | } 143 | if (!all(elseOK)) { 144 | not_oks <- names(elseOK[!elseOK]) 145 | handleWarningFiles( 146 | "Empty or missing \\value sections found in man page(s).", 147 | messages = not_oks 148 | ) 149 | } 150 | } 151 | 152 | # Which pages document things that are exported? 153 | checkExportsAreDocumented <- function(.BiocPackage, lib.loc) { 154 | pkgdir <- .BiocPackage$sourceDir 155 | pkgname <- .BiocPackage$packageName 156 | uses_rd_pack <- .BiocPackage$usesRdpack 157 | manpages <- .BiocPackage$manSources 158 | pkg_ns <- loadNamespace(pkgname, lib.loc = lib.loc) 159 | exports <- getNamespaceExports(pkg_ns) 160 | ## attempt to unload package namespace 161 | try(unloadNamespace(pkg_ns), silent = TRUE) 162 | badManPages <- character(0) 163 | exportingPagesCount <- 0L 164 | noExamplesCount <- 0L 165 | 166 | for (manpage in manpages) { 167 | rd <- .parse_Rd_pack(manpage, usesRdpack = uses_rd_pack) 168 | tags <- .RdTags(rd) 169 | name <- .tagsExtract(rd, tags = tags, Tag = "\\name") 170 | aliases <- .tagsExtract(rd, tags = tags, Tag = "\\alias") 171 | namesAndAliases <- c(name, aliases) 172 | exportedTopics <- unique(namesAndAliases[namesAndAliases %in% exports]) 173 | if (length(exportedTopics)) { 174 | exportingPagesCount <- exportingPagesCount + 1 175 | } 176 | if ( 177 | length(exportedTopics) && 178 | !doesManPageHaveRunnableExample(rd) 179 | ) { 180 | noExamplesCount <- noExamplesCount + 1 181 | badManPages <- append(badManPages, basename(manpage)) 182 | } 183 | } 184 | 185 | ratio <- (exportingPagesCount - noExamplesCount) / exportingPagesCount 186 | 187 | if (exportingPagesCount > 0 && ratio < 0.8) 188 | handleError( 189 | "At least 80% of man pages documenting exported objects must ", 190 | "have runnable examples.", 191 | help_text = "The following pages do not:", 192 | messages = badManPages 193 | ) 194 | else if (length(badManPages)) 195 | handleNote( 196 | "Consider adding runnable examples to man pages that document ", 197 | "exported objects.", 198 | messages = badManPages 199 | ) 200 | 201 | badManPages # for testing 202 | } 203 | 204 | checkUsageOfDont <- function(.BiocPackage) { 205 | manpages <- .BiocPackage$manSources 206 | 207 | hasBad <- rep(FALSE, length(manpages)) 208 | hasdontrun <- rep(FALSE, length(manpages)) 209 | uses_rd_pack <- .BiocPackage$usesRdpack 210 | for (dx in seq_along(manpages)) { 211 | manpage <- manpages[dx] 212 | rd <- .parse_Rd_pack(manpage, usesRdpack = uses_rd_pack) 213 | hasExamples <- "\\examples" %in% .RdTags(rd) 214 | if (hasExamples) { 215 | rdCode <- as.character(rd) 216 | exampleCode <- rdCode[which(rdCode == "\\examples"):length(rdCode)] 217 | donttest <- "\\donttest" %in% exampleCode 218 | dontrun <- "\\dontrun" %in% exampleCode 219 | ## check for the 'internal' keyword - this will be a false positive 220 | keyword <- .RdTags(rd) == "\\keyword" 221 | internalVec <- FALSE 222 | if (any(keyword)) 223 | internalVec <- vapply( 224 | as.character(rd[keyword]), 225 | grepl, 226 | logical(1L), 227 | pattern = "internal", 228 | USE.NAMES = FALSE 229 | ) 230 | if ((donttest || dontrun) && !any(internalVec)) 231 | hasBad[dx] <- TRUE 232 | 233 | if (dontrun && !any(internalVec)) 234 | hasdontrun[dx] <- TRUE 235 | } 236 | } 237 | if (any(hasBad)) { 238 | perVl <- as.character(round( 239 | length(which(hasBad)) / length(hasBad) * 100 240 | )) 241 | handleNoteFiles( 242 | "Usage of dontrun{} / donttest{} tags found in man page examples. ", 243 | paste0(perVl, "% of man pages use at least one of these tags."), 244 | messages = basename(manpages)[hasBad] 245 | ) 246 | } 247 | if (any(hasdontrun)) 248 | handleNoteFiles( 249 | "Use donttest{} instead of dontrun{}.", 250 | messages = basename(manpages)[hasdontrun] 251 | ) 252 | } 253 | -------------------------------------------------------------------------------- /inst/tinytest/test_checkVignettes.R: -------------------------------------------------------------------------------- 1 | source("helpers.R") 2 | 3 | library(devtools) 4 | library(tinytest) 5 | 6 | # vignettes0 -------------------------------------------------------------- 7 | .BiocCheck$zero() 8 | temp_dir <- tempfile() 9 | .bioctest <- create_test_package( 10 | test_dir = temp_dir, 11 | extraActions = function(path) { 12 | vigdir <- file.path(path, "vignettes") 13 | dir.create(vigdir, recursive = TRUE) 14 | } 15 | ) 16 | BiocCheck:::checkVignetteDir(.bioctest) 17 | checkCounter("No vignette sources in vignettes/ directory.", "error") 18 | .BiocCheck$zero() 19 | 20 | .bioctest <- create_test_package( 21 | test_dir = temp_dir, description = list(Title = "unitTestTempDir"), 22 | extraActions = function(path) { 23 | vigdir <- file.path(path, "vignettes") 24 | dir.create(vigdir, recursive = TRUE) 25 | cat( 26 | "<>=\n## some code\n@\n", 27 | file = file.path(vigdir, "test.Rnw") 28 | ) 29 | } 30 | ) 31 | BiocCheck:::checkVignetteDir(.bioctest) 32 | expect_true( 33 | any( 34 | grepl( 35 | pattern="VignetteIndexEntry", 36 | .BiocCheck$get("warning")[["checkVigTemplate"]] 37 | ) 38 | ) 39 | ) 40 | expect_true( 41 | any( 42 | grepl( 43 | pattern="RMarkdown instead of Sweave", 44 | .BiocCheck$get("warning")[["checkVigTypeRNW"]] 45 | ) 46 | ) 47 | ) 48 | expect_true( 49 | any( 50 | grepl( 51 | pattern="'sessionInfo' not found", 52 | .BiocCheck$get("note")[["checkVigSessionInfo"]] 53 | ) 54 | ) 55 | ) 56 | expect_true( 57 | any( 58 | grepl( 59 | pattern="missing chunk labels", 60 | .BiocCheck$get("note")[["checkChunkLabels"]] 61 | ) 62 | ) 63 | ) 64 | .BiocCheck$zero() 65 | 66 | .bioctest <- create_test_package( 67 | test_dir = temp_dir, 68 | extraActions = function(path) { 69 | instdoc <- file.path(path, "inst", "doc") 70 | dir.create(instdoc, recursive = TRUE) 71 | cat("nothing", file = file.path(instdoc, "test.rnw")) 72 | } 73 | ) 74 | ## check rnw file in inst/doc WARNING 75 | BiocCheck:::checkInstContents(.bioctest) 76 | checkCounter( 77 | "Remove vignette sources from inst/doc", "warning" 78 | ) 79 | .BiocCheck$zero() 80 | 81 | .bioctest <- create_test_package( 82 | test_dir = temp_dir, 83 | extraActions = function(path) { 84 | instdoc <- file.path(path, "inst", "doc") 85 | dir.create(instdoc, recursive = TRUE) 86 | cat("nothing", file = file.path(instdoc, "test.Rmd")) 87 | } 88 | ) 89 | ## check rmd file in inst/doc WARNING 90 | BiocCheck:::checkInstContents(.bioctest) 91 | checkCounter( 92 | "Rmd file in inst/doc not seen as valid vignette source", "warning" 93 | ) 94 | .BiocCheck$zero() 95 | 96 | ## check for Rnw vignettes, warn if any 97 | .bioctest <- read_test_package("testpkg0") 98 | BiocCheck:::checkVigTypeRNW(.bioctest) 99 | expect_equivalent( 100 | .BiocCheck$getNum("warning"), 1L, 101 | info = "check for Rnw vignettes, warn if any" 102 | ) 103 | .BiocCheck$zero() 104 | 105 | ## check for duplicate chunk labels 106 | BiocCheck:::checkDupChunkLabels( 107 | .bioctest$VigSources["vignettes/dupChunks.Rmd"] 108 | ) 109 | expect_true( 110 | any( 111 | grepl( 112 | pattern="duplicate chunk labels", 113 | .BiocCheck$get("error")[["checkDupChunkLabels"]] 114 | ) 115 | ) 116 | ) 117 | .BiocCheck$zero() 118 | 119 | ## check for missing chunk labels in Rmd 120 | BiocCheck:::checkChunkLabels( 121 | .bioctest$VigSources["vignettes/testpkg0.Rmd"] 122 | ) 123 | expect_true( 124 | any( 125 | grepl( 126 | pattern="missing chunk labels", 127 | .BiocCheck$get("note")[["checkChunkLabels"]] 128 | ) 129 | ) 130 | ) 131 | .BiocCheck$zero() 132 | 133 | ## check for missing chunk labels in qmd 134 | BiocCheck:::checkChunkLabels( 135 | .bioctest$VigSources["vignettes/testpkg0.qmd"] 136 | ) 137 | expect_true( 138 | any( 139 | grepl( 140 | pattern="missing chunk labels", 141 | .BiocCheck$get("note")[["checkChunkLabels"]] 142 | ) 143 | ) 144 | ) 145 | .BiocCheck$zero() 146 | 147 | ## check for missing chunk labels in Rnw 148 | BiocCheck:::checkChunkLabels( 149 | .bioctest$VigSources["vignettes/testpkg0.Rnw"] 150 | ) 151 | expect_true( 152 | any( 153 | grepl( 154 | pattern="missing chunk labels", 155 | .BiocCheck$get("note")[["checkChunkLabels"]] 156 | ) 157 | ) 158 | ) 159 | .BiocCheck$zero() 160 | 161 | .bioctest <- create_test_package( 162 | test_dir = temp_dir, description = list(VignetteBuilder = "knitr"), 163 | extraActions = function(path) { 164 | vigdir <- file.path(path, "vignettes") 165 | dir.create(vigdir, recursive = TRUE) 166 | cat( 167 | "% \\VignetteIndexEntry{header} \nnnothing", 168 | file = file.path(vigdir, "test.Rnw") 169 | ) 170 | } 171 | ) 172 | expect_silent( 173 | BiocCheck:::checkVigBuilder(.bioctest) 174 | ) 175 | BiocCheck:::checkVigTypeRNW(.bioctest) 176 | expect_equivalent( 177 | .BiocCheck$getNum("warning"), 1L 178 | ) 179 | .BiocCheck$zero() 180 | 181 | ## check 'SystemRequirements' in DESCRIPTION for qmd 182 | .bioctest <- read_test_package("testpkg0") 183 | BiocCheck:::checkVigTypeQMD(.bioctest) 184 | expect_true( 185 | grepl( 186 | pattern = "'SystemRequirements' field not in DESCRIPTION", 187 | .BiocCheck$get("warning")[["checkVigTypeQMD"]] 188 | ) 189 | ) 190 | .BiocCheck$zero() 191 | 192 | ## check for quarto in 'SystemRequirements' in DESCRIPTION 193 | .bioctest <- create_test_package( 194 | test_dir = temp_dir, 195 | description = list( 196 | VignetteBuilder = "quarto", 197 | SystemRequirements = "azcopy" 198 | ), 199 | extraActions = function(path) { 200 | vigdir <- file.path(path, "vignettes") 201 | dir.create(vigdir, recursive = TRUE) 202 | cat( 203 | "---\n", 204 | "%\\VignetteIndexEntry{A Quarto Vignette}\n", 205 | "%\\VignetteEngine{quarto}\n", 206 | "%\\VignetteEncoding{UTF-8}\n", 207 | "---\n\n", 208 | "# Quarto Vignette\n\n", 209 | "```{r}\n", 210 | "print('This is a Quarto vignette')\n", 211 | "```\n", 212 | sep = "", 213 | file = file.path(vigdir, "test.qmd") 214 | ) 215 | } 216 | ) 217 | BiocCheck:::checkVigTypeQMD(.bioctest) 218 | expect_true( 219 | grepl( 220 | pattern = "'SystemRequirements' does not list 'quarto'", 221 | .BiocCheck$get("warning")[["checkVigTypeQMD"]] 222 | ) 223 | ) 224 | 225 | BiocCheck:::checkVigSessionInfo(.bioctest) 226 | expect_true( 227 | any( 228 | grepl( 229 | pattern = "'sessionInfo' not found", 230 | .BiocCheck$get("note")[["checkVigSessionInfo"]] 231 | ) 232 | ) 233 | ) 234 | .BiocCheck$zero() 235 | 236 | .bioctest <- read_test_package("testpkg0") 237 | BiocCheck:::checkVigTemplate(.bioctest$VigSources["vignettes/testpkg0.Rmd"]) 238 | expect_true( 239 | any( 240 | grepl( 241 | pattern="VignetteIndex", 242 | .BiocCheck$get("warning")[["checkVigTemplate"]] 243 | ) 244 | ) 245 | ) 246 | .BiocCheck$zero() 247 | 248 | BiocCheck:::checkVigEvalAllFalse(.bioctest) 249 | expect_true( 250 | any( 251 | grepl( 252 | "evalfalse.Rmd", 253 | .BiocCheck$get("warning")[["checkVigEvalAllFalse"]] 254 | ) 255 | ) 256 | ) 257 | .BiocCheck$zero() 258 | 259 | .bioctest <- read_test_package("testpkg2") 260 | BiocCheck:::checkVigSuggests(.bioctest) 261 | expect_true( 262 | any( 263 | grepl( 264 | pattern="VignetteBuilder", 265 | .BiocCheck$get("warning")[["checkVigSuggests"]] 266 | ) 267 | ) 268 | ) 269 | .BiocCheck$zero() 270 | 271 | BiocCheck:::checkVigEngine(.bioctest) 272 | expect_true( 273 | any( 274 | grepl( 275 | pattern="VignetteEngine", 276 | .BiocCheck$get("error")[["checkVigEngine"]] 277 | ) 278 | ) 279 | ) 280 | .BiocCheck$zero() 281 | 282 | BiocCheck:::checkVigMetadata(.bioctest$VigSources["vignettes/testpkg0.Rmd"]) 283 | expect_true( 284 | any( 285 | grepl( 286 | pattern="missing vignette metadata", ignore.case = TRUE, 287 | .BiocCheck$get("warning")[["checkVigMetadata"]] 288 | ) 289 | ) 290 | ) 291 | .BiocCheck$zero() 292 | 293 | BiocCheck:::checkVigSuggests(.bioctest) 294 | expect_true( 295 | any( 296 | grepl( 297 | pattern="not currently Suggested", 298 | .BiocCheck$get("warning")[["checkVigSuggests"]] 299 | ) 300 | ) 301 | ) 302 | .BiocCheck$zero() 303 | 304 | BiocCheck:::checkVigChunkEval(.bioctest$VigSources["vignettes/testpkg0.Rmd"]) 305 | expect_true( 306 | grepl( 307 | pattern="Evaluate more vignette chunks", 308 | .BiocCheck$get("warning")[["checkVigChunkEval"]] 309 | ) 310 | ) 311 | .BiocCheck$zero() 312 | 313 | .bioctest <- read_test_package("testpkg2") 314 | BiocCheck:::checkVigFiles(.bioctest) 315 | expect_true( 316 | any( 317 | grepl( 318 | pattern="intermediate files found", 319 | .BiocCheck$get("note")[["checkVigFiles"]] 320 | ) 321 | ) 322 | ) 323 | .BiocCheck$zero() 324 | 325 | BiocCheck:::checkVigEvalAllFalse(.bioctest) 326 | expect_true( 327 | any( 328 | grepl( 329 | "vignettes/testpkg0.Rmd", 330 | .BiocCheck$get("warning")[["checkVigEvalAllFalse"]], 331 | fixed = TRUE, 332 | ) 333 | ) 334 | ) 335 | .BiocCheck$zero() 336 | 337 | unlink(temp_dir, recursive = TRUE) 338 | 339 | -------------------------------------------------------------------------------- /R/checkDESCRIPTION.R: -------------------------------------------------------------------------------- 1 | checkDESCRIPTION <- function(.BiocPackage) { 2 | handleCheck("Checking if DESCRIPTION is well formatted...") 3 | if (!.BiocPackage$isValid) { 4 | handleError("DESCRIPTION is malformed.") 5 | handleMessage(.BiocPackage$readError) 6 | } 7 | } 8 | 9 | validMaintainer <- function(.BiocPackage) { 10 | if (.BiocPackage$isTar) 11 | return() 12 | handleCheck("Checking for valid maintainer...") 13 | dcf <- .BiocPackage$DESCRIPTION 14 | authr <- "Authors@R" %in% colnames(dcf) 15 | autmain <- c("Author","Maintainer") %in% colnames(dcf) 16 | if (authr && any(autmain)) 17 | handleError( 18 | "Use Authors@R field not Author/Maintainer fields. Do not use both." 19 | ) 20 | else if (any(autmain)) 21 | handleError("Do not use Author/Maintainer fields. Use Authors@R.") 22 | } 23 | 24 | checkDESCRIPTIONFile <- function(.BiocPackage) { 25 | dcf <- .BiocPackage$DESCRIPTION 26 | 27 | checkLicenseForRestrictiveUse(dcf[, "License"]) 28 | checkDESCfields(dcf) 29 | checkBiocDepsDESC(dcf) 30 | checkPinnedDeps(dcf) 31 | checkFndPerson(dcf) 32 | } 33 | 34 | checkRemotesUsage <- function(.BiocPackage) 35 | { 36 | dcf <- .BiocPackage$DESCRIPTION 37 | if ("Remotes" %in% colnames(dcf)) 38 | handleError( 39 | "Package dependencies must be on CRAN or Bioconductor.", 40 | " Remove 'Remotes:' from DESCRIPTION" 41 | ) 42 | } 43 | 44 | checkNewPackageVersionNumber <- function(.BiocPackage) 45 | { 46 | dcf <- .BiocPackage$DESCRIPTION 47 | version <- dcf[, "Version"] 48 | if (!grepl("^0+[-.][0-9]+[-.][0-9]+$", version)) 49 | handleWarning( 50 | "New package x version starting with non-zero value ", 51 | "(e.g., 1.y.z, 2.y.z); got ", sQuote(version), ".") 52 | if (!grepl("^[0-9]+[-.]99[-.][0-9]+$", version)) 53 | handleError( 54 | "New package 'y' version not 99 (i.e., x.99.z)", 55 | "; Package version: ", version 56 | ) 57 | } 58 | 59 | checkForVersionNumberMismatch <- function(.BiocPackage) 60 | { 61 | if (!.BiocPackage$isTar) 62 | return() 63 | tarfilename <- .BiocPackage$tarFilename 64 | ver <- tail(unlist(strsplit(tarfilename, "_|\\.tar\\.[xg]z")), 1L) 65 | dcf <- .BiocPackage$DESCRIPTION 66 | dcfVer <- unname(dcf[, "Version"]) 67 | if (!identical(ver, dcfVer)) 68 | { 69 | handleError( 70 | "Version number in tarball filename must match Version field ", 71 | "in DESCRIPTION. (Tip: create tarball with R CMD build)") 72 | } 73 | } 74 | 75 | checkLazyDataUsage <- function(.BiocPackage) 76 | { 77 | dcf <- .BiocPackage$DESCRIPTION 78 | if ("LazyData" %in% colnames(dcf) && 79 | tools:::str_parse_logic(dcf[, "LazyData"])) 80 | handleNote( 81 | "'LazyData:' in the 'DESCRIPTION' should be set to false or removed" 82 | ) 83 | } 84 | 85 | checkVersionNumber <- function(.BiocPackage) 86 | { 87 | version <- .BiocPackage$packageVersion 88 | regex <- "^[0-9]+[-\\.]([0-9]+)[-\\.][0-9]+$" 89 | if(!grepl(regex, version)) 90 | { 91 | handleError( 92 | "Invalid package Version, see ", 93 | "https://contributions.bioconductor.org/versionnum.html" 94 | ) 95 | return() 96 | } 97 | tryCatch({ 98 | pv <- package_version(version) 99 | }, error = function(e) { 100 | handleError(conditionMessage(e)) 101 | }) 102 | x <- pv$major 103 | y <- pv$minor 104 | mod <- y %% 2 105 | isDevel <- identical( 106 | BiocManager:::.version_bioc("devel"), BiocManager::version() 107 | ) 108 | bioc.mod <- as.numeric(isDevel) 109 | if (identical(x, 0L)) { 110 | handleMessage("Package version ", as.character(pv), "; pre-release") 111 | } else if (mod != bioc.mod) { 112 | shouldBe <- ifelse(isDevel, "odd", "even") 113 | vers <- ifelse(isDevel, "devel", "release") 114 | handleWarning( 115 | "y of x.y.z version should be ", shouldBe, " in ", vers 116 | ) 117 | } 118 | } 119 | 120 | .PersonsFromDCF <- function( 121 | dcf, 122 | field = c("Authors@R", "Author"), 123 | .dreturn = NULL 124 | ) { 125 | field <- match.arg(field) 126 | if (identical(field, "Author")) 127 | return(dcf[, field]) 128 | env <- new.env(parent = emptyenv()) 129 | env[["c"]] <- c 130 | env[["person"]] <- utils::person 131 | pp <- parse(text = dcf[, field], keep.source = TRUE) 132 | tryCatch({ 133 | eval(pp, env) 134 | }, error = function(e) { 135 | .dreturn 136 | }) 137 | } 138 | 139 | .MainEmailAuthorsAtR <- function(dcf) { 140 | email <- NULL 141 | people <- 142 | .PersonsFromDCF(dcf, "Authors@R", .dreturn = NULL) 143 | for (person in people) { 144 | if ("cre" %in% person$role) { 145 | email <- person$email 146 | break 147 | } 148 | } 149 | email 150 | } 151 | 152 | .MainEmailMaintainer <- function(dcf) { 153 | res <- unname( 154 | gsub(".*<(.*)>", "\\1", dcf[, "Maintainer"]) 155 | ) 156 | if (!nzchar(res)) NULL else res 157 | } 158 | 159 | pullMaintainerEmail <- function(pkgpath) { 160 | desc <- file.path(pkgpath, "DESCRIPTION") 161 | stopifnot(file.exists(desc)) 162 | dcf <- read.dcf(desc) 163 | if ("Maintainer" %in% colnames(dcf)) 164 | .MainEmailMaintainer(dcf) 165 | else if ("Authors@R" %in% colnames(dcf)) 166 | .MainEmailAuthorsAtR(dcf) 167 | } 168 | 169 | getMaintainerEmail <- function(.BiocPackage) 170 | { 171 | # Eventually update this to just look at Authors@R 172 | # Since the intention is to possible start running 173 | # this on the daily builder, leave Maintainer field 174 | # check. This is used to check for mailing list registration 175 | dcf <- .BiocPackage$DESCRIPTION 176 | if ("Maintainer" %in% colnames(dcf)) 177 | .MainEmailMaintainer(dcf) 178 | else if ("Authors@R" %in% colnames(dcf)) 179 | .MainEmailAuthorsAtR(dcf) 180 | } 181 | 182 | checkRVersionDependency <- function(.BiocPackage) { 183 | dcf <- .BiocPackage$DESCRIPTION 184 | if ("Depends" %in% colnames(dcf)) { 185 | res <- cleanupDependency(dcf[, "Depends"], FALSE) 186 | verStr <- names(res)[res == "R"] 187 | if (isScalarCharacter(verStr)) { 188 | pkgVer <- package_version(verStr) 189 | RVer <- package_version( 190 | paste0(BiocManager:::.version_field("R"), ".0") 191 | ) 192 | if (pkgVer < RVer) 193 | handleNote( 194 | "Update R version dependency from ", pkgVer, " to ", RVer 195 | ) 196 | } 197 | } 198 | } 199 | 200 | .LICENSE_DB_LOCATION <- "$R_HOME/share/licenses/license.db" 201 | 202 | checkLicenseForRestrictiveUse <- function(license) { 203 | handleCheck("Checking License: for restrictive use...") 204 | 205 | if (!identical(length(license), 1L) || is.na(license)) { 206 | handleNote("malformed 'License:' field '", license, "'") 207 | return(invisible()) 208 | } 209 | ldb_file <- file.path(R.home("share"), "licenses", "license.db") 210 | if (!file.exists(ldb_file)) { 211 | handleNote( 212 | "license database not found. ", 213 | "Expected location: '", ldb_file, "'. ", 214 | "License: '", license, "'" 215 | ) 216 | return(invisible()) 217 | } 218 | licenses <- read.dcf(ldb_file) 219 | result <- tools:::analyze_licenses(license, licenses) 220 | test <- result[["restricts_use"]] 221 | if (isTRUE(test)) 222 | handleError("License '", license, "' restricts use") 223 | else if (is.na(test) || !result[, "is_verified"]) { 224 | handleNote( 225 | "License '", license, "' unknown; refer to ", .LICENSE_DB_LOCATION 226 | ) 227 | handleMessage( 228 | "and https://choosealicense.com/appendix/ for more info.", 229 | indent = 6L 230 | ) 231 | } 232 | } 233 | 234 | checkDESCfields <- function(dcf) { 235 | handleCheck("Checking for recommended DESCRIPTION fields...") 236 | fields <- c("URL", "BugReports") 237 | if ("Date" %in% colnames(dcf)) { 238 | date <- dcf[, "Date"] 239 | if (!grepl("^\\d{4}-\\d{2}-\\d{2}$", date)) 240 | handleNote("'Date:' field format is not 'YYYY-MM-DD': ", date) 241 | } 242 | present <- fields %in% colnames(dcf) 243 | res <- fields[!present] 244 | if (length(res)) { 245 | notFields <- paste(shQuote(res), collapse = ", ") 246 | handleNote("Provide ", notFields, " field(s) in DESCRIPTION") 247 | } 248 | } 249 | 250 | checkBiocDepsDESC <- function(dcf, which = c("Depends", "Imports")) { 251 | handleCheck("Checking for Bioconductor software dependencies...") 252 | which_fields <- dcf[, colnames(dcf) %in% which] 253 | all_deps <- unlist( 254 | lapply(which_fields, function(x) strsplit(x, ",\\s*")[[1L]]), 255 | use.names = FALSE 256 | ) 257 | all_deps <- gsub("(\\w+)\\s+\\(.*\\)$", "\\1", all_deps) 258 | all_deps <- all_deps[all_deps != "R"] 259 | repo <- BiocManager:::.repositories_bioc(BiocManager::version())["BioCsoft"] 260 | biocdb <- utils::available.packages(repos = repo) 261 | bioc_deps <- all_deps %in% rownames(biocdb) 262 | percent <- unname(round(prop.table(table(bioc_deps))["TRUE"], 2L) * 100) 263 | 264 | if (!any(bioc_deps)) { 265 | views <- .BiocPackage$getBiocViews() 266 | handleFUN <- 267 | if ("Infrastructure" %in% views) handleNote else handleWarning 268 | msg <- "No Bioconductor dependencies detected. Note that some 269 | infrastructure packages may not have Bioconductor dependencies. 270 | For more information, reach out to the Bioconductor community 271 | and/or consider a CRAN submission." 272 | handleFUN(msg) 273 | } else { 274 | handleMessage( 275 | "Bioconductor dependencies found in Imports & Depends (", 276 | percent, 277 | "%)." 278 | ) 279 | } 280 | } 281 | 282 | checkPinnedDeps <- function(dcf) { 283 | handleCheck("Checking for pinned package versions in DESCRIPTION...") 284 | deps <- c("Depends", "Imports", "Suggests", "Enhances", "LinkingTo") 285 | validdeps <- deps[deps %in% colnames(dcf)] 286 | doubleeq <- grepl("==", dcf[, validdeps], fixed = TRUE) 287 | if (any(doubleeq)) 288 | handleError("Dependencies in the DESCRIPTION file contain '=='") 289 | } 290 | 291 | checkFndPerson <- function(dcf) { 292 | handleCheck("Checking for 'fnd' role in Authors@R...") 293 | field <- if ("Authors@R" %in% colnames(dcf)) "Authors@R" else "Author" 294 | people <- .PersonsFromDCF(dcf, field, .dreturn = "") 295 | msg <- "No 'fnd' role found in Authors@R. If the work is supported 296 | by a grant, consider adding the 'fnd' role to the list of authors." 297 | if (!any(grepl("fnd", people, fixed = TRUE))) 298 | handleNote(msg) 299 | } 300 | -------------------------------------------------------------------------------- /R/BiocCheck-class.R: -------------------------------------------------------------------------------- 1 | # BiocCheck-class --------------------------------------------------------- 2 | 3 | #' @name BiocCheck-class 4 | #' 5 | #' @docType class 6 | #' 7 | #' @title An internal class for composing BiocCheck reports 8 | #' 9 | #' @description The `BiocCheck` class provides a framework for reporting checks 10 | #' based on Bioconductor guidelines. The class has several methods for working 11 | #' with the provided checks that handle and display messages and the display 12 | #' of the metadata. These methods also record the output of the `BiocCheck()` 13 | #' report in both plain text and JSON formats. 14 | #' 15 | #' **Note** that currently, multiple `BiocCheck` runs will interfere with 16 | #' each other given that they are implemented via a reference class semantic. 17 | #' When running multiple checks in the same session, you can separate these 18 | #' instances by running them in separate processes (e.g., via `BiocParallel`). 19 | #' 20 | #' @details The metadata includes a number of standard fields to allow easier 21 | #' troubleshooting and display of potentially relevant information. Currently, 22 | #' the fields included are: 23 | #' 24 | #' * BiocCheckVersion: The version of the BiocCheck package 25 | #' * BiocVersion: The version of Bioconductor 26 | #' * Package: The name of the package in check 27 | #' * PackageVersion: The version of the package in check 28 | #' * sourceDir: The directory of the package source or tarball in check 29 | #' * installDir: The directory where the package is installed for 30 | #' testing, a temporary location by default 31 | #' * BiocCheckDir: The directory where the `.BiocCheck` folder 32 | #' is saved. Usually the same folder as the package in check 33 | #' * platform: The platform/OS where the check is taking place 34 | #' * isTarBall: Whether the package in check is a source directory or a 35 | #' tarball 36 | #' 37 | #' @field log `list()` A running list of all conditions raised (i.e., notes, 38 | #' warnings, errors) 39 | #' 40 | #' @field check `character(1)` The title of the last check used for logging 41 | #' purposes. 42 | #' 43 | #' @field error,warning,note `list()` Finer extraction of each condition type 44 | #' 45 | #' @field metadata `list()` A list of additional information relevant to the 46 | #' package and its state. See details. 47 | #' 48 | #' @param ... `character()` A vector that makes up the `BiocCheck` exception 49 | #' message (e.g., 'Vignette must be built by R CMD build'). The character 50 | #' vector is handled with `paste0` and made into a list and appended with 51 | #' `help_text` and `messages`. 52 | #' 53 | #' @param help_text `character(1)` Additional text prompting a list of files 54 | #' (e.g,. "Found in files:") 55 | #' 56 | #' @param condition `character(1)` One of the three conditions handled: `error`, 57 | #' `warning`, or `note` 58 | #' 59 | #' @param messages `character()` Often a vector of file names where the check 60 | #' was triggered. 61 | #' 62 | #' 63 | #' @param debug `logical(1)` Whether to append the name of the originating check 64 | #' name into for trace-ability 65 | #' 66 | #' @param checkName `character(1)` The title of the current group of checks. It 67 | #' can be set with `handleCheck`, e.g., 68 | #' `handleCheck("Checking for version number mismatch...")`. Internally, it 69 | #'' is saved with `setCheck` and obtained with `getLastCheck`. 70 | #' 71 | #' @param isOnBBS `logical(1)` Indicates whether the checks are being run on the 72 | #' Bioconductor Build System (BBS). This is helpful for avoiding the creation 73 | #' of folders in the BBS. 74 | #' 75 | #' @param file `character(1)` A path to a JSON file for writing or reading as 76 | #' created by `toJSON` and `fromJSON` `BiocCheck` methods. 77 | #' 78 | #' @importFrom BiocBaseUtils checkInstalled 79 | #' @importFrom utils tail 80 | #' 81 | #' @section methods: 82 | #' * `add`: Include a condition to the `BiocCheck` report 83 | #' * `addMetadata`: Add metadata to the `BiocCheck` object from a 84 | #' `BiocPackage` object 85 | #' * `getLastCheck`: Obtain the name of the last check run 86 | #' * `setCheck`: Create a new element in the internal list for a check 87 | #' * `get`: Extract the list of conditions raised by `BiocCheck` 88 | #' * `getNum`: Tally the number of condition provided by the input 89 | #' * `zero`: Reset the internal log of the condition provided 90 | #' * `getBiocCheckDir`: Report and create the `.BiocCheck` 91 | #' directory as obtained from the metadata 92 | #' * `composeReport`: Simplify the list structure from the `log` and 93 | #' provide a character vector of conditions raised 94 | #' * `report`: Write the `00BiocCheck.log` report into the `BiocCheck` 95 | #' folder 96 | #' * `toJSON`: Write a JSON file to the location indicated with the 97 | #' conditions raised 98 | #' * `fromJSON`: Read a JSON file from the location indicated with the 99 | #' output of previous conditions raised in the check 100 | #' * `show`: Display the information in the class. Currently empty. 101 | #' * `show_meta`: Display the metadata information stored in the `metadata` 102 | #' field 103 | #' 104 | #' @return An internal `BiocCheck` R5 Reference Class used to document 105 | #' conditions such as errors, warnings, and notes 106 | #' 107 | #' @seealso \link{Message-class}, \link{BiocPackage-class} 108 | #' 109 | #' @examples 110 | #' 111 | #' bc <- BiocCheck:::.BiocCheck 112 | #' 113 | #' @exportClass BiocCheck 114 | NULL 115 | 116 | .BiocCheck <- setRefClass("BiocCheck", 117 | fields = list( 118 | log = "list", 119 | # checkName 120 | check = "character", 121 | # conditions 122 | error = "list", 123 | warning = "list", 124 | note = "list", 125 | metadata = "list" 126 | ), 127 | methods = list( 128 | initialize = function(...) { 129 | callSuper(...) 130 | }, 131 | add = function( 132 | ..., condition, help_text, messages 133 | ) { 134 | if (missing(condition)) 135 | stop( 136 | " 'condition' should be:", 137 | " 'error', 'warning', or 'note'" 138 | ) 139 | checkName <- .self$getLastCheck() 140 | mlist <- list(...)[[1]] 141 | stopifnot( 142 | " Input to '$add' must be a list" = is.list(mlist) 143 | ) 144 | ins <- Filter(length, list(mlist, help_text, messages)) 145 | nist <- structure(list(ins), .Names = names(mlist)) 146 | .messages$setMessage(nist, condition = condition) 147 | .self[[condition]] <- append(.self[[condition]], nist) 148 | .self$log[[checkName]] <- append(.self$log[[checkName]], nist) 149 | }, 150 | addMetadata = function(BiocPackage, ...) { 151 | args <- list(...) 152 | .self$metadata <- list( 153 | sourceDir = BiocPackage$sourceDir, 154 | BiocVersion = as.character(BiocManager::version()), 155 | Package = BiocPackage$packageName, 156 | PackageVersion = BiocPackage$packageVersion, 157 | BiocCheckDir = BiocPackage$BiocCheckDir, 158 | BiocCheckVersion = as.character(packageVersion("BiocCheck")), 159 | sourceDir = BiocPackage$sourceDir, 160 | installDir = args[["installDir"]], 161 | isTarBall = BiocPackage$isTar, 162 | platform = .Platform$OS.type 163 | ) 164 | }, 165 | getLastCheck = function() { 166 | checkName <- .self$check 167 | if (!length(checkName)) 168 | "undefined" 169 | else 170 | checkName 171 | }, 172 | setCheck = function(checkName) { 173 | .self$check <- checkName 174 | ## create a list for appending 175 | .self$log[[checkName]] <- list() 176 | }, 177 | get = function(condition) { 178 | cond <- .self[[condition]] 179 | if (length(cond)) { 180 | length_elements <- vapply( 181 | cond, 182 | function(x) length(unlist(x, use.names = FALSE)), 183 | integer(1L) 184 | ) 185 | split( 186 | unlist(cond, use.names = FALSE), 187 | rep(names(cond), length_elements) 188 | ) 189 | } else { 190 | cond 191 | } 192 | }, 193 | getNum = function(conditions = c("error", "warning", "note")) { 194 | vapply( 195 | conditions, 196 | function(condition) { 197 | length(.self[[condition]]) 198 | }, 199 | integer(1L) 200 | ) 201 | }, 202 | zero = function(conditions = c("error", "warning", "note")) { 203 | for (condition in conditions) { 204 | .self[[condition]] <- list() 205 | } 206 | }, 207 | getBiocCheckDir = function() { 208 | bioccheck_dir <- .self$metadata$BiocCheckDir 209 | if (!dir.exists(bioccheck_dir)) 210 | dir.create(bioccheck_dir, recursive = TRUE) 211 | bioccheck_dir 212 | }, 213 | composeReport = function(debug = FALSE) { 214 | unlist(Map( 215 | f = function(...) { 216 | .composeReport(..., debug = debug) 217 | }, 218 | checkName = names(.self$log), 219 | lowerElements = lapply(.self$log, .flattenElement) 220 | ), use.names = FALSE) 221 | }, 222 | report = function(debug, isOnBBS) { 223 | if (isOnBBS) 224 | return() 225 | bioccheck_dir <- .self$getBiocCheckDir() 226 | outputs <- .self$composeReport(debug = debug) 227 | writeLines( 228 | outputs, con = file.path(bioccheck_dir, "00BiocCheck.log") 229 | ) 230 | }, 231 | toJSON = function(file) { 232 | out <- Filter(length, .self$log) 233 | checkInstalled("jsonlite") 234 | jlog <- jsonlite::toJSON(out, auto_unbox = FALSE) 235 | jsonlite::write_json(jlog, file) 236 | }, 237 | fromJSON = function(file) { 238 | checkInstalled("jsonlite") 239 | infile <- jsonlite::read_json(file)[[1]] 240 | .self[["log"]] <- jsonlite::fromJSON(infile, simplifyVector = FALSE) 241 | }, 242 | show = function() { 243 | invisible() 244 | }, 245 | show_meta = function() { 246 | meta <- .self$metadata 247 | if (!length(meta)) 248 | stop(" No metadata to show.") 249 | lapply( 250 | paste(names(meta), meta, sep = ": "), 251 | cli::cli_alert 252 | ) 253 | } 254 | ) 255 | ) 256 | 257 | .flattenElement <- function(listElem) { 258 | debugFun <- names(listElem) 259 | lowerElem <- unlist(listElem, use.names = FALSE) 260 | attributes(lowerElem) <- list(debugNames = debugFun) 261 | lowerElem 262 | } 263 | 264 | .composeReport <- function(checkName, lowerElements, debug = FALSE) { 265 | if (!length(lowerElements)) 266 | checkName <- paste(checkName, "OK") 267 | else if (debug) 268 | lowerElements <- 269 | c(lowerElements, paste("DEBUG:", attr(lowerElements, "debugNames"))) 270 | c(checkName, lowerElements) 271 | } 272 | 273 | ## singletons. Exported but 'hidden' from ls() by the '.' 274 | 275 | #' @export 276 | .BiocCheck <- .BiocCheck() 277 | -------------------------------------------------------------------------------- /R/BiocPackage-class.R: -------------------------------------------------------------------------------- 1 | # BiocPackage-class ------------------------------------------------------- 2 | 3 | #' @name BiocPackage-class 4 | #' 5 | #' @docType class 6 | #' 7 | #' @aliases BiocPackage 8 | #' 9 | #' @title A class for representing files in a Bioconductor package 10 | #' 11 | #' @description The BiocPackage class is used to represent a Bioconductor 12 | #' package. It is used by BiocCheck to store information about the package 13 | #' being checked. The class has several methods to identify the type of 14 | #' package, check for common issues, and store metadata about the package. 15 | #' 16 | #' @field isValid `logical` indicating whether the package's `DESCRIPTION` file 17 | #' was able to be read without any errors 18 | #' 19 | #' @field isTar `logical` indicating whether the package is a tarball 20 | #' 21 | #' @field isSourceDir `logical` indicating whether the package being checked is 22 | #' from a source directory 23 | #' 24 | #' @field isInfrastructure `logical` indicating whether the package is an 25 | #' Bioconductor infrastructure package based on the `biocViews` field 26 | #' 27 | #' @field usesRoxygen `logical` indicating whether the package uses `roxygen2` 28 | #' documentation 29 | #' 30 | #' @field usesRdpack `logical` indicating whether the package uses `Rdpack` 31 | #' package 32 | #' 33 | #' @field DESCRIPTION `matrix` containing the DCF contents of the `DESCRIPTION` 34 | #' file 35 | #' 36 | #' @field dependencies `character` vector of package dependencies 37 | #' 38 | #' @field readError `character` error message if the `DESCRIPTION` file could 39 | #' not be read 40 | #' 41 | #' @field packageVersion `character` version of the package 42 | #' 43 | #' @field packageType `character` indicating the type of package based on the 44 | #' `biocViews` field; can be `NA_character_` there are invalid `biocViews` 45 | #' terms 46 | #' 47 | #' @field sourceDir `character` path to the source directory 48 | #' 49 | #' @field vignettesDir `character` path to the vignettes directory 50 | #' 51 | #' @field RSources `character` vector of R source files 52 | #' 53 | #' @field VigSources `character` vector of vignette source files 54 | #' 55 | #' @field manSources `character` vector of Rd source files 56 | #' 57 | #' @field BiocCheckDir `character` path to the directory where the package 58 | #' BiocCheck logs are written 59 | #' 60 | #' @field packageName `character` name of the package 61 | #' 62 | #' @field tarFilename `character` filename of the tarball 63 | #' 64 | #' @field metadata `list` containing metadata about the package 65 | #' 66 | #' @return An object of class `BiocPackage` 67 | #' 68 | #' @keywords internal 69 | #' 70 | #' @section methods: 71 | #' 72 | #' * `initialize`: Initialize a `BiocPackage` object 73 | #' * `getPackageDir`: Get the package directory 74 | #' * `getRSources`: Get the R source files 75 | #' * `getVigSources`: Get the vignette source files 76 | #' * `getManSources`: Get the Rd source files 77 | #' * `getBiocCheckDir`: Get the directory where the BiocCheck logs are written 78 | #' * `getBiocViews`: Get the `biocViews` field from the `DESCRIPTION` file 79 | #' * `getPackageType`: Get the package type based on the `biocViews` field 80 | #' * `readDESCRIPTION`: Read the `DESCRIPTION` file 81 | #' * `getVigBuilder`: Get the vignette builder 82 | #' * `getAllDependencies`: Get all dependencies from the `DESCRIPTION` file 83 | #' * `findInfrastructure`: Is the package an infrastructure package? 84 | #' * `findRoxygen`: Does the package use `roxygen2`? 85 | #' * `getPackageVersion`: Get the package version 86 | #' * `untarTarball`: Untar the source tarball 87 | #' 88 | #' @seealso \link{BiocCheck-class}, \link{Message-class} 89 | #' @examples 90 | #' 91 | #' # Create a BiocPackage object 92 | #' packageDirectory <- "path/to/package" 93 | #' if (dir.exists(packageDirectory)) 94 | #' .bioctest <- .BiocPackage$initialize(packageDirectory) 95 | #' 96 | #' .bioctest <- BiocCheck:::.BiocPackage 97 | #' 98 | #' .bioctest$DESCRIPTION 99 | #' 100 | #' @importFrom utils untar 101 | #' @exportClass BiocPackage 102 | .BiocPackage <- setRefClass( 103 | "BiocPackage", 104 | fields = list( 105 | isValid = "logical", 106 | isTar = "logical", 107 | isSourceDir = "logical", 108 | isInfrastructure = "logical", 109 | usesRoxygen = "logical", 110 | usesRdpack = "logical", 111 | DESCRIPTION = "matrix", 112 | dependencies = "character", 113 | readError = "character", 114 | packageVersion = "character", 115 | packageType = "character", 116 | sourceDir = "character", 117 | vignettesDir = "character", 118 | RSources = "character", 119 | VigSources = "character", 120 | VigBuilder = "character", 121 | manSources = "character", 122 | BiocCheckDir = "character", 123 | packageName = "character", 124 | tarFilename = "character", 125 | metadata = "list" 126 | ), 127 | methods = list( 128 | initialize = function(packageDir, checkDir = dirname(packageDir), ...) { 129 | if (!missing(packageDir)) { 130 | packageDir <- normalizePath(packageDir) 131 | .self[["isTar"]] <- grepl("\\.tar\\.[gx]z$", packageDir) 132 | .self[["isSourceDir"]] <- 133 | !.self$isTar && file.info(packageDir)[["isdir"]] 134 | .self$getPackageDir(packageDir) 135 | .self$getRSources() 136 | .self$getVigSources() 137 | .self$getManSources() 138 | .self$readDESCRIPTION() 139 | .self$getVigBuilder() 140 | .self$getAllDependencies() 141 | .self$getBiocCheckDir(checkDir) 142 | .self$getPackageType() 143 | .self$findInfrastructure() 144 | .self$findRoxygen() 145 | } 146 | callSuper(...) 147 | }, 148 | getPackageDir = function(packageDir) { 149 | if (.self[["isTar"]]) { 150 | .self[["tarFilename"]] <- packageDir 151 | packageDir <- .self$untarTarball(packageDir) 152 | } else if (!file.info(packageDir)[["isdir"]]) { 153 | .stop( 154 | "'%s' is not a directory or package source tarball.", 155 | packageDir 156 | ) 157 | } 158 | if (identical(.Platform$OS.type, "windows")) 159 | packageDir <- gsub("\\\\", "/", packageDir) 160 | .self[["sourceDir"]] <- packageDir 161 | }, 162 | getRSources = function() { 163 | Rdir <- file.path(.self[["sourceDir"]], "R") 164 | rfiles <- 165 | list.files(Rdir, pattern = "\\.[Rr]$", full.names = TRUE) 166 | if (length(rfiles)) 167 | names(rfiles) <- paste0("R/", basename(rfiles)) 168 | .self[["RSources"]] <- rfiles 169 | }, 170 | getVigSources = function() { 171 | .self[["vignettesDir"]] <- 172 | file.path(.self[["sourceDir"]], "vignettes") 173 | vigfiles <- list.files( 174 | .self[["vignettesDir"]], 175 | pattern="\\.Rmd$|\\.qmd$|\\.Rnw$|\\.Rrst$|\\.Rhtml$|\\.Rtex$", 176 | ignore.case=TRUE, full.names=TRUE 177 | ) 178 | if (length(vigfiles)) 179 | names(vigfiles) <- paste0("vignettes/", basename(vigfiles)) 180 | .self[["VigSources"]] <- vigfiles 181 | }, 182 | getManSources = function() { 183 | mandir <- file.path(.self[["sourceDir"]], "man") 184 | manfiles <- list.files( 185 | mandir, pattern="\\.Rd$", full.names = TRUE, ignore.case = TRUE 186 | ) 187 | if (length(manfiles)) 188 | names(manfiles) <- paste0("man/", basename(manfiles)) 189 | .self[["manSources"]] <- manfiles 190 | }, 191 | getBiocCheckDir = function(checkDir) { 192 | checkDir <- normalizePath(checkDir, winslash = "/") 193 | .self[["BiocCheckDir"]] <- file.path( 194 | checkDir, paste(.self[["packageName"]], "BiocCheck", sep = ".") 195 | ) 196 | }, 197 | getBiocViews = function() { 198 | views <- "" 199 | dcf <- .self[["DESCRIPTION"]] 200 | if ("biocViews" %in% colnames(dcf)) 201 | views <- strsplit(dcf[, "biocViews"], "\\s*,\\s*")[[1]] 202 | views 203 | }, 204 | getPackageType = function() { 205 | views <- .self$getBiocViews() 206 | if (identical(length(views), 1L) && !nzchar(views)) 207 | type <- NA 208 | biocViewsVocab <- .load_data("biocViewsVocab", "biocViews") 209 | if (any(!views %in% nodes(biocViewsVocab))) 210 | type <- NA 211 | parents <- vapply(views, getParent, character(1L), biocViewsVocab) 212 | u <- unique(parents) 213 | type <- if (identical(length(u), 1L)) u else NA_character_ 214 | .self[["packageType"]] <- type 215 | }, 216 | readDESCRIPTION = function() { 217 | desc <- file.path(.self[["sourceDir"]], "DESCRIPTION") 218 | dcf <- try({ read.dcf(desc) }, silent=TRUE) 219 | .self[["isValid"]] <- !inherits(dcf, "try-error") 220 | if (.self[["isValid"]]) { 221 | .self[["DESCRIPTION"]] <- dcf 222 | .self[["packageName"]] <- as.character(dcf[, "Package"]) 223 | .self[["packageVersion"]] <- as.character(dcf[, "Version"]) 224 | } else { 225 | .self[["readError"]] <- conditionMessage(attr(dcf, "condition")) 226 | } 227 | }, 228 | getVigBuilder = function() { 229 | dcf <- .self[["DESCRIPTION"]] 230 | if (.self[["isValid"]] && "VignetteBuilder" %in% colnames(dcf)) 231 | .self[["VigBuilder"]] <- unlist( 232 | strsplit(dcf[, "VignetteBuilder"], ",\\s*\\n?"), 233 | use.names = FALSE 234 | ) 235 | }, 236 | getAllDependencies = function() { 237 | dcf <- .self[["DESCRIPTION"]] 238 | fields <- 239 | c("Depends", "Imports", "Suggests", "Enhances", "LinkingTo") 240 | afields <- intersect(fields, colnames(dcf)) 241 | out <- lapply(afields, function(field) { 242 | cleanupDependency(dcf[, field]) 243 | }) 244 | .self[["dependencies"]] <- as.character(unlist(out)) 245 | .self[["usesRdpack"]] <- "Rdpack" %in% out 246 | }, 247 | findInfrastructure = function() { 248 | dcf <- .self[["DESCRIPTION"]] 249 | if (!nrow(dcf) || !"biocViews" %in% colnames(dcf)) 250 | return() 251 | biocViews <- dcf[, "biocViews"] 252 | views <- strsplit(gsub("\\s", "", biocViews), ",")[[1]] 253 | .self[["isInfrastructure"]] <- "Infrastructure" %in% views 254 | }, 255 | findRoxygen = function() { 256 | .self[["usesRoxygen"]] <- 257 | "RoxygenNote" %in% colnames(.self[["DESCRIPTION"]]) 258 | }, 259 | getPackageVersion = function() { 260 | .self[["packageVersion"]] 261 | }, 262 | untarTarball = function(pkgTarball, tempDir = tempfile()) { 263 | if (!dir.exists(tempDir)) 264 | dir.create(tempDir) 265 | suppressMessages({ untar(pkgTarball, exdir = tempDir) }) 266 | .self[["packageName"]] <- 267 | list.dirs(tempDir, recursive = FALSE, full.names = FALSE) 268 | file.path(tempDir, .self[["packageName"]]) 269 | } 270 | ) 271 | ) 272 | 273 | #' @export 274 | .BiocPackage <- .BiocPackage() 275 | -------------------------------------------------------------------------------- /R/util.R: -------------------------------------------------------------------------------- 1 | #' @importFrom graph nodes acc 2 | #' @importFrom tools Rd2ex 3 | #' @importFrom utils Stangle 4 | #' @importFrom codetools walkCode findGlobals 5 | NULL 6 | 7 | .msg <- function(..., appendLF=TRUE, indent=0, exdent=2) 8 | { 9 | contents <- list(...) 10 | txt <- if (length(contents) != 1L) do.call(sprintf, contents) else contents 11 | message(paste(strwrap(txt, indent=indent, exdent=exdent), collapse="\n"), 12 | appendLF=appendLF) 13 | } 14 | 15 | .stop <- function(...) stop(noquote(sprintf(...)), call.=FALSE) 16 | 17 | handleCondition <- 18 | function( 19 | ..., condition, help_text = character(0L), 20 | messages = character(0L), nframe = 2L 21 | ) 22 | { 23 | msg <- list(paste0(...)) 24 | if (!tolower(condition) %in% c("warning", "error", "note")) 25 | stop(" Designate input with 'warning', 'error', or 'note'.") 26 | cl <- sys.call(sys.parent(n = nframe))[[1L]] 27 | ml <- structure(msg, .Names = tail(as.character(cl), 1L)) 28 | .BiocCheck$add( 29 | ml, condition = condition, help_text = help_text, messages = messages 30 | ) 31 | .BiocCheck$log 32 | } 33 | 34 | #' @importFrom cli symbol 35 | handleCheck <- function(..., appendLF=TRUE) 36 | { 37 | msg <- paste0(...) 38 | .BiocCheck$setCheck(msg) 39 | if (!interactive()) 40 | cli::cli_text(paste("*", msg)) 41 | else 42 | cli::cli_progress_step(msg = msg) 43 | } 44 | 45 | handleError <- function(...) 46 | { 47 | handleCondition(..., condition = "error") 48 | } 49 | 50 | handleErrorFiles <- function(..., help_text = "Found in files:") { 51 | handleCondition(..., help_text = help_text, condition = "error") 52 | } 53 | 54 | handleWarning <- function(...) 55 | { 56 | handleCondition(..., condition = "warning") 57 | } 58 | 59 | handleWarningFiles <- function(..., help_text = "Found in files:") { 60 | handleCondition(..., help_text = help_text, condition = "warning") 61 | } 62 | 63 | handleNote <- function(...) 64 | { 65 | handleCondition(..., condition = "note") 66 | } 67 | 68 | handleNoteFiles <- function(..., help_text = "Found in files:") { 69 | handleCondition(..., help_text = help_text, condition = "note") 70 | } 71 | 72 | handleMessage <- function(..., indent=4, exdent=6) 73 | { 74 | msg <- paste0(...) 75 | cli::cli_alert_info(msg, wrap = TRUE) 76 | } 77 | 78 | .tryInstallwLoad <- function(.BiocPackage, install_dir = tempfile()) { 79 | pkgpath <- .BiocPackage$sourceDir 80 | pkgname <- .BiocPackage$packageName 81 | if (!dir.exists(install_dir)) 82 | dir.create(install_dir) 83 | dir.create(libdir <- file.path(install_dir, "lib")) 84 | file.create(stderr <- file.path(install_dir, "install.stderr")) 85 | 86 | r_libs_user <- paste(c(libdir, .libPaths()), collapse=.Platform$path.sep) 87 | lpath <- paste0("--library=", libdir) 88 | res <- callr::rcmd_safe( 89 | "INSTALL", 90 | c( 91 | "--use-vanilla", lpath, pkgpath 92 | ), 93 | env = c(callr::rcmd_safe_env(), R_LIBS_USER = r_libs_user) 94 | ) 95 | 96 | if (!identical(res[["status"]], 0L)) 97 | handleError(pkgpath, " must be installable and loadable.") 98 | 99 | install_dir 100 | } 101 | 102 | # Takes as input the value of an Imports, Depends, 103 | # or LinkingTo field and returns a named character 104 | # vector of Bioconductor dependencies, where the names 105 | # are version specifiers or blank. 106 | cleanupDependency <- function(input, remove.R=TRUE) 107 | { 108 | if (is.null(input)) return(character(0)) 109 | if (!nchar(input)) return(character(0)) 110 | output <- gsub("\\s", "", input) 111 | raw_nms <- output 112 | nms <- strsplit(raw_nms, ",")[[1]] 113 | namevec <- vector(mode = "character", length(nms)) 114 | output <- gsub("\\([^)]*\\)", "", output) 115 | res <- strsplit(output, ",")[[1]] 116 | for (i in seq_along(nms)) 117 | { 118 | if(grepl(">=", nms[i], fixed=TRUE)) 119 | { 120 | tmp <- gsub(".*>=", "", nms[i]) 121 | tmp <- gsub(")", "", tmp, fixed=TRUE) 122 | namevec[i] <- tmp 123 | } else { 124 | namevec[i] <- '' 125 | } 126 | } 127 | names(res) <- namevec 128 | if (remove.R) 129 | res <- res[which(res != "R")] 130 | res 131 | } 132 | 133 | get_deprecated_status_db_url <- function(version) { 134 | sprintf( 135 | "https://bioconductor.org/checkResults/%s/bioc-LATEST/meat-index.dcf", 136 | version 137 | ) 138 | } 139 | 140 | #' @importFrom BiocFileCache BiocFileCache bfcquery bfcneedsupdate bfcdownload 141 | #' bfcrpath 142 | get_status_file_cache <- function(url) { 143 | cache <- tools::R_user_dir("BiocCheck", "cache") 144 | bfc <- BiocFileCache(cache, ask = FALSE) 145 | 146 | bquery <- bfcquery(bfc, url, "rname", exact = TRUE) 147 | if (identical(nrow(bquery), 1L) && bfcneedsupdate(bfc, bquery[["rid"]])) 148 | bfcdownload(x = bfc, rid = bquery[["rid"]], ask = FALSE) 149 | 150 | bfcrpath( 151 | bfc, rnames = url, exact = TRUE, download = TRUE, rtype = "web" 152 | ) 153 | } 154 | 155 | .STATUS_FILE_FIELDS <- c( 156 | "Package", "Version", "Maintainer", "MaintainerEmail", 157 | "PackageStatus", "UnsupportedPlatforms" 158 | ) 159 | 160 | .SENTINEL_PACKAGE_STATUS <- matrix( 161 | ncol = length(.STATUS_FILE_FIELDS), 162 | dimnames = list(NULL, .STATUS_FILE_FIELDS) 163 | ) 164 | 165 | .try_read_dcf <- function(file) { 166 | pkg_status <- try({ 167 | read.dcf( 168 | file, all = TRUE, fields = .STATUS_FILE_FIELDS 169 | ) 170 | }, silent = TRUE) 171 | if (is(pkg_status, "try-error")) 172 | .SENTINEL_PACKAGE_STATUS 173 | else 174 | pkg_status 175 | } 176 | 177 | get_status_from_dcf <- function(status_file) { 178 | pkg_status <- .try_read_dcf(status_file) 179 | is_deprecated <- pkg_status[, "PackageStatus"] == "Deprecated" & 180 | !is.na(pkg_status[, "PackageStatus"]) 181 | names(is_deprecated) <- pkg_status[, "Package"] 182 | is_deprecated 183 | } 184 | 185 | get_deprecated_status <- function(version) { 186 | if (version %in% c("release", "devel")) 187 | version <- BiocManager:::.version_bioc(version) 188 | status_file_url <- get_deprecated_status_db_url(version) 189 | status_file <- get_status_file_cache(status_file_url) 190 | get_status_from_dcf(status_file) 191 | } 192 | 193 | getAllDeprecatedPkgs <- function() 194 | { 195 | ## use the more complete BiocPkgTools::biocBuildReport to identify 196 | ## deprecated packages rather than using the VIEWS files 197 | deps_release <- get_deprecated_status("release") 198 | deps_devel <- get_deprecated_status("devel") 199 | 200 | union( 201 | names(deps_release[deps_release]), 202 | names(deps_devel[deps_devel]) 203 | ) 204 | } 205 | 206 | .getDirFiles <- function(fpaths) { 207 | if (!BiocBaseUtils::isCharacter(fpaths, zchar = TRUE, na.ok = TRUE)) 208 | stop(" 'fpaths' input must be a character vector") 209 | vapply(fpaths, function(fpath) { 210 | if (nzchar(fpath) && !is.na(fpath)) 211 | fpath <- file.path(basename(dirname(fpath)), basename(fpath)) 212 | fpath 213 | }, character(1L)) 214 | } 215 | 216 | .RdTags <- tools:::RdTags 217 | 218 | docType <- function(rd, tags) { 219 | if (missing(tags)) 220 | tags <- .RdTags(rd) 221 | .tagsExtract(rd, tags, "\\docType") 222 | } 223 | 224 | getBadDeps <- function(pkgdir, lib.loc) 225 | { 226 | cmd <- file.path(Sys.getenv("R_HOME"), "bin", "R") 227 | oldquotes <- getOption("useFancyQuotes") 228 | on.exit(options(useFancyQuotes=oldquotes)) 229 | options(useFancyQuotes=FALSE) 230 | args <- sprintf("-q --vanilla --no-echo -f %s --args %s", 231 | system.file("script", "checkBadDeps.R", package="BiocCheck"), 232 | paste(dQuote(pkgdir), dQuote(lib.loc))) 233 | system2(cmd, args, stdout=TRUE, stderr=FALSE, 234 | env="R_DEFAULT_PACKAGES=NULL") 235 | } 236 | 237 | getVigEngine <- function(vignetteFile) { 238 | lines <- readLines(vignetteFile, n=100L, warn=FALSE) 239 | vigEngine <- grep(lines, pattern="VignetteEngine", value = TRUE) 240 | vigEngine <- trimws(vigEngine) 241 | gsub("%\\s*\\\\VignetteEngine\\{(.*)\\}", "\\1", vigEngine) 242 | } 243 | 244 | getVigEnginePkg <- function(vignetteFile) { 245 | vigEngineField <- getVigEngine(vignetteFile) 246 | if (length(vigEngineField)) 247 | head(strsplit(vigEngineField, "::", fixed = TRUE)[[1L]], 1L) 248 | else 249 | NA_character_ 250 | } 251 | 252 | isEngineInBuilder <- function(vignetteFile, builder) { 253 | eng <- getVigEnginePkg(vignetteFile) 254 | !is.na(eng) && eng %in% builder 255 | } 256 | 257 | .load_data <- function(dataname, package) { 258 | env <- new.env(parent = emptyenv()) 259 | data(list = dataname, package = package, envir = env) 260 | env[[dataname]] 261 | } 262 | 263 | getParent <- function(view, biocViewsVocab) 264 | { 265 | topLevel <- c("Software", "ExperimentData", "AnnotationData", "Workflow") 266 | if (view %in% topLevel) 267 | return(view) 268 | parent <- "" 269 | for (level in topLevel) { 270 | if (view %in% names(acc(biocViewsVocab, level)[[level]])) { 271 | parent <- level 272 | break 273 | } 274 | } 275 | parent 276 | } 277 | 278 | .FUNCTION_LENGTHS_SENTINEL <- list( 279 | data.frame( 280 | length = integer(0L), 281 | startLine = integer(0L), 282 | endLine = integer(0L), 283 | codingLines = integer(0L) 284 | ) 285 | ) 286 | 287 | getFunctionLengths <- function(df) { 288 | df <- df[df$terminal & df$parent > -1,] 289 | 290 | # Identify comment-only lines 291 | is_comment_only_line <- df$token == "COMMENT" & 292 | !(duplicated(df$line1) | duplicated(df$line1, fromLast=TRUE)) 293 | 294 | # Create a lookup table for comment-only lines 295 | comment_lines <- unique(df$line1[is_comment_only_line]) 296 | 297 | all_lines <- unique(df$line1) 298 | 299 | rownames(df) <- NULL 300 | max <- nrow(df) 301 | res <- list() 302 | funcRows <- df[df$token %in% c("FUNCTION", "'\\\\'"), ] 303 | lst <- lapply(split(df, rownames(df)), as.list) 304 | 305 | if (!nrow(funcRows)) 306 | return(.FUNCTION_LENGTHS_SENTINEL) 307 | 308 | for (i in seq_len(nrow(funcRows))) { 309 | funcRowId <- as.integer(rownames(funcRows)[i]) 310 | funcRow <- funcRows[as.character(funcRowId),] 311 | funcStartLine <- funcRow$line1 # this might get updated later 312 | funcLines <- NULL 313 | funcName <- "_anonymous_" 314 | 315 | # attempt to get function name 316 | if (funcRowId >= 3) { 317 | up1 <- lst[[as.character(funcRowId -1)]] 318 | up2 <- lst[[as.character(funcRowId -2)]] 319 | if (up1$token %in% c("EQ_ASSIGN", "LEFT_ASSIGN", "EQ_SUB") && 320 | up2$token %in% c("SYMBOL", "SYMBOL_SUB")) { 321 | funcName <- up2$text 322 | funcStartLine <- up2$line1 323 | } 324 | } 325 | 326 | findFunctionEnd <- function() { 327 | parent_level <- funcRow$parent 328 | last_valid_row <- NULL 329 | 330 | for (j in seq((funcRowId + 1), max)) { 331 | curr_row <- lst[[as.character(j)]] 332 | if (curr_row$parent > parent_level) { 333 | return(j - 1) 334 | } 335 | if (curr_row$parent > 0) { 336 | last_valid_row <- j 337 | } 338 | if (j == max) { 339 | return(max) 340 | } 341 | } 342 | return(last_valid_row) 343 | } 344 | 345 | # Get end line and calculate metrics 346 | end_row_id <- findFunctionEnd() 347 | end_row <- lst[[as.character(end_row_id)]] 348 | endLine <- end_row$line2 349 | funcLines <- endLine - (funcStartLine - 1) 350 | 351 | # Count coding lines 352 | function_lines <- 353 | all_lines[all_lines >= funcStartLine & all_lines <= endLine] 354 | function_comment_lines <- comment_lines[ 355 | comment_lines >= funcStartLine & comment_lines <= endLine 356 | ] 357 | coding_line_count <- length( 358 | setdiff(function_lines, function_comment_lines) 359 | ) 360 | 361 | # Store results 362 | if (funcName == "_anonymous_") 363 | funcName <- paste0(funcName, ".", funcStartLine) 364 | 365 | res[[funcName]] <- c( 366 | length = funcLines, 367 | startLine = funcStartLine, 368 | endLine = endLine, 369 | codingLines = coding_line_count 370 | ) 371 | } 372 | res 373 | } 374 | 375 | doesManPageHaveRunnableExample <- function(rd) 376 | { 377 | hasExamples <- any(unlist(lapply(rd, 378 | function(x) attr(x, "Rd_tag") == "\\examples"))) 379 | if (!hasExamples) return(FALSE) 380 | 381 | ex <- character() 382 | tc <- textConnection("ex", "w", local=TRUE) 383 | tools::Rd2ex(rd, commentDontrun = TRUE, commentDonttest = TRUE, out = tc) 384 | close(tc) 385 | 386 | if(!length(ex)) 387 | return(FALSE) 388 | 389 | parsed <- try(parse(text = ex), silent = TRUE) 390 | 391 | # if code contains only comments the length with be 0 392 | length(parsed) && !inherits(parsed, "try-error") 393 | } 394 | 395 | .getYAMLfront <- function(lines) { 396 | fm_idx <- grep("^---\\s*$", lines) 397 | if (length(fm_idx) && !identical(length(fm_idx), 2L)) 398 | stop("More than 2 YAML front matter delimiters, i.e., '---' found") 399 | if (length(fm_idx)) 400 | lines <- lines[seq(min(fm_idx), max(fm_idx))] 401 | lines 402 | } 403 | 404 | .isNULLorFALSE <- function(x) { 405 | is.null(x) || isFALSE(x) 406 | } 407 | -------------------------------------------------------------------------------- /R/checkRcoding.R: -------------------------------------------------------------------------------- 1 | # check R coding practice ------------------------------------------------- 2 | 3 | checkCodingPractice <- function(.BiocPackage, parsedCode) 4 | { 5 | package_name <- .BiocPackage$packageName 6 | pkgdir <- .BiocPackage$sourceDir 7 | Rdir <- file.path(pkgdir, "R") 8 | 9 | # sapply 10 | msg_sapply <- checkSapply(.BiocPackage) 11 | if (length(msg_sapply)) { 12 | handleNoteFiles( 13 | " Avoid sapply(); use vapply()", 14 | messages = msg_sapply 15 | ) 16 | } 17 | 18 | # 1:... 19 | msg_seq <- check1toN(.BiocPackage) 20 | if (length(msg_seq)) { 21 | handleNoteFiles( 22 | " Avoid 1:...; use seq_len() or seq_along()", 23 | messages = msg_seq 24 | ) 25 | } 26 | 27 | # pkg:fun... 28 | msg_sc <- checkSingleColon(.BiocPackage) 29 | if (length(msg_sc)) { 30 | handleErrorFiles( 31 | " Use double colon for qualified imports: 'pkg::foo()'", 32 | messages = msg_sc 33 | ) 34 | } 35 | 36 | # cat() and print() 37 | msg_cat <- checkCatInRCode(.BiocPackage) 38 | if (length(msg_cat)) { 39 | handleNoteFiles( 40 | " Avoid 'cat' and 'print' outside of 'show' methods", 41 | messages = msg_cat 42 | ) 43 | } 44 | 45 | # assignment with = 46 | msg_eq <- checkEqInAssignment(.BiocPackage) 47 | if (length(msg_eq)) { 48 | handleNoteFiles( 49 | " Avoid using '=' for assignment and use '<-' instead", 50 | messages = msg_eq 51 | ) 52 | } 53 | 54 | # message(paste(...)) 55 | msg_mp <- checkPasteInSignaler(.BiocPackage) 56 | if (length(msg_mp)) { 57 | handleNoteFiles( 58 | " Avoid the use of 'paste' in condition signals", 59 | messages = msg_mp 60 | ) 61 | } 62 | 63 | # stop("Error: ") 64 | msg_ss <- checkSignalerInSignaler(.BiocPackage) 65 | if (length(msg_ss)) { 66 | handleNoteFiles( 67 | "Avoid redundant 'stop' and 'warn*' in signal conditions", 68 | messages = msg_ss 69 | ) 70 | } 71 | 72 | # T/F 73 | msg_tf <- findSymbolsInRFiles( 74 | .BiocPackage, c("T", "F"), "SYMBOL", notLookback = "$" 75 | ) 76 | if (length(msg_tf)) { 77 | handleWarning( 78 | " Avoid T/F variables; If logical, use TRUE/FALSE ", 79 | help_text = paste("Found", length(msg_tf), "times:"), 80 | messages = msg_tf 81 | ) 82 | } 83 | 84 | # class() == 85 | msg_class <- checkClassNEEQLookup(.BiocPackage) 86 | if (length(msg_class)) { 87 | handleWarningFiles( 88 | " Avoid class membership checks with class() / is() and == / !=", 89 | "; Use is(x, 'class') for S4 classes", 90 | messages = msg_class 91 | ) 92 | } 93 | 94 | # system() vs system2() 95 | msg_sys <- findSymbolsInRFiles( 96 | .BiocPackage, "system", "SYMBOL_FUNCTION_CALL" 97 | ) 98 | if(length(msg_sys)) { 99 | handleNoteFiles( 100 | " Avoid system() ; use system2()", 101 | messages = msg_sys 102 | ) 103 | } 104 | 105 | # external data 106 | msg_eda <- checkExternalData(.BiocPackage) 107 | if (length(msg_eda)) { 108 | handleErrorFiles( 109 | " Avoid references to external hosting platforms", 110 | messages = msg_eda 111 | ) 112 | } 113 | 114 | # download / download.file in .onAttach / .onLoad 115 | msg_dl <- checkOnAttachLoadCalls(.BiocPackage) 116 | if (length(msg_dl)) { 117 | handleErrorFiles( 118 | " Avoid downloads in '.onAttach' or '.onLoad' functions", 119 | messages = msg_dl 120 | ) 121 | } 122 | 123 | # set.seed 124 | msg_seed <- 125 | findSymbolsInRFiles(.BiocPackage, "set.seed", "SYMBOL_FUNCTION_CALL") 126 | if (length(msg_seed)){ 127 | handleWarning( 128 | " Remove set.seed usage (found ", length(msg_seed), " times)", 129 | messages = msg_seed 130 | ) 131 | } 132 | 133 | # .Deprecated / .Defunct usage should be updated after every release 134 | msg_depr <- findSymbolsInRFiles( 135 | .BiocPackage, 136 | c( 137 | ".Deprecated", ".Defunct", "lifeCycle", 138 | "deprecate_warn", "deprecate_stop" 139 | ), 140 | "SYMBOL_FUNCTION_CALL" 141 | ) 142 | if (length(msg_depr)) { 143 | handleWarning( 144 | ".Deprecated / .Defunct usage (found ", 145 | length(msg_depr), " times)", 146 | messages = msg_depr 147 | ) 148 | } 149 | 150 | handleCheck("Checking parsed R code in R directory, examples, vignettes...") 151 | 152 | # direct slot access 153 | checkForDirectSlotAccess(parsedCode, package_name) 154 | 155 | # browser() calls 156 | msg_b <- 157 | findSymbolsInRFiles(.BiocPackage, "browser", "SYMBOL_FUNCTION_CALL") 158 | if (length(msg_b)) { 159 | handleWarning( 160 | "Remove browser() statements (found ", length(msg_b), " times)", 161 | messages = msg_b 162 | ) 163 | } 164 | 165 | # <<- 166 | msg_da <- findSymbolsInRFiles(.BiocPackage, "<<-", "LEFT_ASSIGN") 167 | if (length(msg_da)) { 168 | handleNote( 169 | "Avoid '<<-' if possible (found ", length(msg_da), " times)", 170 | messages = msg_da 171 | ) 172 | } 173 | 174 | # Sys.setenv calls 175 | msg_env <- 176 | findSymbolsInRFiles(.BiocPackage, "Sys.setenv", "SYMBOL_FUNCTION_CALL") 177 | if (length(msg_env)) { 178 | handleError( 179 | "Avoid 'Sys.setenv' (found ", length(msg_env), " times)", 180 | messages = msg_env 181 | ) 182 | } 183 | 184 | # suppressWarnings/Messages calls 185 | msg_supp <- findSymbolsInRFiles( 186 | .BiocPackage, 187 | c("suppressWarnings", "suppressMessages"), 188 | "SYMBOL_FUNCTION_CALL" 189 | ) 190 | if (length(msg_supp)) { 191 | handleNote( 192 | "Avoid 'suppressWarnings'/'*Messages' if possible (found ", 193 | length(msg_supp), " times)", 194 | messages = msg_supp 195 | ) 196 | } 197 | } 198 | 199 | checkSapply <- function(.BiocPackage) { 200 | msg_sapply <- findSymbolsInRFiles( 201 | .BiocPackage, "sapply", "SYMBOL_FUNCTION_CALL", FALSE 202 | ) 203 | } 204 | 205 | check1toN <- function(.BiocPackage) { 206 | rfiles <- .BiocPackage$RSources 207 | msg_seq <- lapply(rfiles, function(rfile) { 208 | tokens <- getParseData(parse(rfile, keep.source=TRUE)) 209 | tokens <- tokens[tokens[,"token"] != "expr", ,drop=FALSE] 210 | colons <- which(tokens[,"text"] == ":") - 1 211 | colons <- colons[tokens[colons, "text"] == "1"] 212 | tokens <- tokens[colons, , drop=FALSE] 213 | tokens <- tokens[ tokens[,"text"] == "1", , drop=FALSE] 214 | sprintf( 215 | "%s (line %d, column %d)", 216 | basename(rfile), tokens[,"line1"], tokens[,"col1"] 217 | ) 218 | }) 219 | msg_seq <- unlist(msg_seq) 220 | } 221 | 222 | checkSingleColon <- function(.BiocPackage, avail_pkgs = character(0L)) { 223 | 224 | rfiles <- .BiocPackage$RSources 225 | names(rfiles) <- basename(rfiles) 226 | colon_pres <- lapply(rfiles, function(rfile) { 227 | tokens <- getParseData(parse(rfile, keep.source = TRUE)) 228 | tokens <- tokens[tokens[,"token"] != "expr", ,drop=FALSE] 229 | colons <- which(tokens[,"text"] == ":") - 1 230 | colons <- colons[grepl("[[:alpha:]]", tokens[colons, "text"])] 231 | tokens[colons, , drop = FALSE] 232 | }) 233 | colon_pres <- Filter(nrow, colon_pres) 234 | if (length(colon_pres)) 235 | avail_pkgs <- BiocManager::available() 236 | msg_sc <- lapply(names(colon_pres), function(rfile, framelist) { 237 | tokens <- framelist[[rfile]] 238 | tokens <- tokens[tokens[, "text"] %in% avail_pkgs, , drop = FALSE] 239 | sprintf( 240 | "%s (line %d, column %d)", 241 | rfile, tokens[, "line1"], tokens[, "col1"] 242 | ) 243 | }, framelist = colon_pres) 244 | msg_sc <- unlist(msg_sc) 245 | } 246 | 247 | .filtTokens <- 248 | function(ind, tokens, keywords = c("paste0", "paste")) 249 | { 250 | txt <- tokens[ind, "text"] 251 | filt <- tolower(txt) %in% keywords 252 | #filt <- grepl(txt, keywords, ignore.case = ignore.case) 253 | if (any(filt) && "collapse" %in% txt) 254 | filt <- FALSE 255 | ind[filt] 256 | } 257 | 258 | .getTokens <- function(rfile) { 259 | tokens <- getParseData(parse(rfile, keep.source = TRUE)) 260 | tokens[tokens[,"token"] != "expr", ,drop=FALSE] 261 | } 262 | 263 | .grepSymbolRanges <- function( 264 | tokens, patterns, tokenType = "SYMBOL_FUNCTION_CALL", isExp = FALSE 265 | ) { 266 | txt <- tokens[, "text"] 267 | found <- lapply(patterns, function(pattern) grepl(pattern, txt)) 268 | found <- Reduce(`|`, found) 269 | hits <- which(found & tokens[, "token"] == tokenType) 270 | openBracket <- if (isExp) "{" else "(" 271 | opar <- which(txt == openBracket) 272 | startHit <- vapply(hits, function(x) min(opar[opar > x]), numeric(1L)) 273 | parnum <- tokens[startHit, "parent"] 274 | endHit <- nrow(tokens) - match(parnum, rev(tokens[, "parent"])) 275 | Map(seq, startHit, endHit) 276 | } 277 | 278 | .findSymbolRanges <- 279 | function(tokens, symbols, tokenType = "SYMBOL_FUNCTION_CALL", isExp = FALSE) 280 | { 281 | txt <- tokens[, "text"] 282 | signalers <- which( 283 | txt %in% symbols & tokens[, "token"] == tokenType 284 | ) 285 | openBracket <- if (isExp) "{" else "(" 286 | opar <- which(txt == openBracket) 287 | startSig <- vapply(signalers, function(x) min(opar[opar > x]), numeric(1L)) 288 | parnum <- tokens[startSig, "parent"] 289 | endSig <- nrow(tokens) - match(parnum, rev(tokens[, "parent"])) 290 | Map(seq, startSig, endSig) 291 | } 292 | 293 | .findInSignaler <- function(rfile, symbols, FUN, ...) { 294 | tokens <- .getTokens(rfile) 295 | sigRanges <- .findSymbolRanges(tokens, symbols) 296 | pasteInd <- lapply(sigRanges, FUN, tokens = tokens, ...) 297 | tokens <- tokens[unlist(pasteInd), , drop = FALSE] 298 | rfile <- paste0("R/", basename(rfile)) 299 | sprintf( 300 | "%s (line %d, column %d)", 301 | rfile, tokens[, "line1"], tokens[, "col1"] 302 | ) 303 | } 304 | 305 | .SIGNALERS_TXT <- c("message", "warning", "stop") 306 | 307 | .findPasteInSignaler <- function(rfile, symbols = .SIGNALERS_TXT) { 308 | .findInSignaler(rfile, symbols, .filtTokens) 309 | } 310 | 311 | .filtersetMethodRanges <- function(tokens) { 312 | excl <- .findSymbolRanges(tokens, "setMethod") 313 | if (length(excl)) { 314 | showHits <- vapply(excl, 315 | function(x) '"show"' %in% tokens[x, "text"], logical(1)) 316 | negind <- unlist(lapply(excl[showHits], `-`)) 317 | tokens <- tokens[negind, ] 318 | } 319 | tokens 320 | } 321 | 322 | .filterS3printRanges <- function(tokens) { 323 | excl <- .grepSymbolRanges(tokens, "^print\\..*", tokenType = "SYMBOL") 324 | if (length(excl)) { 325 | showHits <- vapply(excl, 326 | function(x) "cat" %in% tokens[x, "text"], logical(1) 327 | ) 328 | negind <- unlist(lapply(excl[showHits], `-`)) 329 | tokens <- tokens[negind, ] 330 | } 331 | tokens 332 | } 333 | 334 | checkCatInRCode <- 335 | function(.BiocPackage, symbols = c("cat", "print")) 336 | { 337 | rfiles <- .BiocPackage$RSources 338 | parsedCodes <- lapply( 339 | structure(rfiles, .Names = rfiles), parseFile, 340 | .BiocPackage = .BiocPackage 341 | ) 342 | parsedCodes <- lapply(parsedCodes, .filtersetMethodRanges) 343 | parsedCodes <- lapply(parsedCodes, .filterS3printRanges) 344 | msg_res <- findSymbolsInParsedCode( 345 | parsedCodeList = parsedCodes, 346 | symbolNames = symbols, 347 | tokenTypes = c("SYMBOL_FUNCTION_CALL", "SYMBOL") 348 | ) 349 | unlist(msg_res) 350 | } 351 | 352 | checkEqInAssignment <- 353 | function(.BiocPackage, symbol = "=", tokenType = "EQ_ASSIGN") 354 | { 355 | rfiles <- .BiocPackage$RSources 356 | parsedCodes <- lapply( 357 | structure(rfiles, .Names = rfiles), parseFile, 358 | .BiocPackage = .BiocPackage 359 | ) 360 | msg_res <- findSymbolsInParsedCode( 361 | parsedCodeList = parsedCodes, 362 | symbolNames = symbol, 363 | tokenTypes = tokenType, 364 | fun = FALSE 365 | ) 366 | unlist(msg_res) 367 | } 368 | 369 | .grepTokens <- 370 | function(ind, tokens, keywords) 371 | { 372 | txt <- tokens[ind, , drop = FALSE] 373 | strs <- txt$token == "STR_CONST" 374 | ind <- ind[strs] 375 | txt <- txt[strs, "text"] 376 | filt <- grepl(paste0(keywords, collapse = "|"), txt, ignore.case = TRUE) 377 | ind[filt] 378 | } 379 | 380 | .findSignalerInSignaler <- function(rfile, symbols) { 381 | .findInSignaler(rfile, symbols, .grepTokens, 382 | keywords = c("message", "warn", "error")) 383 | } 384 | 385 | checkPasteInSignaler <- function(.BiocPackage) { 386 | rfiles <- .BiocPackage$RSources 387 | pasteSig <- lapply(rfiles, .findPasteInSignaler) 388 | pasteSig <- unlist(pasteSig) 389 | } 390 | 391 | checkSignalerInSignaler <- function(.BiocPackage) { 392 | rfiles <- .BiocPackage$RSources 393 | sisig <- lapply(rfiles, .findSignalerInSignaler, symbols = .SIGNALERS_TXT) 394 | sisig <- unlist(sisig) 395 | } 396 | 397 | .checkValidNEEQPattern <- function(tokens, eqnums) { 398 | tokens[["rowID"]] <- seq_len(nrow(tokens)) 399 | unlist(lapply(eqnums, function(eq) { 400 | parnum <- tokens[eq, "parent"] 401 | hits <- which(tokens[, "parent"] %in% parnum) 402 | if (!length(hits)) { return(NULL) } 403 | startEQ <- min(hits) 404 | endEQ <- max(hits) 405 | EQblock <- tokens[startEQ:endEQ, ] 406 | hasIS <- EQblock[, "token"] == "SYMBOL_FUNCTION_CALL" & 407 | EQblock[, "text"] %in% c("is", "class") 408 | if ( 409 | any(hasIS) && 410 | "STR_CONST" %in% EQblock[EQblock[["rowID"]] > eq, "token"] 411 | ) 412 | eq 413 | else 414 | NULL 415 | })) 416 | } 417 | 418 | getClassNEEQLookup <- function(rfile) { 419 | tokens <- getParseData(parse(rfile, keep.source = TRUE)) 420 | eqtoks <- which(tokens[, "token"] %in% c("NE", "EQ")) 421 | eqtoks <- .checkValidNEEQPattern(tokens, eqtoks) 422 | tokens[eqtoks, , drop = FALSE] 423 | } 424 | 425 | checkClassNEEQLookup <- function(.BiocPackage) { 426 | rfiles <- .BiocPackage$RSources 427 | names(rfiles) <- basename(rfiles) 428 | NEEQ_pres <- lapply(rfiles, getClassNEEQLookup) 429 | NEEQ_pres <- Filter(nrow, NEEQ_pres) 430 | msg_neeq <- lapply(names(NEEQ_pres), function(rfile, framelist) { 431 | tokens <- framelist[[rfile]] 432 | sprintf( 433 | "%s (line %d, column %d)", 434 | rfile, tokens[, "line1"], tokens[, "col1"] 435 | ) 436 | }, framelist = NEEQ_pres) 437 | unlist(msg_neeq) 438 | } 439 | 440 | checkExternalData <- function(.BiocPackage) { 441 | rfiles <- .BiocPackage$RSources 442 | msg_eda <- lapply(rfiles, function(rfile) { 443 | tokens <- getParseData(parse(rfile, keep.source=TRUE)) 444 | tokens <- tokens[tokens[,"token"] == "STR_CONST", ,drop=FALSE] 445 | 446 | platforms <- paste0( 447 | "githubusercontent|github.*[^html\"]$|", 448 | "gitlab|bitbucket|[^\\.]dropbox" 449 | ) 450 | txtkns <- tokens[, "text"] 451 | hits <- grepl(platforms, txtkns, ignore.case = TRUE) & 452 | grepl("dl|\\.\\w+\"$", txtkns) 453 | tokens <- tokens[hits, , drop = FALSE] 454 | 455 | sprintf( 456 | "%s (line %d, column %d)", 457 | basename(rfile), tokens[,"line1"], tokens[,"col1"] 458 | ) 459 | }) 460 | unlist(msg_eda) 461 | } 462 | 463 | checkOnAttachLoadCalls <- function(.BiocPackage) { 464 | rfiles <- .BiocPackage$RSources 465 | parsedCodes <- lapply( 466 | structure(rfiles, .Names = rfiles), parseFile, 467 | .BiocPackage = .BiocPackage 468 | ) 469 | parsedCodes <- lapply(parsedCodes, function(tokens) { 470 | tokens <- tokens[!tokens[, "token"] %in% c("expr", "COMMENT"), ] 471 | incl <- .findSymbolRanges( 472 | tokens, c(".onLoad", ".onAttach"), "SYMBOL", TRUE 473 | ) 474 | tokens[unlist(incl), ] 475 | }) 476 | parsedCodes <- Filter(nrow, parsedCodes) 477 | msg_dl <- findSymbolsInParsedCode( 478 | parsedCodeList = parsedCodes, 479 | symbolNames = "download.*", 480 | tokenTypes = "SYMBOL_FUNCTION_CALL", 481 | FUN = .grepTokenTextCode 482 | ) 483 | unlist(msg_dl) 484 | } 485 | 486 | checkForDirectSlotAccess <- function(parsedCode, package_name) 487 | { 488 | idx <- grepl("\\.R$", names(parsedCode), ignore.case=TRUE) 489 | parsedCode <- parsedCode[!idx] 490 | res <- findSymbolInParsedCode(parsedCode, package_name, "@", "'@'") 491 | if (res > 0) 492 | { 493 | handleNote( 494 | "Use accessors; don't access S4 class slots via ", 495 | "'@' in examples/vignettes.") 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /R/BiocCheck.R: -------------------------------------------------------------------------------- 1 | #' Check a package's adherence with the Bioconductor Package Guidelines 2 | #' 3 | #' Analyzes an R package for adherence with Bioconductor package guidelines and 4 | #' best practices. The check outputs are categorized into ERROR, WARNING, and 5 | #' NOTE. See the vignette for more details. `BiocCheck` is complementary 6 | #' to `R CMD check`, which should always be run first. 7 | #' 8 | #' `BiocCheck()` reviews R packages for adherence with Bioconductor 9 | #' package guidelines and best practices. See 10 | #' \url{https://contributions.bioconductor.org} for the latest guidance for 11 | #' writing Bioconductor software. Some rationale behind these best practices 12 | #' can be seen in the vignette and pages in the `references` section. The 13 | #' vignette also provides detailed explanations of all the checks performed by 14 | #' `BiocCheck`. 15 | #' 16 | #' `BiocCheck` is called within R with \preformatted{ BiocCheck() 17 | #' } where `package` points to the source directory or the `.tar.gz` 18 | #' tarball that was created using `R CMD build`. 19 | #' 20 | #' \emph{Note} that `BiocCheck` is complementary to `R CMD check`. 21 | #' `R CMD check` should always be run first for best results. 22 | #' 23 | #' To skip installation of the package during the check, set the 24 | #' `install` option to `FALSE` or `NULL`: 25 | #' \preformatted{ 26 | #' BiocCheck(package, install=FALSE) 27 | #' ## OR 28 | #' BiocCheck(package, install=NULL) 29 | #' } 30 | #' To re-use an existing installation log file, set the `install` option 31 | #' to the name of the installation log file. 32 | #' For example, the following will put the `install_out.txt` log file in the 33 | #' `.BiocCheck` directory: 34 | #' \preformatted{ BiocCheck(package, install="check:install_out.txt") } 35 | #' 36 | #' @section dot-options: 37 | #' To use the dot-options, `BiocCheck` can be called with named arguments 38 | #' corresponding to the options below. Typically, these options are set to 39 | #' `TRUE` to disable specific checks, e.g., 40 | #' \preformatted{ BiocCheck(package, `no-check-vignettes`=TRUE) }. Unless 41 | #' otherwise stated, these options can be left unset (i.e., `NULL`) to enable 42 | #' checks but `FALSE` can also be used to explicitly enable them. The available 43 | #' options are: 44 | #' 45 | #' * `build-output-file`: file containing `R CMD build` output, for 46 | #' additional analysis 47 | #' * `new-package`: enable checks specific to new packages 48 | #' * `no-check-bbs`: disable BBS-specific checks (for non-BioC packages). 49 | #' Valid DESCRIPTION 50 | #' * `no-check-bioc-help`: disable check for registration on Bioconductor 51 | #' * `no-check-bioc-views`: disable biocViews-specific checks (for non-BioC 52 | #' packages) 53 | #' mailing list and support site 54 | #' * `no-check-coding-practices`: disable check for some common best coding 55 | #' practices 56 | #' * `no-check-CRAN`: disable check for if package exists in CRAN 57 | #' * `no-check-dependencies`: disable check for bad dependencies 58 | #' * `no-check-deprecated`: disable check for usage of deprecated packages 59 | #' * `no-check-description`: disable DESCRIPTION file checks 60 | #' * `no-check-file-size`: disable check for individual file size 61 | #' * `no-check-formatting`: disable checks for file formatting 62 | #' * `no-check-function-len`: disable check for function length 63 | #' * `no-check-install-self`: disable check for require or library of 64 | #' itself 65 | #' * `no-check-library-calls`: disable check usage of functions that 66 | #' install or update packages 67 | #' * `no-check-man-doc`: disable checks for man page documentation 68 | #' * `no-check-namespace`: disable NAMESPACE file checks 69 | #' * `no-check-news`: disable checks for NEWS file 70 | #' * `no-check-pkg-size`: disable check for package tarball size 71 | #' * `no-check-R-ver`: disable check for valid R version 72 | #' * `no-check-remotes`: disable check for usage of remote packages other 73 | #' than those hosted on CRAN or Bioconductor 74 | #' * `no-check-skip-bioc-tests`: disable check for tests that skip when on 75 | #' bioc 76 | #' * `no-check-unit-tests`: disable checks for unit tests 77 | #' * `no-check-version-num`: disable check for valid version number 78 | #' * `no-check-vignettes`: disable vignette checks 79 | #' * `quit-with-status`: enable exit code option when performing check 80 | #' * `install`: if `FALSE`, the package is not installed; otherwise, if not 81 | #' specified, the package is installed by default. Optionally, a 82 | #' `check:` key-value pair is provided to identify the name of the 83 | #' installation output file which will be copied to the 84 | #' `.BiocCheck` directory. 85 | #' * `libloc`: when `install` is specified, the library location where the 86 | #' package is installed. By default, this is `.libPaths()[1]`. 87 | #' 88 | #' @param package The path to an R package directory or tarball (`.tar.gz`). 89 | #' The `BiocCheck` function is intended to be run from the package 90 | #' directory; therefore, the current working directory (given by `getwd()`) 91 | #' is the default. 92 | #' 93 | #' @param checkDir The directory where the `BiocCheck` output directory will be 94 | #' stored. By default, it will be placed in the same directory as the package 95 | #' directory i.e., `dirname(pkg_dir)`. 96 | #' 97 | #' @param debug Whether to append the names of functions that correspond to 98 | #' each condition raised by `BiocCheck` in the written log (i.e., in the 99 | #' `'.BiocCheck'` folder). This option is only relevant to 100 | #' developers and contributors to `BiocCheck`. 101 | #' 102 | #' @param callr logical(1) Whether to use the `callr` package to run `BiocCheck` 103 | #' in an isolated R session to prevent namespace collisions. 104 | #' 105 | #' @param \dots See the `dot-options` details section for available options. 106 | #' 107 | #' @return `BiocCheck()` is chiefly called for the side effect of the check 108 | #' reporting. The function also creates a `.BiocCheck` folder 109 | #' and returns a `BiocCheck` reference class with three main list elements: 110 | #' 111 | #' * **error**: Items to address before the package can be accepted 112 | #' 113 | #' * **warning**: Strongly suggested items that may require attention 114 | #' 115 | #' * **note**: Items to consider, though not required, before acceptance 116 | #' 117 | #' @author Dan Tenenbaum, Lori Shepherd, and Marcel Ramos 118 | #' 119 | #' @references \url{https://contributions.bioconductor.org} 120 | #' @seealso \link{BiocCheck-class}, \link{Message-class} 121 | #' 122 | #' @examples 123 | #' 124 | #' packageDir <- system.file("testpackages", "testpkg0", package="BiocCheck") 125 | #' BiocCheck(packageDir, `quit-with-status`=FALSE) 126 | #' 127 | #' @export BiocCheck 128 | BiocCheck <- function( 129 | package = getwd(), 130 | checkDir = dirname(package), 131 | debug = FALSE, 132 | callr = FALSE, 133 | ... 134 | ) { 135 | if (callr) { 136 | callr::r( 137 | function(...) { BiocCheck:::BiocCheckRun(...) }, 138 | args = list( 139 | package = package, checkDir = checkDir, debug = debug, ... 140 | ), 141 | cmdargs = c("--no-echo", "--no-save", "--no-restore"), 142 | show = TRUE 143 | ) 144 | } else { 145 | BiocCheckRun( 146 | package = package, checkDir = checkDir, debug = debug, ... 147 | ) 148 | } 149 | } 150 | 151 | #' @importFrom BiocBaseUtils isScalarCharacter 152 | BiocCheckRun <- 153 | function(package, checkDir, debug, ...) 154 | { 155 | .BiocCheck$zero() 156 | package <- normalizePath(package) 157 | if (!file.exists(package) || !isScalarCharacter(package)) 158 | .stop("Package directory or tarball provided does not exist.") 159 | # be careful here: 160 | if (identical(.Platform$OS.type, "windows")) 161 | package <- gsub("\\\\", "/", package) 162 | 163 | dots <- list(...) 164 | if (length(dots) == 1L && is.list(dots[[1]])) 165 | dots <- dots[[1]] # command line args come as list 166 | 167 | oldwarn <- getOption("warn") 168 | oldwidth <- getOption("cli.width") 169 | on.exit({ 170 | options(warn = oldwarn, cli.width = oldwidth) 171 | }) 172 | options(warn = 1, cli.width = 80) 173 | 174 | .BiocPackage <- .BiocPackage$initialize( 175 | packageDir = package, checkDir = checkDir 176 | ) 177 | 178 | ## consider merging these operations into one 179 | cli::cli_div(theme = list(.pkg = list(color = "orange"))) 180 | cli::cli_rule("Installing {.pkg { .BiocPackage$packageName }}") 181 | 182 | install_param <- dots[["install"]] 183 | should_install <- is.null(install_param) || isTRUE(install_param) 184 | 185 | if (should_install) { 186 | package_install_dir <- .tryInstallwLoad(.BiocPackage) 187 | cli::cli_alert_success("Package installed successfully") 188 | libloc <- file.path(package_install_dir, "lib") 189 | } else { 190 | if (is.character(install_param)) { 191 | split_log <- strsplit(install_param, ":")[[1L]] 192 | inst_log <- utils::tail(split_log, n = 1L) 193 | on.exit({ 194 | if (file.exists(inst_log)) 195 | file.copy( 196 | from = inst_log, 197 | to = file.path( 198 | .BiocPackage$BiocCheckDir, basename(inst_log) 199 | ) 200 | ) 201 | }, add = TRUE) 202 | } 203 | libloc <- dots[["libloc"]] %||% .libPaths()[1L] 204 | package_install_dir <- .libPaths()[1L] 205 | } 206 | 207 | isBBS <- Sys.getenv("IS_BIOC_BUILD_MACHINE") 208 | onBBS <- nzchar(isBBS) && identical(tolower(isBBS), "true") 209 | hasAdmin <- nzchar(Sys.getenv("BIOC_DEVEL_PASSWORD")) 210 | 211 | .BiocCheck$addMetadata( 212 | BiocPackage = .BiocPackage, installDir = package_install_dir 213 | ) 214 | cli::cli_rule("{.pkg { .BiocPackage$packageName }} session metadata") 215 | .BiocCheck$show_meta() 216 | cli::cli_rule("Running BiocCheck on {.pkg { .BiocPackage$packageName }}") 217 | 218 | # BiocCheck checks -------------------------------------------------------- 219 | if (.isNULLorFALSE(dots[["no-check-deprecated"]])) { 220 | handleCheck("Checking for deprecated package usage...") 221 | checkDeprecatedPackages(.BiocPackage) 222 | } 223 | 224 | if (.isNULLorFALSE(dots[["no-check-remotes"]])){ 225 | handleCheck("Checking for remote package usage...") 226 | checkRemotesUsage(.BiocPackage) 227 | } 228 | 229 | handleCheck("Checking for 'LazyData: true' usage...") 230 | checkLazyDataUsage(.BiocPackage) 231 | 232 | if (.isNULLorFALSE(dots[["no-check-version-num"]])){ 233 | handleCheck("Checking version number...") 234 | if (!.BiocPackage$isSourceDir) { 235 | handleCheck("Checking for version number mismatch...") 236 | checkForVersionNumberMismatch(.BiocPackage) 237 | } 238 | 239 | if (isTRUE(dots[["new-package"]])) { 240 | handleCheck("Checking new package version number...") 241 | checkNewPackageVersionNumber(.BiocPackage) 242 | } else if (.isNULLorFALSE(dots[["new-package"]])) { 243 | handleCheck("Checking version number validity...") 244 | checkVersionNumber(.BiocPackage) 245 | } 246 | } 247 | 248 | if (.isNULLorFALSE(dots[["no-check-R-ver"]])) { 249 | handleCheck("Checking R version dependency...") 250 | checkRVersionDependency(.BiocPackage) 251 | } 252 | 253 | if (.isNULLorFALSE(dots[["no-check-pkg-size"]])){ 254 | handleCheck("Checking package size...") 255 | if (.BiocPackage$isTar){ 256 | checkPackageSize(.BiocPackage) 257 | } else { 258 | handleMessage("Skipped... only checked on source tarball", indent=4) 259 | } 260 | } 261 | 262 | if (.isNULLorFALSE(dots[["no-check-file-size"]])){ 263 | handleCheck("Checking individual file sizes...") 264 | checkIndivFileSizes(.BiocPackage) 265 | checkDataFileSizes(.BiocPackage) 266 | } 267 | 268 | if (.isNULLorFALSE(dots[["no-check-bioc-views"]])) 269 | { 270 | handleCheck("Checking biocViews...") 271 | result <- checkBiocViews(.BiocPackage) 272 | if(result) 273 | { 274 | cli::cli_alert_info( 275 | "Search 'biocViews' at https://contributions.bioconductor.org" 276 | ) 277 | } 278 | } 279 | 280 | if (.isNULLorFALSE(dots[["no-check-bbs"]])){ 281 | handleCheck("Checking build system compatibility...") 282 | checkBBScompatibility(.BiocPackage) 283 | } 284 | 285 | if (.isNULLorFALSE(dots[["no-check-description"]])) { 286 | checkDESCRIPTIONFile(.BiocPackage) 287 | } 288 | 289 | if (.isNULLorFALSE(dots[["no-check-namespace"]])) 290 | checkNAMESPACE(.BiocPackage) 291 | 292 | handleCheck("Checking .Rbuildignore...") 293 | checkRbuildignore(.BiocPackage) 294 | 295 | handleCheck("Checking for stray BiocCheck output folders...") 296 | checkBiocCheckOutputFolder(.BiocPackage) 297 | 298 | if (!.BiocPackage$isTar) { 299 | handleCheck("Checking for inst/doc folders...") 300 | checkInstDocFolder(.BiocPackage) 301 | } 302 | 303 | if (.isNULLorFALSE(dots[["no-check-vignettes"]])) { 304 | handleCheck("Checking vignette directory...") 305 | checkVignetteDir(.BiocPackage) 306 | if ("build-output-file" %in% names(dots)) { 307 | handleCheck( 308 | "Checking whether vignette is built with 'R CMD build'..." 309 | ) 310 | checkIsVignetteBuilt(dots[["build-output-file"]]) 311 | } 312 | } 313 | 314 | if (.isNULLorFALSE(dots[["no-check-library-calls"]])){ 315 | handleCheck("Checking package installation calls in R code...") 316 | checkPkgInstallCalls(.BiocPackage) 317 | } 318 | 319 | package_dir <- .BiocPackage$sourceDir 320 | package_name <- .BiocPackage$packageName 321 | parsedCode <- parseFiles(.BiocPackage) 322 | 323 | if (.isNULLorFALSE(dots[["no-check-install-self"]])){ 324 | handleCheck(sprintf("Checking for library/require of %s...", 325 | package_name)) 326 | checkForLibraryRequire(.BiocPackage) 327 | } 328 | 329 | if (.isNULLorFALSE(dots[["no-check-coding-practices"]])){ 330 | handleCheck("Checking coding practice...") 331 | checkCodingPractice(.BiocPackage, parsedCode) 332 | } 333 | 334 | if (.isNULLorFALSE(dots[["no-check-function-len"]])){ 335 | handleCheck("Checking function lengths...") 336 | checkFunctionLengths(parsedCode, package_name) 337 | } 338 | 339 | if (.isNULLorFALSE(dots[["no-check-man-doc"]])){ 340 | handleCheck("Checking man page documentation...") 341 | checkManDocumentation(.BiocPackage, libloc) 342 | } 343 | 344 | if (.isNULLorFALSE(dots[["no-check-news"]])){ 345 | handleCheck("Checking package NEWS...") 346 | checkNEWS(package_dir) 347 | } 348 | 349 | if (.isNULLorFALSE(dots[["no-check-unit-tests"]])){ 350 | handleCheck("Checking unit tests...") 351 | checkUnitTests(package_dir) 352 | } 353 | 354 | if (.isNULLorFALSE(dots[["no-check-skip-bioc-tests"]])){ 355 | handleCheck("Checking skip_on_bioc() in tests...") 356 | checkSkipOnBioc(package_dir) 357 | } 358 | 359 | if (.isNULLorFALSE(dots[["no-check-formatting"]])){ 360 | handleCheck( 361 | "Checking formatting of DESCRIPTION, NAMESPACE, ", 362 | "man pages, R source, and vignette source...") 363 | checkFormatting(.BiocPackage) 364 | } 365 | 366 | if (.isNULLorFALSE(dots[["no-check-CRAN"]])) 367 | { 368 | handleCheck("Checking if package already exists in CRAN...") 369 | checkIsPackageNameAlreadyInUse(package_name, "CRAN") 370 | } 371 | 372 | if (isTRUE(dots[["new-package"]])) 373 | { 374 | handleCheck("Checking if new package already exists in Bioconductor...") 375 | checkIsPackageNameAlreadyInUse(package_name, "BioCsoft") 376 | checkIsPackageNameAlreadyInUse(package_name, "BioCann") 377 | checkIsPackageNameAlreadyInUse(package_name, "BioCexp") 378 | checkIsPackageNameAlreadyInUse(package_name, "BioCworkflows") 379 | # TODO: add VIEWS files for books 380 | # checkIsPackageNameAlreadyInUse(package_name, "BioCbooks") 381 | } 382 | 383 | if (.isNULLorFALSE(dots[["no-check-bioc-help"]])) { 384 | handleCheck("Checking for bioc-devel mailing list subscription...") 385 | if (hasAdmin) { 386 | checkForBiocDevelSubscription(.BiocPackage) 387 | } else { 388 | handleNote( 389 | "Cannot determine whether maintainer is subscribed to the ", 390 | "Bioc-Devel mailing list (requires admin credentials). ", 391 | "Subscribe here: ", 392 | "https://stat.ethz.ch/mailman/listinfo/bioc-devel" 393 | ) 394 | } 395 | 396 | handleCheck("Checking for support site registration...") 397 | checkForSupportSiteRegistration(.BiocPackage) 398 | } 399 | 400 | cli::cli_rule( 401 | left = paste0("BiocCheck v", packageVersion("BiocCheck"), " results") 402 | ) 403 | cli::cli_text( 404 | paste0( 405 | "{symbol$cross} { .BiocCheck$getNum('error') } ERRORS | ", 406 | "{symbol$warning} { .BiocCheck$getNum('warning') } WARNINGS | ", 407 | "{symbol$info} { .BiocCheck$getNum('note') } NOTES\n" 408 | ) 409 | ) 410 | cli::cli_alert_info( 411 | paste0( 412 | "\nSee the { .BiocPackage$packageName }.BiocCheck folder and run\n", 413 | " browseVignettes(package = 'BiocCheck')\n", 414 | " for details." 415 | ) 416 | ) 417 | 418 | .BiocCheck$report(debug, onBBS) 419 | 420 | if (isTRUE(dots[["quit-with-status"]])) { 421 | errcode <- as.integer(.BiocCheck$getNum("error") > 0) 422 | q("no", errcode) 423 | } 424 | 425 | return(.BiocCheck) 426 | 427 | } 428 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | CHANGES IN VERSION 1.48.0 2 | ----------------------- 3 | 4 | BUG FIXES AND MINOR IMPROVEMENTS 5 | 6 | o Package tarball size check updated to have a max of 10 MB (@LiNk-NY, 7 | #234). 8 | 9 | CHANGES IN VERSION 1.46.0 10 | ----------------------- 11 | 12 | NEW FEATURES 13 | 14 | o Checks support `qmd` vignettes and require that a `SystemRequirements` 15 | field be present and list the `quarto` CLI in the DESCRIPTION file. 16 | o All chunks in vignettes should have labels (@vjcitn, #222) 17 | o Flag export all regex patterns in `NAMESPACE` (@lshep, #230) 18 | 19 | BUG FIXES AND MINOR IMPROVEMENTS 20 | 21 | o `Rd` pages that do not have `\usage` sections are excluded from `\value` 22 | checks (@hpages, #225) 23 | o `Rd` pages that have `\keyword{internal}` are excluded from `\value` 24 | checks 25 | o Avoid overwritting `data` document types for `\value` checks 26 | o Vignettes with no code chunks were incorrectly flagged in chunk label 27 | checks 28 | o Chunks in Rnw vignettes with no labels are now correctly identified 29 | o `sessionInfo` checks in vignettes occur only when code chunks are present 30 | o BiocCheck() options are now ordered alphabetically in documentation to 31 | make it easier to find specific options (@Bisaloo, #229). 32 | 33 | CHANGES IN VERSION 1.44.0 34 | ----------------------- 35 | 36 | NEW FEATURES 37 | 38 | o Add `lifeCycle`, `deprecate_warn`, and `deprecate_stop` to the list of 39 | functions for the `.Deprecated` / `.Defunct` check. 40 | o Support checking `\()` syntax as anonymous R functions. 41 | o Exclude comment-only lines in function lengths check (@hpages, #220) 42 | o Add `checkFndPerson()` to validate Authors@R / Authors fields in the 43 | `DESCRIPTION` file. A corresponding `NOTE` is shown in the vignette (#215) 44 | o Refactor function length check to count only coding lines and not comment 45 | lines (@hpages, #220) 46 | 47 | BUG FIXES AND MINOR IMPROVEMENTS 48 | 49 | o Check file sizes only for source directories and in `BiocCheckGitClone` 50 | (@hpages, #219) 51 | o Use R version from `BiocManager:::.version_map` for 52 | `checkRVersionDependency` (@helenalc, #223) 53 | o Use `checkInstalled()` to verify packages listed in `Suggests:` 54 | o Suppress warning in `.hasPkg()` when the package is not installed 55 | o Use `INFO` messages instead of bullet points in output 56 | 57 | CHANGES IN VERSION 1.42.0 58 | ----------------------- 59 | 60 | NEW FEATURES 61 | 62 | o Use the `cli` R package to update the aesthetics of the output. 63 | 64 | BUG FIXES AND MINOR IMPROVEMENTS 65 | 66 | o Fix issue in `checkVigSuggests` internal function to only extract 67 | packages that should be in the `Suggests` field. 68 | o Fix issue where the lookbacks in static code searches were not 69 | iterating over all of the indices. This affects all functions that 70 | searched through code in R and vignette files. 71 | o Non-interactive sessions default to old output syntax. 72 | o Use full names e.g., 'WARNING' in report output for `grep`ping by 73 | dependent software, i.e., Single Package Builder (SPB) 74 | 75 | 76 | CHANGES IN VERSION 1.40.0 77 | ----------------------- 78 | 79 | NEW FEATURES 80 | 81 | o Check for duplicate chunk labels in vignettes and produce an error when 82 | present (@hpages, #199) 83 | o Warn when Rnw vignettes are present (@jwokaty, #190) 84 | o Add error when maintainer email not parse-able in DESCRIPTION 85 | o Check for Bioconductor dependencies in the Imports and Depends fields; 86 | if none, produce a warning (@vjcitn). 87 | o Remove redundant `DESCRIPTION` and `NAMESPACE` consistency checks; these 88 | are already included in `R CMD check` 89 | 90 | BUG FIXES AND MINOR IMPROVEMENTS 91 | 92 | o Produce `NOTE` instead of `WARNING` for infrastructure packages without 93 | Bioconductor dependencies 94 | o `% \VignetteEngine` tags with spaces were not recognized; this has been 95 | fixed 96 | o Skip `\value` checks for package and class documentation; `\formats` 97 | checked for data documentation 98 | o Delegate system calls to `devtools::build` to build packages 99 | o Update `checkRbuildignore` to flag `test` and `longtests` entries 100 | appropriately (@hpages, #197) 101 | o Fix issue where BiocCheck would fail when errors caused by duplicate chunk 102 | labels were present (@hpages, #199) 103 | o Avoid format checks e.g., length, indentation, and tabs for Rd files 104 | that use roxygen2 105 | o Use the `tools:::analyze_licenses` function to check for restrictive 106 | license use (@hpages, #119) 107 | o Use httr2 over httr for http requests 108 | o Clarify `NOTE` and `WARNING` messages for `CITATION` file inclusion 109 | (#209). 110 | 111 | CHANGES IN VERSION 1.38.0 112 | ----------------------- 113 | 114 | BUG FIXES AND MINOR IMPROVEMENTS 115 | 116 | o Exclude data docs with `\\format` tags in addition to 'package' docs when 117 | checking for `\\value` / `@return` in documentation. 118 | o Resolve unknown macro warnings when using Rdpack (@LiNK-NY, #196) 119 | o Improve the `read.dcf` operation that looks for any deprecated packages 120 | in both Bioconductor release and devel versions. 121 | o Remove overwrite prompt when updating cached resources in deprecated 122 | packages check. 123 | 124 | CHANGES IN VERSION 1.36.0 125 | ----------------------- 126 | 127 | NEW FEATURES 128 | 129 | o Include size limit checks for data files in `data`, `inst/extdata`, and 130 | `data-raw` folders (@lshep, @const-ae, #167, #67) 131 | o Source package directories that include an `inst/doc` folder with files 132 | are now flagged with an error. `doc` folders are generated during 133 | `R CMD build`. 134 | o The error for packages already hosted on CRAN has been converted to a 135 | warning (@lshep, #177). Any such incoming packages must be removed from CRAN 136 | before the following Bioconductor release. 137 | 138 | BUG FIXES AND MINOR IMPROVEMENTS 139 | 140 | o Filter out 'package' docTypes from '\value' documentaiton checks 141 | (@grimbough, #189) 142 | o Obtain a more complete list of deprecated packages for 143 | `checkDeprecatedPkgs` 144 | o Fix issue with path seperators on Windows ('\\' vs '/') causing the unit 145 | test for `getBiocCheckDir` to report erroneous mismatches (@grimbough, #175) 146 | o Fix bug where the wrong number of functions with length greater than 50 147 | was reported (@grimbough, #182) 148 | o biocViews term suggestions should be a scalar character (@lcolladotor, 149 | #184) 150 | o Update email in bioc-devel subscription check (@lshep, #185) 151 | o Handle function length checks when there is no R code (@lshep, #186) 152 | o Edit warning text when empty or missing `value` sections are found in 153 | a package (@vjcitn) 154 | o `checkForValueSection` re-implemented for clarity; filters out comments 155 | in `value` sections (@LiNk-NY) 156 | o Correctly identify `Rd` comments by updating the regular expression to 157 | identify them (@LiNk-NY) 158 | 159 | CHANGES IN VERSION 1.34 160 | ----------------------- 161 | 162 | NEW FEATURES 163 | 164 | o Redundant package dependencies checks between `DESCRIPTION` and 165 | `NAMESPACE` have been removed. These are already present in `R CMD check` 166 | as "checking package dependencies". 167 | o Use `callr` to run `BiocCheck` in a separate process, this avoids 168 | interference with loaded packages (@vjcitn, #158) 169 | 170 | BUG FIXES AND MINOR IMPROVEMENTS 171 | 172 | o Update `checkVigInstalls` and `checkVigBiocInst` to avoid false positives 173 | (@almeidasilvaf, #170). 174 | o Only count non evaluated chunks when there are any present in the 175 | vignette 176 | o Fix false positive WARNING "Import {pkg} in NAMESPACE as well as 177 | DESCRIPTION." where pkg was not in NAMESPACE but it was used using double 178 | colons pkg::function inside an S4 method. (@zeehio, #166) 179 | o Fix bug where inputs to `getDirFile` were vectors in 180 | `checkForValueSection` (@zeehio, #163) 181 | o Allow lookback for matching T/F and exclude list elements, e.g., `list$F` 182 | (@lshep, #161) 183 | o Fix indentation count by excluding yaml front matter from vignettes 184 | (@harpomaxx, #100) 185 | o Update internal documentation of the `BiocCheck-class` 186 | o Fix bug where line numbers were off due to removal of empty lines at 187 | parsing (@lshep, #159) 188 | o Slightly improve sentence counter for Description field check 189 | (@lshep, #160) 190 | o Update documentation links to point to contributions.bioconductor.org 191 | (@LiNk-NY, #157) 192 | 193 | CHANGES IN VERSION 1.32 194 | ----------------------- 195 | 196 | NEW FEATURES 197 | 198 | o Add package metadata to main report for easier diagnostics 199 | o `.BiocCheck` folder, created above the package folder, includes 200 | the full report and NAMESPACE suggestions, if available. 201 | o Add check to find any stray `.BiocCheck` folders 202 | o Update doc links and recommendations for additional information in report 203 | o Update `BiocCheck` report to be more brief by only noting the conditions; 204 | details are included in the full report 205 | 206 | BUG FIXES AND MINOR IMPROVEMENTS 207 | 208 | o Initialize default verbose value (FALSE) for internal reference object 209 | o Flag only hidden '.RData' files as bad files and allow 'myData.RData' 210 | (@hpages, #155) 211 | o Improve internal handling of condition messages with unified mechanism 212 | o Internal improvements to `BiocCheck` mechanism: export `.BiocCheck` object 213 | which contains all conditions, log list, and method for writing to JSON 214 | o Update to changes in R 4.2 `--no-echo` flag 215 | o Make use of `lib.loc` to helper functions that install and load the 216 | checked package 217 | o (1.31.36) Reduce function length count slightly by removing empty lines. 218 | o (1.31.35) Restricted files in `inst` will be flagged with a `WARNING` 219 | instead of an `ERROR` 220 | o (1.31.32) Account for S3 print methods when checking for `cat` usage 221 | o (1.31.31) Single package imports in the NAMESPACE were breaking the code 222 | to get all package imports. 223 | o (1.31.29) Include other import fields from NAMESPACE file when checking 224 | consistency between imports in DESCRIPTION/NAMESPACE. 225 | o (1.31.27) Update and clean up unit tests. 226 | o (1.31.26) Improve load test for the package being checked. 227 | o (1.31.25) Exclude GitHub URLs that end in HTML from external data check. 228 | o (1.31.23) Internal updates to the `require` and `library` check. 229 | o (1.31.22) Remove old code related to running `BiocCheck` on the command 230 | line and update `BiocCheck` documentation. 231 | o (1.31.21) Remove redundant `=` from message to avoid `=` assignment. 232 | o (1.31.20) Add line feed to "Checking function lengths..." message 233 | o (1.31.18) Packages should not download files when loaded or attached. 234 | o (1.31.17) Using '=' for assignment should be avoided and '<-' should be 235 | used instead for clarity and legibility. 236 | o (1.31.16) Note the use of `cat` and `print` outside of show methods. 237 | o (1.31.15) Check for pinned package versions in the `DESCRIPTION` file 238 | denoted by the use of `==`. 239 | o (1.31.14) Enhancements to internal helper functions and 240 | `BiocCheckGitClone` 241 | o (1.31.13) Revert move to new package checks. Update Bioc-devel mailing 242 | list check to fail early when not in BBS environment. 243 | o (1.31.12) Move Bioc-devel mailing list and support site registration 244 | checks to new package checks. 245 | o (1.31.10) Various internal improvements to `BiocCheck` and the 246 | identification of the package directory and name. 247 | o (1.31.6) Use a more reliable approach to identify package name from the 248 | `DESCRIPTION` file. 249 | o (1.31.5) Fixed bug in the case where the `VignetteBuilder` field in the 250 | a package's `DESCRIPTION` has more than one listed. 251 | o (1.31.3) Add `BioCbooks` repository url to 252 | `checkIsPackageNameAlreadyInUse`, `VIEWS` file is pending. 253 | o (1.31.2) Fix logical length > 1 error in `checkImportSuggestions` 254 | (@vjcitn, #141) 255 | o (1.31.1) Simplify check for function lengths; remove excessive dots. 256 | 257 | CHANGES IN VERSION 1.30 258 | ----------------------- 259 | 260 | NEW FEATURES 261 | 262 | o (1.29.10) Check for `Sys.setenv` and `suppressWarnings`/`suppressMessages` 263 | o (1.29.8) Check for `sessionInfo` / `session_info` in vignette code. 264 | o (1.29.5) Check for installation calls in vignette code. 265 | o (1.29.1) Check for `install()` function calls in R code. 266 | 267 | BUG FIXES 268 | 269 | o (1.29.14) Various internal improvements to the codebase. 270 | o (1.29.12) Checks on class membership code now include `is() ==` grammar. 271 | o (1.29.6) Use appropriate input (`pkgdir`) to internal checking functions. 272 | o (1.29.3) Add unit tests for legacy function searches. 273 | o (1.29.2) rename internal function from checkIsPackageAlreadyInRepo to 274 | checkIsPackageNameAlreadyInUse 275 | 276 | CHANGES IN VERSION 1.27 277 | ----------------------- 278 | 279 | BUG FIX 280 | 281 | o (1.27.17) Update support site watched tags. tags are case insensitive 282 | o (1.27.15) Reporting checking of vignette despite package type 283 | (@lshep, #136) 284 | o (1.27.9) Allow portability of child Rmd documents via parseFile 285 | o (1.27.3) Correct check for if package already exists in CRAN/Bioc 286 | o (1.27.3) Correct check for single colon use 287 | o (1.27.2) Correct path to R license database file by calling 288 | R.home('share'). 289 | 290 | 291 | NEW FEATURES 292 | 293 | o (1.27.16) Check vignettes for all package types (@lshep, #136) 294 | o (1.27.12) Check for `LazyData: TRUE` in the DESCRIPTION (@lshep, #128) 295 | o (1.27.11) R version dependency check in the 'DESCRIPTION' is now a 296 | 'NOTE' (@lshep, #126) 297 | o (1.27.10) Check for 'error' and other keywords in signaler functions, 298 | 'message', 'warning', and 'stop' etc. (@hpages, #125) 299 | o (1.27.8) Check for 'tests' entry in '.Rbuildignore' 300 | o (1.27.7) Removed BiocCheck and BiocCheckGitClone installation scripts; 301 | recommended usage is `BiocCheck()` 302 | o (1.27.6) Check that a user has the package name in watched tags of 303 | support site 304 | o (1.27.5) Check for 'paste' / 'paste0' in signaler functions, 'message', 305 | 'warning', and 'stop' (@LiNk-NY, #64) 306 | o (1.27.4) Check for downloads from external resources (github, gitlab, 307 | bitbucket, dropbox; @LiNk-NY, #75) 308 | o (1.27.1) Check that licenses do not exclude classes of users, 309 | e.g., non-academic users. 310 | 311 | CHANGES IN VERSION 1.25 312 | ----------------------- 313 | 314 | DEPRECATION ANNOUNCEMENT 315 | 316 | o (1.25.11) R CMD BiocCheck and R CMD BiocCheckGitClone are deprecated. The 317 | recommended way to run the functions is within R. 318 | 319 | NEW FEATURES 320 | 321 | o (1.25.1) Check for warning/notes on too-brief a Description: field 322 | (@federicomarini, #65) 323 | o (1.25.4) Check for single colon typos when using qualified imports 324 | pkg::foo() 325 | o (1.25.8) Validate ORCID iDs (if any) in the DESCRIPTION file 326 | (@LiNk-NY, #97) 327 | o (1.25.10) Add check for properly formatted CITATION file 328 | o (1.25.12) Add NOTE to change dontrun to donttest 329 | 330 | BUG FIXES 331 | 332 | o (1.25.14) The ORCID iD check now accepts IDs with a X at the end. 333 | o (1.25.9) All packages including infrastructure require a vignette 334 | o Usage of donttest and dontrun in manual pages tagged with the keyword 335 | 'internal' will no longer trigger a NOTE (@grimbough, #59) 336 | o (1.25.7) Adding the sessionInfo at the end of the vignette (@llrs) 337 | 338 | USER SIGNIFICANT CHANGES 339 | 340 | o (1.25.3) Require Aurhors@R format over Author/Maintainer fields in the 341 | DESCRIPTION file. This has been upgraded to an ERROR. 342 | o (1.25.2) Suggest styler over formatR for automatic code re-formatting 343 | (@lcolladotor, #57). 344 | o (1.25.5) Add warning to new package versions with non-zero x version 345 | (@mtmorgan, #101) 346 | 347 | 348 | CHANGES IN VERSION 1.23 349 | ----------------------- 350 | 351 | BUG FIX 352 | 353 | o (1.23.4) Update locations for NEWS 354 | o (1.23.1) Fix False Positive class == check 355 | 356 | CHANGES IN VERSION 1.19 357 | ----------------------- 358 | 359 | NEW FEATURES 360 | 361 | o (1.19.33) Add Authors@R vs Author/Maintainer check 362 | o (1.19.29) Add non evaluated code chunks that have not been executed 363 | because of invalid syntax (```, ```R, ```r). Valid syntax for an evaluated 364 | code chunk takes the form ```{r} or ```{R}. 365 | o (1.19.28) Check that vignette metadata set correctly 366 | o (1.19.27) Check for Author@R or Author/Maintainer but not both 367 | o (1.19.25) Check for use of remotes: in Description 368 | o (1.19.18) Check for use of dontrun/donttest 369 | o (1.19.15) Check vignetteEngine/vignetteBuilder minimially declared in 370 | Suggests 371 | o (1.19.9) Check usage of donttest and dontrun in man page documentation 372 | o (1.19.9) Update deprecated check to check for Bioconductor release and 373 | devel deprecated packages as specified in biocViews 374 | o (1.19.7) More helpful ERROR when using a non valid command line option. 375 | o (1.19.4) All checks module. Ability to turn on/off with flag options. See 376 | 'R CMD BiocCheck --help' 377 | o (1.19.1) New Check options to turn off if in CRAN (--no-check-CRAN) and 378 | Bioconductor mailing list and support site (--no-check-bioc-help) 379 | 380 | USER SIGNIFICANT CHANGES 381 | 382 | o (1.19.8) Updated Documentation in vignette for flag/option controls and 383 | reorganization of code/checks. Grouped similar checks together and changed 384 | order of checks. 385 | o (1.19.3) Remove Native Routine Registrations (use flag in R CMD check 386 | instead _R_CHECK_NATIVE_ROUTINE_REGISTRATION_) 387 | o (1.19.2) Match CRAN standards of package size <= 5MB (updated from 4MB) 388 | 389 | BUG FIXES 390 | 391 | o (1.19.26) Fix NEWS check to recognize NEWS.md 392 | o (1.19.26) Check all repos for existing package not just software 393 | o (1.19.23) Informative message if no biocViews term found 394 | o (1.19.22) Fix output of system2 usage check 395 | o (1.19.19) Test only closures for T/F 396 | o (1.19.14) Fix ERROR when more than one VignetteEngine found 397 | o (1.19.10) Add suggestion of FormatR package to formatting notes 398 | o (1.19.10) Fix function lengths to be a NOTE and only display if functions 399 | are greater than 50 lines long. 400 | o (1.19.6) Replace use of devtools::create with usethis::create_package as 401 | function was deprecated. 402 | o (1.19.5) Fix length > 1 logical comparison in if statement 403 | 404 | CHANGES IN VERSION 1.17 405 | ----------------------- 406 | 407 | NEW FEATURES 408 | 409 | o (1.17.21) Added quit-with-status option to both BiocCheck and 410 | BiocCheckGitClone for compatibility with travis 411 | 412 | o (1.17.18) Update devel to use BiocManager instructions instead of 413 | BiocInstaller 414 | 415 | o (1.17.17) Add a new function that can be run interactive or command line 416 | BiocCheckGitClone which is only run on a source directory not a 417 | tarball. This will check for bad system files 418 | 419 | o (1.17.17) BiocCheck addition: Checks vignette directory for intermediate 420 | and end files that should not be included. 421 | 422 | o (1.17.16) Checks for Bioconductor package size requirement if checking 423 | tarball 424 | 425 | BUG FIXES 426 | 427 | o (1.17.19) Updated internal functions to use BiocManger instead of 428 | BiocInstaller 429 | 430 | CHANGES IN VERSION 1.16 431 | ----------------------- 432 | 433 | BUG FIXES 434 | 435 | o handle interactive BiocCheck() arguments correctly 436 | 437 | CHANGES IN VERSION 1.14 438 | ----------------------- 439 | 440 | NEW FEATURES 441 | 442 | o NOTE when maintainer subscription to bioc-devel mailing list 443 | cannot be checked (checking requires mailing list admin 444 | password). 445 | 446 | BUG FIXES 447 | 448 | o Use shell quotes to allow spaces in package paths 449 | -------------------------------------------------------------------------------- /R/checkVignettes.R: -------------------------------------------------------------------------------- 1 | # Vignette Checks --------------------------------------------------------- 2 | 3 | checkVignetteDir <- function(.BiocPackage) 4 | { 5 | pkgdir <- .BiocPackage$sourceDir 6 | vigdir <- .BiocPackage$vignettesDir 7 | 8 | if (!dir.exists(vigdir)) { 9 | handleError("No 'vignettes' directory.") 10 | return() 11 | } 12 | 13 | vigdircontents <- .BiocPackage$VigSources 14 | if (!length(vigdircontents)) { 15 | handleError("No vignette sources in vignettes/ directory.") 16 | return() 17 | } 18 | 19 | checkInstContents(.BiocPackage) 20 | 21 | checkVigFiles(.BiocPackage) 22 | 23 | checkVigBuilder(.BiocPackage) 24 | 25 | checkVigMetadata(vigdircontents) 26 | 27 | checkVigTypeRNW(.BiocPackage) 28 | 29 | checkVigTypeQMD(.BiocPackage) 30 | 31 | checkVigEngine(.BiocPackage) 32 | 33 | checkVigSuggests(.BiocPackage) 34 | 35 | checkVigTemplate(vigdircontents) 36 | 37 | checkVigChunkEval(vigdircontents) 38 | 39 | checkDupChunkLabels(vigdircontents) 40 | 41 | checkChunkLabels(vigdircontents) 42 | 43 | checkVigBiocInst(.BiocPackage) 44 | 45 | checkVigInstalls(.BiocPackage) 46 | 47 | checkVigClassUsage(.BiocPackage) 48 | 49 | checkTFSymbolUsage(.BiocPackage) 50 | 51 | checkVigSessionInfo(.BiocPackage) 52 | 53 | checkVigEvalAllFalse(.BiocPackage) 54 | } 55 | 56 | checkInstContents <- function(.BiocPackage) 57 | { 58 | instdocdir <- file.path(.BiocPackage$sourceDir, "inst", "doc") 59 | contents <- list.files( 60 | instdocdir, 61 | pattern = "\\.Rmd$|\\.Rnw$|\\.Rrst$|\\.Rhtml$|\\.Rtex$", 62 | ignore.case = TRUE, full.names = TRUE 63 | ) 64 | if (length(contents) && .BiocPackage$isSourceDir) 65 | handleWarning( 66 | "Remove vignette sources from inst/doc; ", 67 | "they belong in vignettes/." 68 | ) 69 | } 70 | 71 | checkVigFiles <- function(.BiocPackage) { 72 | vigdir <- .BiocPackage$vignettesDir 73 | vigdircontents <- .BiocPackage$VigSources 74 | vigs <- tolower(basename(vigdircontents)) 75 | allvigfiles <- setdiff( 76 | tolower( 77 | list.files( 78 | vigdir, all.files = TRUE, ignore.case = TRUE, recursive = TRUE 79 | ) 80 | ), 81 | vigs 82 | ) 83 | 84 | if (length(allvigfiles) != 0) { 85 | badFiles <- unlist(lapply(vigs, 86 | FUN = function(x, allvigfiles){ 87 | vl <- tools::file_path_sans_ext(x) 88 | badext <- c(".tex", ".html", ".pdf", 89 | ".aux", ".log") 90 | ext <- paste0(vl, badext) 91 | fnd <- intersect(allvigfiles, ext) 92 | fnd 93 | }, 94 | allvigfiles = allvigfiles)) 95 | if (length(badFiles) != 0){ 96 | handleNote( 97 | "Potential intermediate files found:", 98 | messages = paste0("vignettes/", badFiles) 99 | ) 100 | } 101 | } 102 | } 103 | 104 | checkVigBuilder <- function(.BiocPackage) 105 | { 106 | builders <- .BiocPackage$VigBuilder 107 | builders <- builders[builders != "Sweave"] 108 | if (!length(builders)) 109 | return() 110 | vigdircontents <- .BiocPackage$VigSources 111 | # check DESCRIPTION is in at least one vignette 112 | vigExt <- tolower(tools::file_ext(vigdircontents)) 113 | vigdircontents <- vigdircontents[vigExt != "rnw"] 114 | 115 | badBuilder <- character(0) 116 | for (builder in builders) { 117 | res <- vapply( 118 | vigdircontents, isEngineInBuilder, logical(1), builder = builder 119 | ) 120 | if (any(!res, na.rm=TRUE)) { 121 | badBuilder <- c(badBuilder, builder) 122 | } 123 | } 124 | # check if a listed builder is not found in any vignette 125 | if (length(badBuilder)) { 126 | handleError( 127 | "'VignetteBuilder' listed in DESCRIPTION but not ", 128 | "found as 'VignetteEngine' in any vignettes:", 129 | messages = badBuilder 130 | ) 131 | } 132 | } 133 | 134 | checkVigTypeRNW <- function(.BiocPackage) { 135 | vigdircontents <- .BiocPackage$VigSources 136 | vigExt <- tolower(tools::file_ext(vigdircontents)) 137 | isRNW <- vigExt == "rnw" 138 | vigNames <- basename(vigdircontents[isRNW]) 139 | if (length(vigNames)) 140 | handleWarning( 141 | "Use RMarkdown instead of Sweave 'Rnw' vignettes.", 142 | help_text = "Rnw vignette(s) found:", 143 | messages = vigNames 144 | ) 145 | } 146 | 147 | checkVigTypeQMD <- function(.BiocPackage) { 148 | vigdircontents <- .BiocPackage$VigSources 149 | vigExt <- tolower(tools::file_ext(vigdircontents)) 150 | isQMD <- vigExt == "qmd" 151 | vigNames <- basename(vigdircontents[isQMD]) 152 | desc <- .BiocPackage$DESCRIPTION 153 | if (length(vigNames)) { 154 | if (!"SystemRequirements" %in% colnames(desc)) 155 | handleWarning( 156 | "Quarto vignette found but 'SystemRequirements'", 157 | " field not in DESCRIPTION." 158 | ) 159 | else if (!grepl("quarto", desc[, "SystemRequirements"])) 160 | handleWarning( 161 | "Quarto vignette found but 'SystemRequirements'", 162 | " does not list 'quarto'." 163 | ) 164 | } 165 | } 166 | 167 | checkVigMetadata <- function(vigdircontents) 168 | { 169 | badVig <- character(0) 170 | vigExt <- tolower(tools::file_ext(vigdircontents)) 171 | dx <- which(vigExt != "rnw") 172 | vigdircontents <- vigdircontents[dx] 173 | for (file in vigdircontents) { 174 | lines <- readLines(file, n=100L, warn=FALSE) 175 | idx <- grep(lines, pattern="vignette:") 176 | if (length(idx) == 0L) 177 | badVig <- c(badVig, basename(file)) 178 | } 179 | if (length(badVig) != 0L){ 180 | handleWarning( 181 | "Vignette(s) missing Vignette metadata. See ", 182 | "http://r-pkgs.had.co.nz/vignettes.html", 183 | help_text = "Update the following files:", 184 | messages = badVig 185 | ) 186 | } 187 | } 188 | 189 | checkVigEngine <- function(.BiocPackage) 190 | { 191 | builder <- .BiocPackage$VigBuilder 192 | vigdircontents <- .BiocPackage$VigSources 193 | # check Engines are in DESCRIPTION 194 | vigExt <- tolower(tools::file_ext(vigdircontents)) 195 | vigdircontents <- vigdircontents[vigExt != "rnw"] 196 | 197 | # check for very rare case that multiple build 198 | # engines specified in vignette 199 | res <- lapply(vigdircontents, getVigEngine) 200 | if (any(lengths(res) > 1L)) { 201 | handleErrorFiles( 202 | "More than one VignetteEngine specified.", 203 | messages = names(which(lengths(res) > 1L)) 204 | ) 205 | } 206 | # check for missing engine 207 | if (any(!lengths(res))) { 208 | handleError( 209 | "No 'VignetteEngine' specified in vignette.", 210 | help_text = "Add 'VignetteEngine' to the following files:", 211 | messages = names(res[!lengths(res)]) 212 | ) 213 | } 214 | vigdircontents <- vigdircontents[lengths(res) == 1L] 215 | if (length(vigdircontents)) { 216 | res <- vapply( 217 | vigdircontents, isEngineInBuilder, logical(1), builder = builder 218 | ) 219 | # check for missing engine in DESCRIPTION 220 | if (any(!res)) { 221 | handleError( 222 | "'VignetteEngine' specified but not in the DESCRIPTION.", 223 | help_text = 224 | "Add 'VignetteEngine' to DESCRIPTION from the following:", 225 | messages = names(res[!res]) 226 | ) 227 | } 228 | # check for missing engine in vignette 229 | if (anyNA(res)) { 230 | nadx <- which(is.na(res)) 231 | files <- names(res[nadx]) 232 | if (is.null(builder)) 233 | files <- c(files, "DESCRIPTION") 234 | handleError( 235 | "No 'VignetteEngine' specified in vignette or DESCRIPTION. ", 236 | help_text = paste( 237 | "Add a 'VignetteEngine' to the following files or", 238 | "a default 'VignetteBuilder' in DESCRIPTION: " 239 | ), 240 | messages = files 241 | ) 242 | } 243 | } 244 | } 245 | 246 | checkVigSuggests <- function(.BiocPackage) 247 | { 248 | builder <- .BiocPackage$VigBuilder 249 | vigdircontents <- .BiocPackage$VigSources 250 | vigExt <- tolower(tools::file_ext(vigdircontents)) 251 | res <- lapply(vigdircontents, getVigEnginePkg) 252 | lst <- unique(c(unlist(unname(res)), builder)) 253 | if (any(is.na(lst))) 254 | lst <- lst[!is.na(lst)] 255 | dep <- .BiocPackage$dependencies 256 | if (!all(lst %in% dep)){ 257 | handleWarning( 258 | "Package listed as VignetteEngine or VignetteBuilder ", 259 | "but not currently Suggested. ", 260 | help_text = "Add the following to Suggests in DESCRIPTION:", 261 | messages = lst[!(lst %in% dep)] 262 | ) 263 | } 264 | } 265 | 266 | checkVigTemplate <- function(vigdircontents) 267 | { 268 | badVig <- character(0) 269 | badVig2 <- character(0) 270 | for (file in vigdircontents) { 271 | lines <- readLines(file, warn=FALSE) 272 | if (tolower(tools::file_ext(file)) %in% c("rmd", "qmd")) 273 | lines <- .getYAMLfront(lines) 274 | idx <- grep(lines, pattern="VignetteIndexEntry", fixed = TRUE) 275 | if (length(idx)) { 276 | title <- tolower(gsub(".*\\{|\\}.*", "", lines[idx])) 277 | if (identical(title, "vignette title")) 278 | badVig <- c(badVig, basename(file)) 279 | } 280 | if (!length(idx)) 281 | badVig2 <- c(badVig2, basename(file)) 282 | } 283 | if (length(badVig)) 284 | handleWarning( 285 | "Vignette(s) still using 'VignetteIndexEntry{{Vignette Title}}' ", 286 | help_text = "The following files use template defaults:", 287 | messages = badVig 288 | ) 289 | if (length(badVig2)) 290 | handleWarning( 291 | "Vignette(s) missing '\\%VignetteIndexEntry{{Vignette Title}}'. ", 292 | help_text = "Update the following files:", 293 | messages = badVig2 294 | ) 295 | } 296 | 297 | detect_non_eval_chunks <- function(lines, vignetteType) { 298 | non_eval_pattern <- switch( 299 | vignetteType, 300 | qmd = , 301 | rmd = "^[\t >]*```+\\s*$", 302 | rnw = "\\\\begin\\{verbatim\\}" 303 | ) 304 | chunk_patterns_start <- switch( 305 | vignetteType, 306 | qmd = "^```\\{r\\}", 307 | rmd = knitr::all_patterns[["md"]]$chunk.begin, 308 | rnw = knitr::all_patterns[["rnw"]]$chunk.begin 309 | ) 310 | chunk_patterns_end <- switch( 311 | vignetteType, 312 | qmd = , 313 | rmd = knitr::all_patterns[["md"]]$chunk.end, 314 | rnw = knitr::all_patterns[["rnw"]]$chunk.end 315 | ) 316 | 317 | chunk_starts <- grep(chunk_patterns_start, lines) 318 | ## find all potential chunk ends 319 | chunk_ends <- grep(chunk_patterns_end, lines) 320 | 321 | non_eval_chunk_lines <- grep(non_eval_pattern, lines) 322 | 323 | if (vignetteType %in% c("rmd", "qmd")) { 324 | matched_chunk_ends <- integer(0L) 325 | irregular_non_eval_chunks <- integer(0L) 326 | for (i in seq_along(chunk_starts)) { 327 | # Find the next end marker after this start 328 | next_end_index <- which(chunk_ends > chunk_starts[i])[1L] 329 | 330 | if (!is.na(next_end_index)) { 331 | matched_chunk_ends <- 332 | c(matched_chunk_ends, chunk_ends[next_end_index]) 333 | # Remove this end from further consideration 334 | chunk_ends <- chunk_ends[-next_end_index] 335 | } 336 | } 337 | irregular_non_eval_chunks <- chunk_ends 338 | } else if (identical(vignetteType, "rnw")) { 339 | irregular_non_eval_chunks <- grep(non_eval_pattern, lines) 340 | } else { 341 | stop("Unknown vignette type: ", vignetteType) 342 | } 343 | 344 | eval_false_lines <- grep("eval\\s*=\\s*F(ALSE)?", lines[chunk_starts]) 345 | 346 | list( 347 | chunks = length(chunk_starts) + length(irregular_non_eval_chunks), 348 | efs = length(eval_false_lines), 349 | noneval = length(irregular_non_eval_chunks) 350 | ) 351 | } 352 | 353 | .EVAL_CHUNKS_SENTINEL <- list( 354 | chunks = 0L, 355 | efs = 0L, 356 | noneval = 0L 357 | ) 358 | 359 | checkVigChunkEval <- function(vigdircontents) 360 | { 361 | results <- lapply( 362 | vigdircontents, 363 | function(file) { 364 | lines <- readLines(file, warn=FALSE) 365 | vigExt <- tolower(tools::file_ext(file)) 366 | if (!vigExt %in% c("rmd", "qmd", "rnw")) 367 | .EVAL_CHUNKS_SENTINEL 368 | else 369 | detect_non_eval_chunks(lines, vigExt) 370 | } 371 | ) 372 | 373 | combined <- Reduce( 374 | function(x, y) { 375 | list( 376 | chunks = x$chunks + y$chunks, 377 | efs = x$efs + y$efs, 378 | noneval = x$noneval + y$noneval 379 | ) 380 | }, results, init = .EVAL_CHUNKS_SENTINEL 381 | ) 382 | 383 | totnon <- combined$efs + combined$noneval 384 | percent <- 385 | if (!combined$chunks && !totnon) 386 | 0L 387 | else 388 | as.integer((totnon * 100 / combined$chunks)) 389 | 390 | if (percent >= 50) { 391 | handleWarning("Evaluate more vignette chunks.") 392 | msg <- sprintf( 393 | "%s out of %s code chunks = %i%% unevaluated", 394 | totnon, 395 | combined$chunks, 396 | percent 397 | ) 398 | handleMessage(msg, indent = 8) 399 | handleMessage( 400 | sprintf( 401 | "%s non-exec code chunk(s) (e.g., '```r')", 402 | combined$noneval 403 | ), 404 | indent = 8 405 | ) 406 | } 407 | } 408 | 409 | checkVigEvalAllFalse <- function(.BiocPackage) { 410 | vigfiles <- .BiocPackage$VigSources 411 | shortnames <- .getDirFiles(vigfiles) 412 | viglist <- structure( 413 | vector("logical", length(vigfiles)), .Names = shortnames 414 | ) 415 | for (i in seq_along(vigfiles)) { 416 | shortName <- shortnames[i] 417 | tempR <- tempfile(fileext=".R") 418 | try_purl_or_tangle(input = vigfiles[i], output = tempR, quiet = TRUE) 419 | pfile <- parseFile(.BiocPackage, tempR) 420 | symbolOK <- .getTokenTextCode( 421 | parsedf = pfile, 422 | token = "SYMBOL_FUNCTION_CALL", 423 | text = "set", 424 | hasLookback = c("opts_chunk", "$") 425 | ) 426 | if (nrow(symbolOK)) { 427 | setRange <- .findSymbolRanges(pfile, "set", "SYMBOL_FUNCTION_CALL") 428 | if (length(setRange) > 1L) 429 | warning("More than one `opts_chunk$set()` in ", vigfiles[i]) 430 | datarange <- pfile[unlist(setRange), ] 431 | viglist[[shortName]] <- 432 | grepl("eval=F", paste(datarange$text, collapse = "")) 433 | } 434 | } 435 | if (any(viglist)) { 436 | handleWarningFiles( 437 | " Vignette set global option 'eval=FALSE'", 438 | messages = names(viglist[viglist]) 439 | ) 440 | } 441 | } 442 | 443 | checkDupChunkLabels <- function(vigfiles) { 444 | viglist <- structure( 445 | vector("logical", length(vigfiles)), 446 | .Names = vigfiles 447 | ) 448 | for (vfile in vigfiles) { 449 | tempR <- tempfile(fileext=".R") 450 | tryCatch({ 451 | quiet_knitr_purl(input = vfile, output = tempR, quiet = TRUE) 452 | }, error = function(e) { 453 | viglist[[vfile]] <<- grepl( 454 | "Duplicate chunk label", conditionMessage(e), fixed = TRUE 455 | ) 456 | if (viglist[[vfile]]) 457 | invisible(NULL) 458 | else 459 | stop(e) 460 | }) 461 | } 462 | if (any(viglist)) 463 | handleErrorFiles( 464 | " Vignette(s) found with duplicate chunk labels", 465 | messages = basename(vigfiles[viglist]) 466 | ) 467 | } 468 | 469 | .hasAllChunkLabels <- function(viglines, type = c("rmd", "rnw", "qmd")) { 470 | type <- match.arg(type) 471 | pattern <- switch( 472 | type, 473 | qmd = , 474 | rmd = "^```\\{r", 475 | rnw = "^<<([^,>\\s]+)", 476 | ) 477 | sub <- switch( 478 | type, 479 | rmd = "```\\{r\\s([^,\\}]+).*\\}", 480 | rnw = "^<<\\s*([\\w-]+)\\s*(?:,.*)?>>=$", 481 | qmd = "#\\| label:" 482 | ) 483 | matches <- grep(pattern, viglines, value = TRUE) 484 | if (!length(matches)) 485 | return(TRUE) 486 | if (identical(type, "qmd")) { 487 | labelIdx <- grep(sub, viglines) 488 | length(labelIdx) >= length(matches) 489 | } else { 490 | all(grepl(sub, matches)) 491 | } 492 | } 493 | 494 | checkChunkLabels <- function(vigfiles) { 495 | viglist <- structure( 496 | vector("logical", length(vigfiles)), 497 | .Names = vigfiles 498 | ) 499 | for (vfile in vigfiles) { 500 | viglines <- readLines(vfile, warn = FALSE) 501 | vigext <- tolower(tools::file_ext(vfile)) 502 | viglist[[vfile]] <- .hasAllChunkLabels(viglines, type = vigext) 503 | } 504 | if (!all(viglist)) 505 | handleNoteFiles( 506 | " Vignette(s) found with missing chunk labels", 507 | messages = basename(vigfiles[!viglist]) 508 | ) 509 | } 510 | 511 | .OLD_INSTALL_CALLS <- 512 | c("BiocInstaller", "biocLite", "useDevel", "biocinstallRepos") 513 | 514 | checkVigBiocInst <- function(.BiocPackage) { 515 | msg_return <- findSymbolsInVignettes( 516 | .BiocPackage, 517 | Symbols = .OLD_INSTALL_CALLS, 518 | tokenTypes = c("COMMENT", "SYMBOL_FUNCTION_CALL") 519 | ) 520 | if (length(msg_return)) { 521 | handleWarningFiles( 522 | " BiocInstaller code found in vignette(s)", 523 | messages = msg_return 524 | ) 525 | } 526 | } 527 | 528 | checkVigInstalls <- function(.BiocPackage) { 529 | match_return <- findSymbolsInVignettes( 530 | .BiocPackage, 531 | Symbols = .BAD_INSTALL_CALLS, 532 | tokenTypes = "SYMBOL_FUNCTION_CALL" 533 | ) 534 | if (length(match_return)) 535 | handleErrorFiles( 536 | "Package installation calls found in vignette(s)", 537 | messages = unlist(match_return, use.names = FALSE) 538 | ) 539 | grep_return <- findSymbolsInVignettes( 540 | .BiocPackage, 541 | Symbols = ".*install[^ed].*", 542 | tokenTypes = "SYMBOL_FUNCTION_CALL", 543 | FUN = .grepTokenTextCode 544 | ) 545 | if (length(grep_return)) 546 | handleWarningFiles( 547 | "Potential package installation calls found in vignette(s)", 548 | messages = unlist(grep_return, use.names = FALSE) 549 | ) 550 | } 551 | 552 | checkTFSymbolUsage <- function(.BiocPackage) { 553 | viglist <- findSymbolsInVignettes(.BiocPackage, c("T", "F"), "SYMBOL") 554 | if (length(viglist)) { 555 | handleWarningFiles( 556 | " Avoid T/F variables; If logical, use TRUE/FALSE", 557 | messages = unlist(viglist, use.names = FALSE) 558 | ) 559 | } 560 | } 561 | 562 | ## Completely suppress spurious knitr:::remind_sweave() warning that shows up 563 | ## in TclTk popup window in addition to the usual text-only warning. 564 | quiet_knitr_purl <- function(...) 565 | { 566 | args <- list(...) 567 | callr::r( 568 | function(...) suppressWarnings(knitr::purl(...)), 569 | args = args, 570 | env = c(CI = "true") 571 | ) 572 | } 573 | 574 | purl_or_tangle <- function(input, output, quiet, ...) { 575 | vigEng <- getVigEnginePkg(input) 576 | vigExt <- tolower(tools::file_ext(input)) 577 | if (!identical(vigExt, "rnw") || vigEng %in% c("knitr", "quarto")) 578 | quiet_knitr_purl(input = input, output = output, quiet = quiet, ...) 579 | else 580 | utils::Stangle(file = input, output = output, quiet = quiet) 581 | } 582 | 583 | try_purl_or_tangle <- function(input, output, quiet, ...) { 584 | tryCatch({ 585 | purl_or_tangle(input = input, output = output, quiet = quiet, ...) 586 | }, error = function(e) { 587 | hasDups <- grepl( 588 | "Duplicate chunk label", conditionMessage(e), fixed = TRUE 589 | ) 590 | if (hasDups) { 591 | file.create(output) 592 | invisible(NULL) 593 | } else { 594 | stop(e) 595 | } 596 | }) 597 | } 598 | 599 | checkVigClassUsage <- function(.BiocPackage) { 600 | vigfiles <- .BiocPackage$VigSources 601 | viglist <- structure( 602 | vector("list", length(vigfiles)), .Names = basename(vigfiles) 603 | ) 604 | for (vfile in vigfiles) { 605 | tempR <- tempfile(fileext=".R") 606 | try_purl_or_tangle(input = vfile, output = tempR, quiet = TRUE) 607 | tokens <- getClassNEEQLookup(tempR) 608 | viglist[[basename(vfile)]] <- sprintf( 609 | "%s (code line %d, column %d)", 610 | basename(vfile), tokens[,"line1"], tokens[,"col1"] 611 | ) 612 | } 613 | viglist <- Filter(length, viglist) 614 | if (length(viglist)) { 615 | handleWarningFiles( 616 | " Avoid class membership checks with class() / is() and == / !=", 617 | "; Use is(x, 'class') for S4 classes", 618 | messages = unlist(viglist, use.names = FALSE) 619 | ) 620 | } 621 | } 622 | 623 | checkVigSessionInfo <- function(.BiocPackage) { 624 | vigfiles <- .BiocPackage$VigSources 625 | notFoundVig <- structure( 626 | vector("logical", length(vigfiles)), .Names = vigfiles 627 | ) 628 | for (vfile in vigfiles) { 629 | pc <- structure( 630 | list(parseFile(.BiocPackage, vfile)), .Names = vfile 631 | ) 632 | if (nrow(pc[[vfile]])) { 633 | res <- findSymbolsInParsedCode( 634 | parsedCodeList = pc, 635 | symbolNames = c("sessionInfo", "session_info"), 636 | tokenTypes = "SYMBOL_FUNCTION_CALL" 637 | ) 638 | if (!length(res)) 639 | notFoundVig[[vfile]] <- TRUE 640 | } 641 | } 642 | if (any(notFoundVig)) { 643 | handleNote( 644 | " 'sessionInfo' not found in vignette(s)", 645 | help_text = "Missing from file(s):", 646 | messages = .getDirFiles(vigfiles[notFoundVig]) 647 | ) 648 | } 649 | } 650 | --------------------------------------------------------------------------------