├── .Rbuildignore ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── R ├── checks.R ├── dontrun.R └── extrachecks.R ├── README.Rmd ├── README.md ├── extrachecks.Rproj ├── man ├── checks.Rd ├── extrachecks.Rd ├── parse_all_rds.Rd └── pkg_root.Rd └── tests ├── testthat.R └── testthat ├── test-dontrun.R └── testdata └── geom-text.Rd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^extrachecks\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: extrachecks 2 | Title: What the Package Does (One Line, Title Case) 3 | Version: 0.0.0.9000 4 | Authors@R: 5 | person("Josiah", "Parry", , "josiah.parry@gmail.com", role = c("aut", "cre"), 6 | comment = c(ORCID = "0000-0001-9910-865X")) 7 | Description: What the package does (one paragraph). 8 | License: MIT + file LICENSE 9 | Encoding: UTF-8 10 | Language: en 11 | Roxygen: list(markdown = TRUE) 12 | RoxygenNote: 7.2.3 13 | Imports: 14 | cli, 15 | rprojroot, 16 | tools 17 | Suggests: 18 | testthat (>= 3.0.0) 19 | Config/testthat/edition: 3 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2023 2 | COPYRIGHT HOLDER: extrachecks authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2023 extrachecks authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(check_dontrun) 4 | export(check_examples) 5 | export(check_license_year) 6 | export(check_return_values) 7 | export(extrachecks) 8 | -------------------------------------------------------------------------------- /R/checks.R: -------------------------------------------------------------------------------- 1 | 2 | has_tag <- function(rd, tag) { 3 | any(vapply(rd, attr, character(1), "Rd_tag") == tag) 4 | } 5 | 6 | 7 | #' Parses all Rd files 8 | parse_all_rds <- function(path) { 9 | man_path <- file.path(path, "man") 10 | rd_paths <- list.files(man_path, full.names= TRUE, pattern = ".R") 11 | setNames(lapply(rd_paths, tools::parse_Rd), basename(tools::file_path_sans_ext(rd_paths))) 12 | } 13 | 14 | #' Get the root path of an R package 15 | pkg_root <- function(path = ".") { 16 | rprojroot::find_root(rprojroot::is_r_package, path = path) 17 | } 18 | 19 | 20 | #' Extra checks for an R package 21 | #' 22 | #' @param path default `"."`. The path to an R package. 23 | #' @param tag an Rd tag to check for example `"\\value` 24 | #' @rdname checks 25 | check_tag <- function(tag, path = ".") { 26 | root <- pkg_root(path) 27 | rds <- parse_all_rds(root) 28 | res <- vapply(rds, has_tag, logical(1), tag = tag) 29 | 30 | index <- which(!res) 31 | 32 | if (length(index) > 0) { 33 | rd_names <- names(res)[index] 34 | fps <- file.path(path, "man", paste0(rd_names, ".Rd")) 35 | cli::cli_alert_warning("\\{tag} missing from: {.file {fps}}") 36 | } 37 | 38 | res 39 | } 40 | 41 | #' Common checks 42 | #' @export 43 | #' @rdname checks 44 | check_examples <- function(path = ".") { 45 | check_tag("\\examples", path) 46 | } 47 | 48 | #' @export 49 | #' @rdname checks 50 | check_return_values <- function(path = ".") { 51 | check_tag("\\value", path) 52 | } 53 | 54 | 55 | #' @export 56 | #' @rdname checks 57 | check_license_year <- function(path = ".") { 58 | license_path <- file.path(pkg_root(path), "LICENSE") 59 | if (file.exists(license_path)) { 60 | cli::cli_alert_info("No {.file LICENSE} file found") 61 | return(NA) 62 | } 63 | 64 | # parse license and identify line with year in it 65 | lic_lines <- readLines(license_path) 66 | year_index <- which(grepl("year", lic_lines, ignore.case = TRUE)) 67 | 68 | if (length(year_index) == 0 || length(year_index) > 1) { 69 | cli::cli_alert_danger("Cannot determine license year") 70 | return(NA) 71 | } 72 | 73 | lic_year <- as.numeric(gsub("\\D", "", lic_lines[year_index])) 74 | 75 | cur_year <- as.numeric(format(Sys.Date(), "%Y")) 76 | 77 | lic_current <- identical(lic_year, cur_year) 78 | 79 | if (lic_current) { 80 | cli::cli_alert_success("{.file LICENSE} year is current") 81 | } else { 82 | cli::cli_alert_warning("{.file LICENSE} year is out of date: {lic_year}") 83 | } 84 | 85 | invisible(lic_current) 86 | 87 | } 88 | -------------------------------------------------------------------------------- /R/dontrun.R: -------------------------------------------------------------------------------- 1 | has_dontrun <- function(rd) { 2 | example_index <- which(vapply(rd, attr, character(1), "Rd_tag") == "\\examples") 3 | if (length(example_index) == 0) { 4 | return(FALSE) 5 | } else { 6 | has_tag(rd[[example_index]], "\\dontrun") 7 | } 8 | } 9 | 10 | #' Check for `\dontrun` usage in examples 11 | #' 12 | #' @export 13 | #' @rdname checks 14 | check_dontrun <- function(path = ".") { 15 | root <- pkg_root(path) 16 | rds <- parse_all_rds(root) 17 | res <- vapply(rds, has_dontrun, logical(1)) 18 | 19 | index <- which(res) 20 | 21 | if (length(index) > 0) { 22 | cli::cli_alert_warning("\\dontrun used in: {.fn {names(res)[index]}}") 23 | } 24 | 25 | res 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /R/extrachecks.R: -------------------------------------------------------------------------------- 1 | #' Run all Extra Checks 2 | #' @inheritParams check_examples 3 | #' 4 | #' @details 5 | #' 6 | #' This is a bad path https://google.com/query 7 | #' 8 | #' @export 9 | #' @examples 10 | #' \dontrun{ 11 | #' stop("dont run this code") 12 | #' } 13 | #' 14 | #' httr::GET("google.com") 15 | extrachecks <- function(path = ".") { 16 | ex <- check_examples(path) 17 | val <- check_return_values(path) 18 | dont <- check_dontrun(path) 19 | urls <- urlchecker::url_check(path) 20 | invisible(list(examples = ex, value = val, dontrun = dont)) 21 | } 22 | 23 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | # extrachecks 17 | 18 | 19 | 20 | 21 | The goal of extrachecks is to help you ensure that you pass common manual checks that the CRAN team do that are not automated. 22 | 23 | Inspired by [DavisVaughan/extrachecks](https://github.com/DavisVaughan/extrachecks) and my own frustration in publishing packages to CRAN. 24 | 25 | Additional checks are very welcome via PR. 26 | 27 | ## Installation 28 | 29 | You can install the development version of extrachecks from [GitHub](https://github.com/) with: 30 | 31 | ``` r 32 | if (!require("remotes")) install.packages("remotes") 33 | remotes::install_github("JosiahParry/extrachecks") 34 | ``` 35 | 36 | ## Example 37 | 38 | Run the extra checks. 39 | 40 | ```{r} 41 | extrachecks::extrachecks() 42 | ``` 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # extrachecks 5 | 6 | 7 | 8 | 9 | The goal of extrachecks is to help you ensure that you pass common 10 | manual checks that the CRAN team do that are not automated. 11 | 12 | Inspired by 13 | [DavisVaughan/extrachecks](https://github.com/DavisVaughan/extrachecks) 14 | and my own frustration in publishing packages to CRAN. 15 | 16 | Additional checks are very welcome via PR. 17 | 18 | ## Installation 19 | 20 | You can install the development version of extrachecks from 21 | [GitHub](https://github.com/) with: 22 | 23 | ``` r 24 | if (!require("remotes")) install.packages("remotes") 25 | remotes::install_github("JosiahParry/extrachecks") 26 | ``` 27 | 28 | ## Example 29 | 30 | Run the extra checks. 31 | 32 | ``` r 33 | extrachecks::extrachecks() 34 | #> ! \\examples missing from: `checks()`, `parse_all_rds()`, and `pkg_root()` 35 | #> ! \\value missing from: `checks()`, `extrachecks()`, `parse_all_rds()`, and `pkg_root()` 36 | #> ! \dontrun used in: `extrachecks()` 37 | #> fetching [ 0 / 2 ]fetching [ 1 / 2 ] 38 | #> ✔ All URLs are correct! 39 | ``` 40 | -------------------------------------------------------------------------------- /extrachecks.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /man/checks.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/checks.R, R/dontrun.R 3 | \name{check_tag} 4 | \alias{check_tag} 5 | \alias{check_examples} 6 | \alias{check_return_values} 7 | \alias{check_license_year} 8 | \alias{check_dontrun} 9 | \title{Extra checks for an R package} 10 | \usage{ 11 | check_tag(tag, path = ".") 12 | 13 | check_examples(path = ".") 14 | 15 | check_return_values(path = ".") 16 | 17 | check_license_year(path = ".") 18 | 19 | check_dontrun(path = ".") 20 | } 21 | \arguments{ 22 | \item{tag}{an Rd tag to check for example \verb{"\\\\value}} 23 | 24 | \item{path}{default \code{"."}. The path to an R package.} 25 | } 26 | \description{ 27 | Extra checks for an R package 28 | 29 | Common checks 30 | 31 | Check for \verb{\dontrun} usage in examples 32 | } 33 | -------------------------------------------------------------------------------- /man/extrachecks.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/extrachecks.R 3 | \name{extrachecks} 4 | \alias{extrachecks} 5 | \title{Run all Extra Checks} 6 | \usage{ 7 | extrachecks(path = ".") 8 | } 9 | \arguments{ 10 | \item{path}{default \code{"."}. The path to an R package.} 11 | } 12 | \description{ 13 | Run all Extra Checks 14 | } 15 | \details{ 16 | This is a bad path https://google.com/query 17 | } 18 | \examples{ 19 | \dontrun{ 20 | stop("dont run this code") 21 | } 22 | 23 | httr::GET("google.com") 24 | } 25 | -------------------------------------------------------------------------------- /man/parse_all_rds.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/checks.R 3 | \name{parse_all_rds} 4 | \alias{parse_all_rds} 5 | \title{Parses all Rd files} 6 | \usage{ 7 | parse_all_rds(path) 8 | } 9 | \description{ 10 | Parses all Rd files 11 | } 12 | -------------------------------------------------------------------------------- /man/pkg_root.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/checks.R 3 | \name{pkg_root} 4 | \alias{pkg_root} 5 | \title{Get the root path of an R package} 6 | \usage{ 7 | pkg_root(path = ".") 8 | } 9 | \description{ 10 | Get the root path of an R package 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview 7 | # * https://testthat.r-lib.org/articles/special-files.html 8 | 9 | library(testthat) 10 | library(extrachecks) 11 | 12 | test_check("extrachecks") 13 | -------------------------------------------------------------------------------- /tests/testthat/test-dontrun.R: -------------------------------------------------------------------------------- 1 | test_that("multiplication works", { 2 | expect_equal(2 * 2, 4) 3 | }) 4 | -------------------------------------------------------------------------------- /tests/testthat/testdata/geom-text.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/geom-label.R, R/geom-text.R 3 | \name{geom_label} 4 | \alias{geom_label} 5 | \alias{geom_text} 6 | \title{Text} 7 | \usage{ 8 | geom_label( 9 | ) 10 | 11 | } 12 | \arguments{ 13 | \item{mapping}{Set of aesthetic mappings created by \code{\link[=aes]{aes()}}. If specified and 14 | \code{inherit.aes = TRUE} (the default), it is combined with the default mapping 15 | at the top level of the plot. You must supply \code{mapping} if there is no plot 16 | mapping.} 17 | 18 | \item{size.unit}{How the \code{size} aesthetic is interpreted: as millimetres 19 | (\code{"mm"}, default), points (\code{"pt"}), centimetres (\code{"cm"}), inches (\code{"in"}), 20 | or picas (\code{"pc"}).} 21 | 22 | \item{na.rm}{If \code{FALSE}, the default, missing values are removed with 23 | a warning. If \code{TRUE}, missing values are silently removed.} 24 | 25 | \item{show.legend}{logical. Should this layer be included in the legends? 26 | \code{NA}, the default, includes if any aesthetics are mapped. 27 | \code{FALSE} never includes, and \code{TRUE} always includes. 28 | It can also be a named logical vector to finely select the aesthetics to 29 | display.} 30 | 31 | \item{inherit.aes}{If \code{FALSE}, overrides the default aesthetics, 32 | rather than combining with them. This is most useful for helper functions 33 | that define both data and aesthetics and shouldn't inherit behaviour from 34 | the default plot specification, e.g. \code{\link[=borders]{borders()}}.} 35 | 36 | } 37 | \description{ 38 | Text geoms are useful for labeling plots. They can be used by themselves as 39 | } 40 | \details{ 41 | Note that when you resize a plot, text labels stay the same size, even 42 | } 43 | \section{Aesthetics}{ 44 | 45 | \code{geom_text()} understands the following aesthetics (required aesthetics are in bold) 46 | } 47 | 48 | \examples{ 49 | p <- ggplot(mtcars, aes(wt, mpg, label = rownames(mtcars))) 50 | 51 | p + geom_text() 52 | 53 | \dontrun{ 54 | # Doesn't work on all systems 55 | p + 56 | geom_text(family = "Times New Roman") 57 | } 58 | 59 | \donttest{ 60 | # Aligning labels and bars -------------------------------------------------- 61 | df <- data.frame( 62 | x = factor(c(1, 1, 2, 2)), 63 | y = c(1, 3, 2, 1), 64 | grp = c("a", "b", "a", "b") 65 | ) 66 | 67 | } 68 | } 69 | --------------------------------------------------------------------------------