├── .Rbuildignore ├── .github ├── .gitignore ├── dependabot.yml └── workflows │ ├── R-CMD-check.yaml │ ├── docker.yaml │ ├── monthly.yaml │ ├── pkgcheck.yaml │ ├── pr-commands.yaml │ └── test-coverage.yaml ├── .gitignore ├── .hooks ├── description └── no-commit-to-main ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── DESCRIPTION ├── Dockerfile ├── NAMESPACE ├── R ├── cache.R ├── check-ci.R ├── check-covr.R ├── check-default-branch.R ├── check-fns-have-exs.R ├── check-fns-have-return-vals.R ├── check-has-citation.R ├── check-has-codemeta.R ├── check-has-contrib.R ├── check-license.R ├── check-num-imports.R ├── check-obsolete-pkg-deps.R ├── check-on-cran.R ├── check-pkgdown.R ├── check-pkgname-available.R ├── check-renv.R ├── check-scrap.R ├── check-unique-fn-names.R ├── check-urls.R ├── check-uses-roxygen2.R ├── check-vignette.R ├── checks-goodpractice.R ├── checks-left-assign.R ├── checks-srr.R ├── format-checks.R ├── github.R ├── info-call-network.R ├── info-ci.R ├── info-git.R ├── info-pkgdown.R ├── info-pkgstats.R ├── info-renv.R ├── info-srr.R ├── license-list.R ├── path-fns.R ├── pkg-deps.R ├── pkgcheck-bg.R ├── pkgcheck-fn.R ├── pkgcheck-methods.R ├── pkgcheck-package.R ├── read-books.R ├── stats-checks.R ├── summarise-checks.R ├── utils.R └── zzz.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── codemeta.json ├── data-raw ├── insert-yaml-in-use-action.R └── license-list.R ├── inst └── pkgcheck.yaml ├── makefile ├── man ├── checks_to_markdown.Rd ├── get_default_github_branch.Rd ├── get_gh_token.Rd ├── get_latest_commit.Rd ├── list_pkgchecks.Rd ├── logfile_names.Rd ├── pkgcheck-package.Rd ├── pkgcheck.Rd ├── pkgcheck_bg.Rd ├── print.pkgcheck.Rd ├── read_pkg_guide.Rd ├── render_md2html.Rd └── use_github_action_pkgcheck.Rd ├── pkgcheck.Rproj ├── tests ├── testthat.R └── testthat │ ├── _snaps │ ├── extra-checks │ │ ├── checks-extra.html │ │ ├── checks-extra.md │ │ └── checks-print.md │ ├── github.md │ ├── github │ │ ├── pkgcheck.yaml │ │ └── with_inputs.yaml │ └── pkgcheck │ │ ├── checks.html │ │ └── checks.md │ ├── helper-snapshots-clean.R │ ├── test-extra-checks.R │ ├── test-extra-local-check.R │ ├── test-github.R │ ├── test-goodpractice.R │ ├── test-license-list.R │ ├── test-list-checks.R │ ├── test-order-checks.R │ ├── test-path-fns.R │ ├── test-pkgcheck.R │ └── test-pkgcheck_bg.R └── vignettes ├── _output.yml ├── checks.Rds ├── environment.Rmd ├── extending-checks.Rmd └── list-checks.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | .ropensci-staff 2 | ^.*\.Rproj$ 3 | ^CODE_OF_CONDUCT\.md$ 4 | ^CONTRIBUTING.md$ 5 | ^Dockerfile$ 6 | ^README\.Rmd$ 7 | ^\.Rproj\.user$ 8 | ^\.env$ 9 | ^\.github$ 10 | ^\.hooks$ 11 | ^\.pre-commit-config\.yaml$ 12 | ^\.travis\.yml$ 13 | ^_pkgdown\.yml$ 14 | ^aaa.Rmd$ 15 | ^appveyor\.yml$ 16 | ^codemeta\.json$ 17 | ^ctags$ 18 | ^inst/WORDLIST$ 19 | ^inst/precommit$ 20 | ^makefile$ 21 | ^vignettes/makefile$ 22 | data-raw/ 23 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 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: [main, master] 6 | pull_request: 7 | 8 | name: R-CMD-check.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | R-CMD-check: 14 | runs-on: ${{ matrix.config.os }} 15 | 16 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | config: 22 | - {os: macos-latest, r: 'release'} 23 | - {os: windows-latest, r: 'release'} 24 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 25 | - {os: ubuntu-latest, r: 'release'} 26 | # - {os: ubuntu-latest, r: 'oldrel-1'} 27 | 28 | env: 29 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 30 | R_KEEP_PKG_SOURCE: yes 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - uses: r-lib/actions/setup-pandoc@v2 36 | 37 | - uses: r-lib/actions/setup-r@v2 38 | with: 39 | r-version: ${{ matrix.config.r }} 40 | http-user-agent: ${{ matrix.config.http-user-agent }} 41 | use-public-rspm: true 42 | 43 | - name: Extra deps 44 | if: runner.os == 'Linux' 45 | run: | 46 | sudo apt-get install -y autoconf automake git global libgit2-dev libglpk-dev 47 | 48 | - name: ctags latest 49 | if: runner.os == 'Linux' 50 | run: | 51 | git clone https://github.com/universal-ctags/ctags.git 52 | cd ctags 53 | ./autogen.sh 54 | ./configure --prefix=/usr 55 | make 56 | sudo make install 57 | 58 | - uses: r-lib/actions/setup-r-dependencies@v2 59 | with: 60 | extra-packages: any::rcmdcheck 61 | needs: check 62 | 63 | - uses: r-lib/actions/check-r-package@v2 64 | with: 65 | upload-snapshots: true 66 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 67 | -------------------------------------------------------------------------------- /.github/workflows/docker.yaml: -------------------------------------------------------------------------------- 1 | name: docker 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | schedule: 11 | - cron: "0 0 * * 1" 12 | 13 | jobs: 14 | 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | env: 20 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Buildx 27 | uses: docker/setup-buildx-action@v3 28 | 29 | - name: Login 30 | if: ${{ github.event_name != 'pull_request' }} 31 | uses: docker/login-action@v3 32 | with: 33 | username: ${{ secrets.DOCKER_USERNAME }} 34 | password: ${{ secrets.DOCKER_PASSWORD }} 35 | 36 | - name: Login to GitHub Container Registry 37 | if: ${{ github.event_name != 'pull_request' }} 38 | uses: docker/login-action@v3 39 | with: 40 | registry: ghcr.io 41 | username: ${{ github.repository_owner }} 42 | password: ${{ secrets.GITHUB_TOKEN }} 43 | 44 | - name: Build and push 45 | uses: docker/build-push-action@v6 46 | with: 47 | context: . 48 | push: ${{ github.event_name != 'pull_request' }} 49 | secrets: | 50 | "GITHUB_PAT=${{ secrets.GITHUB_TOKEN }}" 51 | tags: | 52 | mpadge/pkgcheck:latest 53 | ghcr.io/ropensci-review-tools/pkgcheck:latest 54 | 55 | - name: Trigger pkgcheck-action build 56 | if: ${{ github.event_name != 'pull_request' }} 57 | uses: actions/github-script@v7 58 | with: 59 | github-token: ${{ secrets.RRT_TOKEN }} 60 | script: | 61 | await github.rest.actions.createWorkflowDispatch({ 62 | owner: 'ropensci-review-tools', 63 | repo: 'pkgcheck-action', 64 | workflow_id: 'publish.yaml', 65 | ref: 'main' 66 | }); 67 | -------------------------------------------------------------------------------- /.github/workflows/monthly.yaml: -------------------------------------------------------------------------------- 1 | name: Renew token issue 2 | on: 3 | schedule: 4 | - cron: 0 0 1 */2 * 5 | 6 | jobs: 7 | 8 | issue_comment: 9 | 10 | name: Renew token issue 11 | 12 | runs-on: ubuntu-latest 13 | permissions: 14 | issues: write 15 | 16 | steps: 17 | 18 | - name: Create Issue Comment 19 | uses: actions/github-script@v7 20 | with: 21 | script: | 22 | 23 | github.rest.issues.createComment({ 24 | owner: "ropensci-review-tools", 25 | repo: "pkgcheck", 26 | issue_number: 123, 27 | body: 28 | `@mpadge It's time to renew your 'RRT_TOKEN' 29 | 30 | - (Re-)Generate **personal** token called 'RRT_TOKEN', with 90 day expiry 31 | - grant both repo and workflow access 32 | - copy token to \'pkgcheck\' and \'pkgcheck-action\' repo secrets` 33 | }) 34 | -------------------------------------------------------------------------------- /.github/workflows/pkgcheck.yaml: -------------------------------------------------------------------------------- 1 | name: pkgcheck 2 | 3 | # This will cancel running jobs once a new run is triggered 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.head_ref }} 6 | cancel-in-progress: true 7 | 8 | on: 9 | # Manually trigger the Action under Actions/pkgcheck 10 | workflow_dispatch: 11 | # Run on every push to main 12 | push: 13 | branches: 14 | - main 15 | 16 | jobs: 17 | pkgcheck: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: ropensci-review-tools/pkgcheck-action@main 21 | -------------------------------------------------------------------------------- /.github/workflows/pr-commands.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/master/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | name: Commands 8 | 9 | jobs: 10 | document: 11 | if: startsWith(github.event.comment.body, '/document') 12 | name: document 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - uses: r-lib/actions/pr-fetch@v2 20 | with: 21 | repo-token: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - uses: r-lib/actions/setup-r@v2 24 | with: 25 | use-public-rspm: true 26 | 27 | - uses: r-lib/actions/setup-r-dependencies@v2 28 | with: 29 | extra-packages: roxygen2 30 | 31 | - name: Document 32 | run: Rscript -e 'roxygen2::roxygenise()' 33 | 34 | - name: commit 35 | run: | 36 | git config --local user.name "$GITHUB_ACTOR" 37 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 38 | git add man/\* NAMESPACE 39 | git commit -m 'Document' 40 | 41 | - uses: r-lib/actions/pr-push@v2 42 | with: 43 | repo-token: ${{ secrets.GITHUB_TOKEN }} 44 | 45 | style: 46 | if: startsWith(github.event.comment.body, '/style') 47 | name: style 48 | runs-on: ubuntu-latest 49 | env: 50 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 51 | steps: 52 | - uses: actions/checkout@v4 53 | 54 | - uses: r-lib/actions/pr-fetch@v2 55 | with: 56 | repo-token: ${{ secrets.GITHUB_TOKEN }} 57 | 58 | - uses: r-lib/actions/setup-r@v2 59 | 60 | - name: Install dependencies 61 | run: Rscript -e 'install.packages("styler");install.packages("remotes"); remotes::install_github("ropensci-review-tools/spaceout")' 62 | 63 | - name: Style 64 | run: Rscript -e 'styler::style_pkg(style = spaceout::spaceout_style)' 65 | 66 | - name: commit 67 | run: | 68 | git config --local user.name "$GITHUB_ACTOR" 69 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 70 | git add \*.R 71 | git commit -m 'Style' 72 | 73 | - uses: r-lib/actions/pr-push@v2 74 | with: 75 | repo-token: ${{ secrets.GITHUB_TOKEN }} 76 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 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: [main, master] 6 | pull_request: 7 | 8 | name: test-coverage.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | test-coverage: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/setup-r@v2 22 | with: 23 | use-public-rspm: true 24 | 25 | - name: Extra deps 26 | if: runner.os == 'Linux' 27 | run: | 28 | sudo apt-get install -y autoconf automake git global libgit2-dev libglpk-dev 29 | 30 | - name: ctags latest 31 | if: runner.os == 'Linux' 32 | run: | 33 | git clone https://github.com/universal-ctags/ctags.git 34 | cd ctags 35 | ./autogen.sh 36 | ./configure --prefix=/usr 37 | make 38 | sudo make install 39 | 40 | - uses: r-lib/actions/setup-r-dependencies@v2 41 | with: 42 | extra-packages: any::covr, any::xml2 43 | needs: coverage 44 | 45 | - name: Test coverage 46 | run: | 47 | cov <- covr::package_coverage( 48 | quiet = FALSE, 49 | clean = FALSE, 50 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 51 | ) 52 | covr::to_cobertura(cov) 53 | shell: Rscript {0} 54 | 55 | - uses: codecov/codecov-action@v4 56 | with: 57 | # Fail if error if not on PR, or if on PR and token is given 58 | fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} 59 | file: ./cobertura.xml 60 | plugin: noop 61 | disable_search: true 62 | token: ${{ secrets.CODECOV_TOKEN }} 63 | 64 | - name: Show testthat output 65 | if: always() 66 | run: | 67 | ## -------------------------------------------------------------------- 68 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 69 | shell: bash 70 | 71 | - name: Upload test results 72 | if: failure() 73 | uses: actions/upload-artifact@v4 74 | with: 75 | name: coverage-test-failures 76 | path: ${{ runner.temp }}/package 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | inst/doc 5 | inst/WORDLIST 6 | aaa\.Rmd* 7 | # History files 8 | .Rhistory 9 | .Rapp.history 10 | # Session Data files 11 | .RData 12 | # Output files from R CMD build 13 | /*.tar.gz 14 | # vim files 15 | .env 16 | .*.un~ 17 | .*.swp 18 | # compiled object files 19 | *.o 20 | *.so 21 | -------------------------------------------------------------------------------- /.hooks/description: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | # only stop on main branch 4 | on_main <- identical (gert::git_branch (), "main") 5 | 6 | s <- gert::git_status() 7 | chk <- ("DESCRIPTION" %in% s$file && 8 | (s$status [s$file == "DESCRIPTION"] == "modified" | 9 | s$status [s$file == "DESCRIPTION"] == "new")) 10 | if (!chk & on_main) 11 | stop ("DESCRIPTION has not been updated") 12 | 13 | f <- file.path (rprojroot::find_root("DESCRIPTION"), "DESCRIPTION") 14 | x <- system2 ("git", args = c ("diff", "--cached", "-U0", f), stdout = TRUE) 15 | if (!any (grepl ("^\\+Version", x)) & on_main) 16 | stop ("Version number in DESCRIPTION has not been incremented") 17 | -------------------------------------------------------------------------------- /.hooks/no-commit-to-main: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | # do not commit to main branch 4 | on_main <- identical (gert::git_branch (), "main") 5 | if (on_main) 6 | stop ("main branch is protected on GitHub; commits must be made via PR from other branch") 7 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # All available hooks: https://pre-commit.com/hooks.html 2 | # R specific hooks: https://github.com/lorenzwalthert/precommit 3 | repos: 4 | - repo: https://github.com/lorenzwalthert/precommit 5 | rev: v0.4.3.9009 6 | hooks: 7 | - id: style-files 8 | args: [--style_pkg=spaceout, --style_fun=spaceout_style] 9 | additional_dependencies: 10 | - ropensci-review-tools/spaceout 11 | # - id: roxygenize 12 | # codemeta must be above use-tidy-description when both are used 13 | # - id: codemeta-description-updated 14 | - id: use-tidy-description 15 | - id: spell-check 16 | exclude: > 17 | (?x)^( 18 | .*\.[rR]| 19 | .*\.feather| 20 | .*\.jpeg| 21 | .*\.pdf| 22 | .*\.png| 23 | .*\.py| 24 | .*\.RData| 25 | .*\.rds| 26 | .*\.Rds| 27 | .*\.Rproj| 28 | .*\.sh| 29 | (.*/|)\.gitignore| 30 | (.*/|)\.gitlab-ci\.yml| 31 | (.*/|)\.lintr| 32 | (.*/|)\.pre-commit-.*| 33 | (.*/|)\.Rbuildignore| 34 | (.*/|)\.Renviron| 35 | (.*/|)\.Rprofile| 36 | (.*/|)\.travis\.yml| 37 | (.*/|)appveyor\.yml| 38 | (.*/|)NAMESPACE| 39 | (.*/|)renv/settings\.dcf| 40 | (.*/|)renv\.lock| 41 | (.*/|)WORDLIST| 42 | \.github/workflows/.*| 43 | data/.*| 44 | )$ 45 | # - id: lintr 46 | - id: readme-rmd-rendered 47 | - id: parsable-R 48 | - id: no-browser-statement 49 | - id: no-print-statement 50 | - id: no-debug-statement 51 | - id: deps-in-desc 52 | # - id: pkgdown 53 | - repo: https://github.com/pre-commit/pre-commit-hooks 54 | rev: v5.0.0 55 | hooks: 56 | - id: check-added-large-files 57 | args: ['--maxkb=200'] 58 | - id: file-contents-sorter 59 | files: '^\.Rbuildignore$' 60 | - id: end-of-file-fixer 61 | exclude: '\.Rd' 62 | - repo: https://github.com/pre-commit-ci/pre-commit-ci-config 63 | rev: v1.6.1 64 | hooks: 65 | # Only required when https://pre-commit.ci is used for config validation 66 | - id: check-pre-commit-ci-config 67 | - repo: local 68 | hooks: 69 | - id: forbid-to-commit 70 | name: Don't commit common R artifacts 71 | entry: Cannot commit .Rhistory, .RData, .Rds or .rds. 72 | language: fail 73 | files: '\.(Rhistory|RData|Rds|rds)$' 74 | # `exclude: ` to allow committing specific files 75 | - id: description version 76 | name: Version has been incremeneted in DESCRIPTION 77 | entry: .hooks/description 78 | language: script 79 | - id: Commit via PR only 80 | name: Commit made to non-main branch 81 | entry: .hooks/no-commit-to-main 82 | language: script 83 | 84 | ci: 85 | autoupdate_schedule: monthly 86 | # skip: [pkgdown] 87 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to pkgcheck 2 | 3 | ## Opening issues 4 | 5 | The easiest way to note any behavioural curiosities or to request any new 6 | features is by opening a [github 7 | issue](https://github.com/ropensci-review-tools/pkgcheck/issues). 8 | 9 | 10 | ## Development guidelines 11 | 12 | If you'd like to contribute changes to `pkgcheck`, we use [the GitHub 13 | flow](https://docs.github.com/en/get-started/quickstart/github-flow) for proposing, 14 | submitting, reviewing, and accepting changes. If you haven't done this before, 15 | there's a nice [overview of git](https://r-pkgs.org/git.html), as well 16 | as [best practices for submitting pull requests](http://r-pkgs.org/git.html#pr-make) 17 | in the R packages book by Hadley Wickham and Jenny Bryan. 18 | 19 | The `pkgcheck` coding style diverges somewhat from [the commonly used tidyverse style 20 | guide](https://style.tidyverse.org/syntax.html#spacing), primarily through judicious use of 21 | whitespace, which aims to improve code readability. Code references in 22 | `pkgcheck` are separated by whitespace, just like words of text. Just like it 23 | is easier to understand "these three words" than "thesethreewords", code is not 24 | formatted like this: 25 | 26 | ``` r 27 | these <- three(words(x)) 28 | ``` 29 | rather like this: 30 | 31 | ``` r 32 | these <- three (words (x)) 33 | ``` 34 | 35 | The position of brackets is then arbitrary, and we could also write 36 | 37 | ``` r 38 | these <- three( words (x)) 39 | ``` 40 | 41 | `pkgcheck` code opts for the former style, with the natural result that one 42 | ends up writing 43 | 44 | ```r 45 | this <- function () 46 | ``` 47 | 48 | with a space between `function` and `()`. That's it. 49 | 50 | ## Adding new checks 51 | 52 | New checks are a welcome contribution to `pkgcheck`, for which there is a 53 | [dedicated 54 | vignette](https://docs.ropensci.org/pkgcheck/articles/extending-checks.html). 55 | Please discuss any proposed new checks by opening an issue on the GitHub 56 | repository. 57 | 58 | 59 | ## Code of Conduct 60 | 61 | We want to encourage a warm, welcoming, and safe environment for contributing to 62 | this project. See the [code of 63 | conduct](https://ropensci.org/code-of-conduct/) for 64 | more information. 65 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: pkgcheck 2 | Title: rOpenSci Package Checks 3 | Version: 0.1.2.133 4 | Authors@R: c( 5 | person("Mark", "Padgham", , "mark.padgham@email.com", role = c("aut", "cre"), 6 | comment = c(ORCID = "0000-0003-2172-5265")), 7 | person("Maëlle", "Salmon", role = "aut"), 8 | person("Jacob", "Wujciak-Jens", , "jacob@wujciak.de", role = "aut", 9 | comment = c(ORCID = "0000-0002-7281-3989")) 10 | ) 11 | Description: Check whether a package is ready for submission to rOpenSci's 12 | peer review system. 13 | License: GPL-3 14 | URL: https://docs.ropensci.org/pkgcheck/, 15 | https://github.com/ropensci-review-tools/pkgcheck 16 | BugReports: https://github.com/ropensci-review-tools/pkgcheck/issues 17 | Depends: 18 | R (>= 3.5.0) 19 | Imports: 20 | cli, 21 | covr, 22 | curl, 23 | fs, 24 | gert, 25 | ghql, 26 | glue, 27 | goodpractice, 28 | httr, 29 | httr2, 30 | jsonlite, 31 | magrittr, 32 | methods, 33 | pkgstats, 34 | rappdirs, 35 | rmarkdown, 36 | rprojroot, 37 | rvest, 38 | srr, 39 | withr 40 | Suggests: 41 | callr, 42 | knitr, 43 | pkgbuild, 44 | roxygen2, 45 | testthat (>= 3.0.0), 46 | visNetwork, 47 | VignetteBuilder: 48 | knitr 49 | Remotes: 50 | ropensci-review-tools/pkgstats, 51 | ropensci-review-tools/srr 52 | Config/testthat/edition: 3 53 | Encoding: UTF-8 54 | Language: en-GB 55 | LazyData: true 56 | NeedsCompilation: yes 57 | Roxygen: list(markdown = TRUE) 58 | RoxygenNote: 7.3.2 59 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This is built on top of a bspm image, generated with the first RUN comand. 2 | # The second command then installs most but not all of the libraries used to 3 | # build GitHub's Ubuntu-24.04 runner. 4 | # 5 | # After that are manual installs of ctags & the GitHub cli (`gh`). 6 | # Finally, a standard setup for RCMD check, plus a few additional system 7 | # libraries. 8 | 9 | FROM eddelbuettel/r2u:24.04 10 | LABEL org.opencontainers.image.authors="mark.padgham@email.com" 11 | 12 | RUN apt-get update && apt-get install -y --no-install-recommends \ 13 | sudo \ 14 | r-cran-bspm \ 15 | && echo "bspm::enable()" >> /etc/R/Rprofile.site \ 16 | && echo "options(bspm.sudo=TRUE)" >> /etc/R/Rprofile.site \ 17 | && echo 'APT::Install-Recommends "false";' > /etc/apt/apt.conf.d/90local-no-recommends \ 18 | && echo "docker ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/local-docker-user \ 19 | && chmod 0440 /etc/sudoers.d/local-docker-user \ 20 | && chgrp 1000 /usr/local/lib/R/site-library \ 21 | && install.r remotes 22 | 23 | # still need ubuntugis for gdal 24 | RUN apt-get update -qq \ 25 | && apt-get install -y software-properties-common gpg-agent \ 26 | && apt-get update 27 | RUN add-apt-repository -y ppa:ubuntugis/ubuntugis-unstable \ 28 | && apt update \ 29 | && apt -y upgrade 30 | 31 | # GitHub Ubuntu-24.04 runner, but not imagemagick because v7 needs to be 32 | # compiled with librsvg2, rather than binary-installed 33 | # https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2004-Readme.md 34 | # netbase: https://github.com/tensorflow/haskell/issues/182 35 | RUN apt-get update -qq && apt-get install -y \ 36 | acl \ 37 | aria2 \ 38 | autoconf \ 39 | automake \ 40 | binutils \ 41 | bison \ 42 | brotli \ 43 | bzip2 \ 44 | coreutils \ 45 | curl \ 46 | dbus \ 47 | dnsutils \ 48 | dpkg \ 49 | dpkg-dev \ 50 | fakeroot \ 51 | file \ 52 | findutils \ 53 | flex \ 54 | fonts-noto-color-emoji \ 55 | ftp \ 56 | g++ \ 57 | gcc \ 58 | gnupg2 \ 59 | haveged \ 60 | iproute2 \ 61 | iputils-ping \ 62 | jq \ 63 | lib32z1 \ 64 | libc++-dev \ 65 | libc++abi-dev \ 66 | libc6-dev \ 67 | libcurl4 \ 68 | libgbm-dev \ 69 | libgsl-dev \ 70 | libgtk-3-0 \ 71 | libmagic-dev \ 72 | libmagickcore-dev \ 73 | libmagickwand-dev \ 74 | libsecret-1-dev \ 75 | libsqlite3-dev \ 76 | libtool \ 77 | libunwind8 \ 78 | libxkbfile-dev \ 79 | libxss1 \ 80 | libyaml-dev \ 81 | locales \ 82 | m4 \ 83 | make \ 84 | mediainfo \ 85 | mercurial \ 86 | net-tools \ 87 | netcat-openbsd \ 88 | openssh-client \ 89 | p7zip-full \ 90 | p7zip-rar \ 91 | parallel \ 92 | pass \ 93 | patchelf \ 94 | pigz \ 95 | pkg-config \ 96 | pollinate \ 97 | python-is-python3 \ 98 | rpm \ 99 | rsync \ 100 | shellcheck \ 101 | sphinxsearch \ 102 | sqlite3 \ 103 | ssh \ 104 | sshpass \ 105 | swig \ 106 | tar \ 107 | telnet \ 108 | texinfo \ 109 | time \ 110 | tk \ 111 | tzdata \ 112 | unzip \ 113 | upx \ 114 | wget \ 115 | xorriso \ 116 | xvfb \ 117 | xz-utils \ 118 | zip \ 119 | zsync && \ 120 | apt-get clean 121 | 122 | RUN apt-get update -qq && apt-get install -y \ 123 | apt-utils \ 124 | build-essential \ 125 | cargo \ 126 | cmake \ 127 | coinor-libcbc-dev \ 128 | coinor-libsymphony-dev \ 129 | dos2unix \ 130 | flac \ 131 | fonts-emojione \ 132 | git \ 133 | global \ 134 | jags \ 135 | language-pack-en-base \ 136 | libapparmor-dev \ 137 | libarchive-dev \ 138 | libavfilter-dev \ 139 | libbam-dev \ 140 | libboost-filesystem-dev \ 141 | libboost-program-options-dev \ 142 | libcairo2-dev \ 143 | libcurl4-openssl-dev \ 144 | libdb-dev \ 145 | libeigen3-dev \ 146 | libelf-dev \ 147 | libfftw3-dev \ 148 | libfreetype6-dev \ 149 | libfribidi-dev \ 150 | libgdal-dev \ 151 | libgeos-dev \ 152 | libghc-citeproc-dev \ 153 | libgit2-dev \ 154 | libglpk-dev \ 155 | libglu1-mesa-dev \ 156 | libgpgme-dev \ 157 | libharfbuzz-dev \ 158 | libhdf5-dev \ 159 | libhiredis-dev \ 160 | libicu-dev \ 161 | libjansson-dev \ 162 | libjpeg-dev \ 163 | libjq-dev \ 164 | libmagick++-dev \ 165 | libmpfr-dev \ 166 | libmysqlclient-dev \ 167 | libnetcdf-dev \ 168 | libnng-dev \ 169 | libopenbabel-dev \ 170 | libopenblas0 \ 171 | libopencv-dev \ 172 | libpng-dev \ 173 | libpoppler-cpp-dev \ 174 | libpq-dev \ 175 | libproj-dev \ 176 | libprotobuf-dev \ 177 | libprotoc-dev \ 178 | librabbitmq-dev \ 179 | librdf0 \ 180 | librrd-dev \ 181 | librsvg2-dev \ 182 | libsasl2-dev \ 183 | libseccomp-dev \ 184 | libsodium-dev \ 185 | libssh-dev \ 186 | libssh2-1-dev \ 187 | libssl-dev \ 188 | libtesseract-dev \ 189 | libtiff-dev \ 190 | libudunits2-dev \ 191 | libv8-dev \ 192 | libwebp-dev \ 193 | libxml2-dev \ 194 | libxslt-dev \ 195 | libxslt1-dev \ 196 | libzmq3-dev \ 197 | netbase \ 198 | pandoc \ 199 | protobuf-compiler \ 200 | python3-dev \ 201 | python3-full \ 202 | python3-docutils \ 203 | python3-numpy \ 204 | python3-pandas \ 205 | python3-pip \ 206 | python3-venv \ 207 | r-base-dev \ 208 | r-cran-rjava \ 209 | tesseract-ocr-eng \ 210 | texlive-fonts-extra \ 211 | texlive-fonts-recommended \ 212 | texlive-latex-base \ 213 | texlive-latex-extra \ 214 | ttf-mscorefonts-installer \ 215 | unixodbc-dev \ 216 | zlib1g-dev \ 217 | zstd && \ 218 | apt-get clean 219 | 220 | # For some reason, librdf0-dev doesn't install in the list above: 221 | # (See also pkgcheck-action#48) 222 | RUN apt-get install -y librdf0-dev 223 | 224 | # ctags install 225 | RUN git clone https://github.com/universal-ctags/ctags.git \ 226 | && cd ctags \ 227 | && ./autogen.sh \ 228 | && ./configure --prefix=/usr \ 229 | && make \ 230 | && make install 231 | 232 | # gh cli: https://github.com/cli/cli/blob/trunk/docs/install_linux.md 233 | RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ 234 | && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ 235 | && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ 236 | && apt update \ 237 | && apt install gh -y 238 | 239 | # nix: 240 | RUN curl --proto '=https' --tlsv1.2 -sSf \ 241 | -L https://install.determinate.systems/nix | \ 242 | sh -s -- install linux --no-confirm --init none 243 | 244 | # Julia: 245 | # https://github.com/ropensci-review-tools/roreviewapi/issues/28 246 | RUN wget https://raw.githubusercontent.com/abelsiqueira/jill/main/jill.sh \ 247 | && bash jill.sh -y \ 248 | && rm jill.sh 249 | 250 | 251 | # https://arrow.apache.org/docs/r/articles/install.html#s3-support 252 | ENV ARROW_S3="ON" 253 | 254 | # ropensci-review-tools/pkgcheck/issues/134: 255 | #ENV R_REMOTES_UPGRADE="always" 256 | ENV NOT_CRAN="true" 257 | ENV CI="true" 258 | ENV ROPENSCI="true" 259 | 260 | # cmdstan path 261 | ENV CMDSTAN_PATH="/root/.cmdstan" 262 | 263 | # A selection of R packages, including extra stats packages 264 | RUN install2.r \ 265 | arrow \ 266 | decor \ 267 | devtools \ 268 | distill \ 269 | duckdb \ 270 | foreign \ 271 | glmnet \ 272 | goodpractice \ 273 | lme4 \ 274 | mgcv \ 275 | rstan \ 276 | Rcpp \ 277 | RcppArmadillo \ 278 | RcppEigen \ 279 | RcppParallel \ 280 | randomForest \ 281 | rdflib \ 282 | reticulate \ 283 | rmarkdown \ 284 | seasonal \ 285 | sf \ 286 | survival \ 287 | terra \ 288 | tidymodels \ 289 | tidyverse \ 290 | xts \ 291 | zoo 292 | 293 | # This is the format needed to install from GitHub: 294 | # RUN --mount=type=secret,id=GITHUB_PAT,env=GITHUB_PAT installGithub.r \ 295 | # ropensci-review-tools/goodpractice 296 | 297 | # Install cmdstanr from GitHub 298 | RUN --mount=type=secret,id=GITHUB_PAT,env=GITHUB_PAT installGithub.r \ 299 | stan-dev/cmdstanr 300 | 301 | # Install cmdstan 302 | RUN Rscript -e 'cmdstanr::check_cmdstan_toolchain(fix = TRUE); cmdstanr::install_cmdstan()' 303 | 304 | # Created in /root/.virtualenvs/r-reticulate: 305 | RUN Rscript -e 'reticulate::virtualenv_create()' 306 | 307 | # Extra python packages: 308 | # ---- Authors: Please submit PRs which insert extra python requirements here, 309 | # ---- followed by package name and "#": 310 | RUN /root/.virtualenvs/r-reticulate/bin/pip install earthengine-api # rgeeExtra #608 311 | 312 | # arrow docs suggest this shouldn't be needed, but s3 313 | # support doesn't work without re-install/compile: 314 | RUN Rscript -e 'arrow::install_arrow()' 315 | 316 | # Plus current ubuntu-unstable versions cause failed linkage of sf to GEOS, so 317 | # need to reinstall both 'sf' and 'terra' without bspm: 318 | RUN Rscript -e 'bspm::disable();install.packages(c("sf","terra"));bspm::enable()' 319 | 320 | # Quarto binary: 321 | RUN wget https://github.com/quarto-dev/quarto-cli/releases/download/v1.6.40/quarto-1.6.40-linux-amd64.tar.gz \ 322 | && mkdir ~/opt \ 323 | && tar -C ~/opt -xvzf quarto-1.6.40-linux-amd64.tar.gz \ 324 | && ln -s ~/opt/quarto-1.6.40/bin/quarto /usr/local/bin/quarto \ 325 | && rm quarto-1.6.40-linux-amd64.tar.gz 326 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(print,pkgcheck) 4 | S3method(summary,pkgcheck) 5 | export(checks_to_markdown) 6 | export(get_default_github_branch) 7 | export(get_gh_token) 8 | export(get_latest_commit) 9 | export(list_pkgchecks) 10 | export(logfile_names) 11 | export(pkgcheck) 12 | export(pkgcheck_bg) 13 | export(read_pkg_guide) 14 | export(render_md2html) 15 | export(use_github_action_pkgcheck) 16 | importFrom(magrittr,"%>%") 17 | -------------------------------------------------------------------------------- /R/cache.R: -------------------------------------------------------------------------------- 1 | #' Set up stdout & stderr cache files for `r_bg` process 2 | #' 3 | #' @param path Path to local repository 4 | #' @return Vector of two strings holding respective local paths to `stdout` and 5 | #' `stderr` files for `r_bg` process controlling the main \link{pkgcheck} 6 | #' function when executed in background mode. 7 | #' 8 | #' @note These files are needed for the \pkg{callr} `r_bg` process which 9 | #' controls the main \link{pkgcheck}. The `stdout` and `stderr` pipes from the 10 | #' process are stored in the cache directory so they can be inspected via their 11 | #' own distinct endpoint calls. 12 | #' @family extra 13 | #' @export 14 | #' @examples 15 | #' \dontrun{ 16 | #' logfiles <- logfiles_namnes ("/path/to/my/package") 17 | #' print (logfiles) 18 | #' } 19 | logfile_names <- function (path) { 20 | 21 | temp_dir <- fs::path (Sys.getenv ("PKGCHECK_CACHE_DIR"), "templogs") 22 | if (!dir.exists (temp_dir)) { 23 | dir.create (temp_dir, recursive = TRUE) 24 | } 25 | 26 | pkg_hash <- current_hash (path) 27 | pkg_hash_fmt <- paste0 (pkg_hash, collapse = "_") 28 | 29 | sout <- fs::path (temp_dir, paste0 (pkg_hash_fmt, "_stdout")) 30 | serr <- fs::path (temp_dir, paste0 (pkg_hash_fmt, "_stderr")) 31 | 32 | otherlogs <- list.files ( 33 | temp_dir, 34 | pattern = pkg_hash [1], 35 | full.names = TRUE 36 | ) 37 | otherlogs <- otherlogs [which (!grepl (pkg_hash [2], otherlogs))] 38 | if (length (otherlogs) > 0) { 39 | file.remove (otherlogs) 40 | } 41 | 42 | return (list (stdout = sout, stderr = serr)) 43 | } 44 | 45 | #' Get hash of last git commit 46 | #' 47 | #' @param path Path to local source directory 48 | #' @return Vector of 2 values: (package name, hash) 49 | #' @noRd 50 | current_hash <- function (path) { 51 | 52 | if (!fs::file_exists (fs::path (path, "DESCRIPTION"))) { 53 | stop ("path [", path, "] does not appear to be an R package") 54 | } 55 | 56 | desc <- data.frame (read.dcf (file.path (path, "DESCRIPTION")), 57 | stringsAsFactors = FALSE 58 | ) 59 | pkg <- desc$Package 60 | 61 | if (repo_is_git (path)) { 62 | g <- gert::git_info (path) 63 | hash <- substring (g$commit, 1, 8) 64 | } else { # not a git repo, so use mtime as hash 65 | 66 | flist <- list.files (path, recursive = TRUE, full.names = TRUE) 67 | mt <- file.info (flist)$mtime 68 | if (any (!is.na (mt))) { 69 | mt <- max (mt, na.rm = TRUE) 70 | } else { 71 | mt <- "" 72 | } 73 | hash <- gsub ("\\s+", "-", paste0 (mt)) 74 | } 75 | 76 | c (pkg, hash) 77 | } 78 | 79 | cache_pkgcheck_component <- function (path, 80 | use_cache, 81 | renv_activated, 82 | what = "goodpractice") { 83 | 84 | what <- match.arg (what, c ("goodpractice", "pkgstats")) 85 | 86 | dir_name <- ifelse ( 87 | what == "goodpractice", 88 | "gp_reports", 89 | "pkgstats_results" 90 | ) 91 | this_fn <- ifelse ( 92 | what == "goodpractice", 93 | goodpractice::goodpractice, 94 | pkgstats::pkgstats 95 | ) 96 | 97 | 98 | pkg_hash <- current_hash (path) 99 | fname <- paste0 (pkg_hash [1], "_", pkg_hash [2]) 100 | cache_dir <- file.path ( 101 | Sys.getenv ("PKGCHECK_CACHE_DIR"), 102 | dir_name 103 | ) 104 | if (!dir.exists (cache_dir)) { 105 | dir.create (cache_dir, recursive = TRUE) 106 | } 107 | 108 | cache_file <- fs::path (cache_dir, fname) 109 | 110 | # rm old components: 111 | flist <- fs::dir_ls (cache_dir, regexp = paste0 (pkg_hash [1], "\\_")) 112 | flist <- flist [which (!basename (flist) == fname)] 113 | if (length (flist) > 0) { 114 | chk <- fs::file_delete (flist) 115 | } 116 | 117 | if (fs::file_exists (cache_file) & use_cache) { 118 | 119 | out <- readRDS (cache_file) 120 | 121 | } else { 122 | 123 | # this envvar is for goodpractice, but no harm setting for other 124 | # components too 125 | Sys.setenv ("_R_CHECK_FORCE_SUGGESTS_" = FALSE) 126 | if (renv_activated) { 127 | renv_deactivate (path) # in R/info-renv.R 128 | message ( 129 | "'renv' has been de-activated; to reactivate, run ", 130 | "`renv::activate()` in your project directory after ", 131 | "`pkgcheck` has finished" 132 | ) 133 | } 134 | 135 | out <- suppressWarnings (do.call (this_fn, list (path))) 136 | 137 | Sys.unsetenv ("_R_CHECK_FORCE_SUGGESTS_") 138 | 139 | # writing to cache_dir fails on some GHA windows machines. 140 | if (fs::dir_exists (cache_dir)) { 141 | chk <- tryCatch ( 142 | saveRDS (out, cache_file), 143 | error = function (e) NULL 144 | ) 145 | } 146 | } 147 | 148 | return (out) 149 | } 150 | -------------------------------------------------------------------------------- /R/check-ci.R: -------------------------------------------------------------------------------- 1 | output_pkgchk_ci <- function (checks) { 2 | 3 | check_pass <- has_badges <- length (checks$info$badges) > 0L 4 | # There should really be badges, but if not, accept passing workflow results 5 | # regardless (see #87): 6 | if (!check_pass & length (checks$info$github_workflows) > 0L) { 7 | wf <- checks$info$github_workflows 8 | i <- grep ("check|cmd", wf$name, ignore.case = TRUE) 9 | check_pass <- any (wf$conclusion [i] == "success") 10 | check_pass <- ifelse (is.na (check_pass), FALSE, check_pass) 11 | } 12 | 13 | out <- list ( 14 | check_pass = check_pass, 15 | summary = "", 16 | print = "" 17 | ) 18 | 19 | if (!out$check_pass) { 20 | 21 | if (!checks$checks$has_url) { 22 | out$summary <- paste0 ( 23 | "Continuous integration checks ", 24 | "unavailable (no URL in 'DESCRIPTION')." 25 | ) 26 | } else { 27 | out$summary <- " Package has no continuous integration checks." 28 | } 29 | 30 | } else { 31 | 32 | out$summary <- "Package has continuous integration checks." 33 | if (has_badges) { 34 | has_badges <- !is.na (checks$info$badges [1]) 35 | } 36 | if (!has_badges) { 37 | checks$info$badges <- "(There do not appear to be any)" 38 | } 39 | 40 | out$print <- c ( 41 | "#### 3a. Continuous Integration Badges", 42 | "", 43 | unlist (checks$info$badges), 44 | "" 45 | ) 46 | 47 | if (!is.null (checks$info$github_workflows)) { 48 | out$print <- c ( 49 | out$print, 50 | "**GitHub Workflow Results**", 51 | "", 52 | knitr::kable (checks$info$github_workflows) 53 | ) 54 | } 55 | } 56 | 57 | 58 | return (out) 59 | } 60 | -------------------------------------------------------------------------------- /R/check-covr.R: -------------------------------------------------------------------------------- 1 | 2 | output_pkgchk_covr <- function (checks) { 3 | 4 | out <- list ( 5 | check_pass = TRUE, 6 | summary = "", 7 | print = "" 8 | ) 9 | 10 | if (methods::is (checks$goodpractice$covr, "try-error")) { 11 | 12 | out$check_pass <- FALSE 13 | out$summary <- "Package coverage failed" 14 | 15 | } else { 16 | 17 | coverage <- round (checks$goodpractice$covr$pct_by_line, digits = 1) 18 | 19 | if (coverage < 75) { 20 | out$check_pass <- FALSE 21 | out$summary <- paste0 ( 22 | "Package coverage is ", 23 | coverage, 24 | "% (should be at least 75%)." 25 | ) 26 | } else { 27 | out$summary <- paste0 ("Package coverage is ", coverage, "%.") 28 | } 29 | } 30 | 31 | return (out) 32 | } 33 | -------------------------------------------------------------------------------- /R/check-default-branch.R: -------------------------------------------------------------------------------- 1 | #' Ensure that default GitHub branch is not "master" 2 | #' 3 | #' The `$info$git$branch` value is taken by default from GitHub as long as 4 | #' "DESCRIPTION" has a remote URL. It is only taken from local git if not 5 | #' remote GitHub URL can be identified.` 6 | #' 7 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 8 | #' \pkg{goodpractice} results. 9 | #' @return TRUE if default GitHub branch is "master"; otherwise FALSE 10 | #' @noRd 11 | pkgchk_branch_is_master <- function (checks) { 12 | 13 | 14 | ret <- FALSE 15 | if (length (checks$info$git) > 0L) { 16 | ret <- checks$info$git$branch == "master" 17 | } 18 | 19 | # Return true in test environments; see #215 20 | ret <- ret || is_test_env () # In R/utils.R 21 | 22 | return (ret) 23 | } 24 | 25 | output_pkgchk_branch_is_master <- function (checks) { 26 | 27 | out <- list ( 28 | check_pass = !checks$checks$branch_is_master, 29 | summary = "", 30 | print = "" 31 | ) 32 | 33 | if (!out$check_pass) { 34 | out$summary <- "Default GitHub branch of 'master' is not acceptable." 35 | } 36 | 37 | return (out) 38 | } 39 | -------------------------------------------------------------------------------- /R/check-fns-have-exs.R: -------------------------------------------------------------------------------- 1 | #' Check whether all functions have examples. 2 | #' 3 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 4 | #' \pkg{goodpractice} results. 5 | #' @return Vector of named logical values, one for each '.Rd' file indicating 6 | #' whether or not it has example lines. 7 | #' @noRd 8 | pkgchk_fns_have_exs <- function (checks) { 9 | 10 | rd <- list.files ( 11 | fs::path ( 12 | checks$pkg$path, "man" 13 | ), 14 | pattern = "\\.Rd$", 15 | full.names = TRUE 16 | ) 17 | 18 | # don't check for examples in datasets (#103), identified by keyword 19 | what <- c ("name", "docType", "keyword", "examples") 20 | rd_dat <- vapply (rd, function (i) { 21 | rd_i <- tools::parse_Rd (i, permissive = TRUE) 22 | dat <- lapply (what, function (j) { 23 | get_Rd_meta (rd_i, j) 24 | }) 25 | if (length (dat [[2]]) == 0L) { 26 | dat [[2]] <- "" 27 | } 28 | if (length (dat [[3]]) == 0L) { 29 | dat [[3]] <- "" 30 | } 31 | dat [[4]] <- length (dat [[4]]) 32 | unlist (dat) [1:4] 33 | }, 34 | character (4), 35 | USE.NAMES = TRUE 36 | ) 37 | rd_dat <- data.frame ( 38 | t (rd_dat), 39 | row.names = NULL, 40 | stringsAsFactors = FALSE 41 | ) 42 | names (rd_dat) <- what 43 | 44 | # rm internal and datasets, where all re-exported fns should be internal. 45 | rd_dat <- rd_dat [which (!rd_dat$keyword %in% c ("internal", "datasets")), ] 46 | rd_dat <- rd_dat [which (!rd_dat$docType %in% c ("package", "data")), ] 47 | 48 | has_ex <- rd_dat$examples > 0L 49 | names (has_ex) <- rd_dat$name 50 | 51 | return (has_ex) 52 | } 53 | 54 | output_pkgchk_fns_have_exs <- function (checks) { 55 | 56 | no_ex <- which (!checks$checks$fns_have_exs) 57 | no_ex_fns <- names (checks$checks$fns_have_exs) [no_ex] 58 | 59 | out <- list ( 60 | check_pass = length (no_ex) == 0L, 61 | summary = "", 62 | print = "" 63 | ) # no print method 64 | 65 | out$summary <- ifelse (out$check_pass, 66 | "All functions have examples.", 67 | paste0 ( 68 | "These functions do not have ", 69 | "examples: [", 70 | paste0 (no_ex_fns, collapse = ", "), 71 | "]." 72 | ) 73 | ) 74 | 75 | return (out) 76 | } 77 | -------------------------------------------------------------------------------- /R/check-fns-have-return-vals.R: -------------------------------------------------------------------------------- 1 | #' Check that all functions document their return values. 2 | #' 3 | #' The reflects a CRAN checks for all new submissions, to ensure that return 4 | #' values are documented for all functions. This check applies even to functions 5 | #' which are called for their side effects and return `NULL`. 6 | #' 7 | #' @note This is currently the only function which uses 8 | #' `roxygen2::parse_file()`. If any further functions use this, it might be 9 | #' better to move this to an "info" item and out of this specific "check" item. 10 | #' 11 | #' @noRd 12 | pkgchk_fns_have_return_vals <- function (checks) { 13 | 14 | flist <- list.files ( 15 | file.path (checks$pkg$path, "man"), 16 | full.names = TRUE, 17 | recursive = FALSE, 18 | pattern = "\\.Rd$" 19 | ) 20 | 21 | get1tag <- function (tags, rd, what = "docType") { 22 | index <- grep (paste0 (what, "$"), tags) 23 | ret <- "" 24 | if (length (index) > 0) { 25 | ret <- lapply (rd [index], function (j) { 26 | paste0 (unlist (j), collapse = "") 27 | }) 28 | ret <- unlist (ret) 29 | } 30 | return (ret) 31 | } 32 | 33 | tag_data <- lapply (flist, function (f) { 34 | x <- tools::parse_Rd (f, permissive = TRUE) 35 | tags <- unlist (lapply (x, function (i) attr (i, "Rd_tag"))) 36 | list ( 37 | docType = get1tag (tags, x, "docType"), 38 | value = get1tag (tags, x, "value"), 39 | alias = get1tag (tags, x, "alias"), 40 | name = get1tag (tags, x, "name"), 41 | keyword = get1tag (tags, x, "keyword") 42 | ) 43 | }) 44 | 45 | # All of these should have only one value except 'keywords': 46 | doc_types <- vapply (tag_data, function (i) i$docType, character (1L)) 47 | keywords <- lapply (tag_data, function (i) i$keyword) 48 | value <- vapply (tag_data, function (i) i$value, character (1L)) 49 | rd_names <- vapply (tag_data, function (i) i$name, character (1L)) 50 | 51 | kw_in_data_or_internal <- vapply (keywords, function (i) { 52 | any (i %in% c ("datasets", "internal")) 53 | }, logical (1L)) 54 | 55 | index <- which ( 56 | !nzchar (doc_types) & !kw_in_data_or_internal & !nzchar (value) 57 | ) 58 | 59 | return (rd_names [index]) 60 | } 61 | 62 | output_pkgchk_fns_have_return_vals <- function (checks) { # nolint 63 | 64 | out <- list ( 65 | check_pass = length (checks$checks$fns_have_return_vals) == 0L, 66 | summary = "", 67 | print = "" 68 | ) 69 | 70 | if (!out$check_pass) { 71 | 72 | fns <- checks$checks$fns_have_return_vals 73 | 74 | nfns <- length (fns) 75 | 76 | txt <- ifelse (nfns == 1L, "function has", "functions have") 77 | 78 | out$summary <- paste0 ( 79 | "The following ", 80 | txt, 81 | " no documented return value", 82 | ifelse (nfns == 1L, "", "s"), 83 | ": [", 84 | paste0 (fns, collapse = ", "), 85 | "]" 86 | ) 87 | } 88 | 89 | return (out) 90 | } 91 | -------------------------------------------------------------------------------- /R/check-has-citation.R: -------------------------------------------------------------------------------- 1 | # https://github.com/yihui/knitr-examples/blob/master/113-externalization.Rmd 2 | 3 | # ---- pkgchk-citation ---- 4 | #' Check whether a package has a `inst/CITATION` file. 5 | #' 6 | #' "CITATION" files are required for all rOpenSci packages, as documented [in 7 | #' our "*Packaging 8 | #' Guide*](https://devguide.ropensci.org/pkg_building.html#citation-file). This 9 | #' does not check the contents of that file in any way. 10 | #' 11 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 12 | #' \pkg{goodpractice} results. 13 | #' @noRd 14 | pkgchk_has_citation <- function (checks) { 15 | 16 | "CITATION" %in% list.files (fs::path (checks$pkg$path, "inst")) 17 | } 18 | 19 | # ---- output-pkgchk-citation ---- 20 | output_pkgchk_has_citation <- function (checks) { 21 | 22 | out <- list ( 23 | check_pass = checks$checks$has_citation, 24 | summary = "", 25 | print = "" 26 | ) 27 | 28 | # disabled: 29 | # https://github.com/ropensci-review-tools/pkgcheck/issues/115 30 | # out$summary <- paste0 ( 31 | # ifelse (out$check_pass, "has", "does not have"), 32 | # " a 'CITATION' file." 33 | # ) 34 | 35 | return (out) 36 | } 37 | -------------------------------------------------------------------------------- /R/check-has-codemeta.R: -------------------------------------------------------------------------------- 1 | #' Check whether a package has a `codemeta.json` file. 2 | #' 3 | #' "codemeta.json" files are recommended for all rOpenSci packages, as 4 | #' documented [in our "*Packaging 5 | #' Guide*](https://devguide.ropensci.org/pkg_building.html#creating-metadata-for-your-package). 6 | #' 7 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 8 | #' \pkg{goodpractice} results. 9 | #' @noRd 10 | pkgchk_has_codemeta <- function (checks) { 11 | 12 | "codemeta.json" %in% list.files (checks$pkg$path, recursive = FALSE) 13 | } 14 | 15 | output_pkgchk_has_codemeta <- function (checks) { 16 | 17 | out <- list ( 18 | check_pass = checks$checks$has_codemeta, 19 | summary = "", 20 | print = "" 21 | ) # no print method 22 | 23 | out$summary <- paste0 ( 24 | ifelse (out$check_pass, "has", "does not have"), 25 | " a 'codemeta.json' file." 26 | ) 27 | 28 | return (out) 29 | } 30 | -------------------------------------------------------------------------------- /R/check-has-contrib.R: -------------------------------------------------------------------------------- 1 | #' Check whether package has contributor guidelines in a 'contributing' file. 2 | #' 3 | #' A "contributing" file is required for all rOpenSci packages, as documented 4 | #' [in our "*Contributing 5 | #' Guide*](https://devguide.ropensci.org/maintenance_collaboration.html#contributing-guide). 6 | #' 7 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 8 | #' \pkg{goodpractice} results. 9 | #' @return Logical flag 10 | #' @noRd 11 | pkgchk_has_contrib_md <- function (checks) { 12 | 13 | flist <- list.files ( 14 | checks$pkg$path, 15 | all.files = TRUE, 16 | recursive = TRUE, 17 | full.names = FALSE 18 | ) 19 | 20 | # remove any renv files (#141). The 'pattern' param of 21 | # flist only works as positive filter. 22 | ptn <- paste0 (.Platform$file.sep, "renv", .Platform$file.sep) 23 | flist <- flist [which (!grepl (ptn, flist))] 24 | 25 | flist <- vapply (flist, function (i) { 26 | utils::tail (decompose_path (i) [[1]], 1L) 27 | }, 28 | character (1), 29 | USE.NAMES = FALSE 30 | ) 31 | 32 | chk <- any (grepl ("^contributing(\\.|$)", flist, ignore.case = TRUE)) 33 | 34 | return (chk) 35 | } 36 | 37 | output_pkgchk_has_contrib <- function (checks) { 38 | 39 | out <- list ( 40 | check_pass = checks$checks$has_contrib, 41 | summary = "", 42 | print = "" 43 | ) # no print method 44 | 45 | out$summary <- paste0 ( 46 | ifelse (out$check_pass, "has", "does not have"), 47 | " a 'contributing' file." 48 | ) 49 | 50 | return (out) 51 | } 52 | -------------------------------------------------------------------------------- /R/check-license.R: -------------------------------------------------------------------------------- 1 | #' Check whether the package license is acceptable. 2 | #' 3 | #' Details of acceptable licenses are provided in the links in [our *Packaging 4 | #' Guide*](https://devguide.ropensci.org/pkg_building.html#licence). 5 | #' 6 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 7 | #' \pkg{goodpractice} results. 8 | #' @return Character vector of any unacceptable license entries; otherwise 9 | #' a character(0) if license(s) is/are acceptable. 10 | #' @noRd 11 | pkgchk_license <- function (checks) { 12 | 13 | # https://cran.r-project.org/doc/manuals/R-exts.html#Licensing 14 | # multiple licenses must be separated by vertical bars 15 | licenses <- strsplit (checks$pkg$license, "\\|") [[1]] 16 | 17 | llist <- paste0 (license_list (), collapse = "|") 18 | okay <- vapply ( 19 | licenses, function (i) { 20 | grepl (llist, i) 21 | }, 22 | logical (1) 23 | ) 24 | 25 | names (okay [which (!okay)]) 26 | } 27 | 28 | output_pkgchk_license <- function (checks) { 29 | 30 | out <- list ( 31 | check_pass = length (checks$checks$license) == 0L, 32 | summary = "", 33 | print = "" 34 | ) 35 | 36 | if (!out$check_pass) { 37 | out$summary <- paste0 ( 38 | "Package contains unacceptable 'License' entries: [", 39 | checks$checks$license, "]" 40 | ) 41 | } 42 | 43 | return (out) 44 | } 45 | -------------------------------------------------------------------------------- /R/check-num-imports.R: -------------------------------------------------------------------------------- 1 | #' Check numbers of package dependencies ('Imports') 2 | #' 3 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 4 | #' \pkg{goodpractice} results. 5 | #' @return Numeric vector of two values of (number of package "Imports", and 6 | #' proportional threshold of all packages with more imports). 7 | #' @noRd 8 | pkgchk_num_imports <- function (checks) { 9 | 10 | deps <- checks$pkg$dependencies 11 | n <- length (which (deps$type == "imports" & !deps$package == "NA")) 12 | 13 | ndeps_all <- retrieve_all_pkg_deps () 14 | ndeps_pc <- length (which (ndeps_all <= n)) / length (ndeps_all) 15 | # If available.packages() fails, then 'ndeps_pc' == NaN: 16 | not_a_number <- length (ndeps_pc) == 0L || is.na (ndeps_pc) 17 | ndeps_pc <- ifelse (not_a_number, 0L, ndeps_pc) 18 | 19 | return (c (n, ndeps_pc)) 20 | } 21 | 22 | output_pkgchk_num_imports <- function (checks) { 23 | 24 | import_threshold <- 0.95 25 | 26 | out <- list ( 27 | check_pass = checks$checks$num_imports [2] < import_threshold, 28 | summary = "", 29 | print = "" 30 | ) 31 | 32 | if (!out$check_pass) { 33 | out$summary <- paste0 ( 34 | "Package has unusually large number of ", 35 | round (checks$checks$num_imports [1]), 36 | " Imports (> ", 37 | floor (100 * checks$checks$num_imports [2]), 38 | "% of all packages)" 39 | ) 40 | } 41 | 42 | return (out) 43 | } 44 | 45 | retrieve_all_pkg_deps <- function () { 46 | 47 | ap <- get_available_packages () 48 | num_imports <- vapply (ap$Imports, function (i) { 49 | ifelse (is.na (i), 0L, length (strsplit (i, ",") [[1]])) 50 | }, integer (1L), USE.NAMES = FALSE) 51 | return (sort (num_imports)) 52 | } 53 | -------------------------------------------------------------------------------- /R/check-obsolete-pkg-deps.R: -------------------------------------------------------------------------------- 1 | #' Check whether the package depends on any obsolete packages for which better 2 | #' alternative should be used. 3 | #' 4 | #' The list of obsolete packages is given in [our *Packaging 5 | #' Guide*](https://devguide.ropensci.org/pkg_building.html#recommended-scaffolding). 6 | #' Some of these are truly obsolete, the use of which raises a red cross in the 7 | #' summary of checks; while others are only potentially obsolete, thus use of 8 | #' which merely raises a note in the detailed check output. 9 | #' 10 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 11 | #' \pkg{goodpractice} results. 12 | #' @return Character vector of any obsolete dependencies; otherwise 13 | #' a character(0) if license(s) is/are acceptable. 14 | #' @noRd 15 | pkgchk_obsolete_pkg_deps <- function (checks) { 16 | 17 | obs_pkgs <- c ( 18 | "RCurl", "rjson", "RJSONIO", "XML", # truly obselete 19 | "sp", "rgdal", "maptools", "rgeos" # potentially obsolete 20 | ) 21 | 22 | deps <- checks$pkg$dependencies$package 23 | deps <- deps [which (!deps == "NA")] 24 | 25 | deps [which (deps %in% obs_pkgs)] 26 | } 27 | 28 | output_pkgchk_obsolete_pkg_deps <- function (checks) { # nolint 29 | 30 | # https://github.com/ropensci/software-review-meta/issues/47 31 | # potential <- paste0 (c ("sp", "rgdal", "maptools", "rgeos"), collapse = "|") 32 | # Elevate "sp" to "truly obsolete" 33 | potential <- paste0 (c ("rgdal", "maptools", "rgeos"), collapse = "|") 34 | potential <- grep (potential, checks$checks$obsolete_pkg_deps, value = TRUE) 35 | 36 | if (length (potential) == 0) { 37 | index <- seq_along (checks$checks$obsolete_pkg_deps) 38 | } else { 39 | index <- which (!grepl ( 40 | paste0 (potential, collapse = "|"), 41 | checks$checks$obsolete_pkg_deps 42 | )) 43 | } 44 | obs_pkg_deps <- checks$checks$obsolete_pkg_deps [index] 45 | 46 | out <- list ( 47 | check_pass = length (obs_pkg_deps) == 0L, 48 | summary = "", 49 | print = "" 50 | ) 51 | 52 | if (!out$check_pass) { 53 | out$summary <- paste0 ( 54 | "Package depends on the following obsolete packages: [", 55 | paste0 (obs_pkg_deps, collapse = ","), "]" 56 | ) 57 | } 58 | 59 | if (length (potential) > 0L || !out$check_pass) { 60 | 61 | ptl <- ifelse (length (potential) > 0L, "(potentially) ", "") 62 | obs_pkgs <- unique (c (obs_pkg_deps, potential)) 63 | 64 | out$print <- list ( 65 | msg_pre = paste0 ( 66 | "Package contains the following ", 67 | ptl, 68 | "obsolete packages:" 69 | ), 70 | obj = obs_pkgs, 71 | msg_post = paste0 ( 72 | "See our ", 73 | "[Recommended Scaffolding](https://devguide.ropensci.org/", 74 | "building.html?q=scaffol#recommended-scaffolding)", 75 | " for alternatives." 76 | ) 77 | ) 78 | } 79 | 80 | return (out) 81 | } 82 | -------------------------------------------------------------------------------- /R/check-on-cran.R: -------------------------------------------------------------------------------- 1 | #' Check whether a package is on CRAN or not. 2 | #' 3 | #' This does not currently appear in any 'pkgcheck' output (that is, neither in 4 | #' summary or print methods), and is only used as part of 5 | #' `pkcchk_pkgname_available`. 6 | #' 7 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 8 | #' \pkg{goodpractice} results. 9 | #' @return A single logical value indicating whether a package is on CRAN 10 | #' (`TRUE`), or not (`FALSE`). 11 | #' @noRd 12 | pkgchk_on_cran <- function (checks) { 13 | 14 | desc <- data.frame ( 15 | read.dcf (fs::path ( 16 | checks$pkg$path, 17 | "DESCRIPTION" 18 | )), 19 | stringsAsFactors = FALSE 20 | ) 21 | pkg <- desc$Package 22 | 23 | ap <- get_available_packages () 24 | res <- pkg %in% ap$Package 25 | 26 | if (res) { 27 | # Check whether CRAN package of that name has same title 28 | 29 | u <- paste0 ( 30 | "https://cran.r-project.org/web/packages/", 31 | pkg, "/index.html" 32 | ) 33 | x <- tryCatch ( 34 | rvest::read_html (u), 35 | error = function (e) e 36 | ) 37 | 38 | res <- FALSE 39 | if (!methods::is (x, "simpleError")) { 40 | h2 <- paste0 (rvest::html_elements (x, "h2")) 41 | h2 <- gsub ("

|<\\/h2>", "", h2) 42 | if (length (h2) > 0L) { 43 | res <- grepl (desc$Title, h2, fixed = TRUE) | 44 | grepl (desc$Title, h2, ignore.case = TRUE, perl = TRUE) 45 | 46 | if (!res) { # Title may have changed; check URL as backup 47 | tab <- rvest::html_table (x) [[1]] 48 | if (ncol (tab) == 2) { 49 | names (tab) <- c ("field", "value") 50 | tab <- tab [grep ("^URL", tab$field), ] 51 | url <- paste0 (tab$value, collapse = ", ") 52 | res <- identical (url, desc$URL) 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | return (res) 60 | } 61 | -------------------------------------------------------------------------------- /R/check-pkgdown.R: -------------------------------------------------------------------------------- 1 | 2 | # This check is currently active, but can be modified and switched on to check 3 | # pkgdown-related aspects of package documentation. 4 | output_pkgchk_pkgdown <- function (checks) { 5 | 6 | # Grouped concepts must mean at least 2, so check is > 1: 7 | out <- list ( 8 | check_pass = length (checks$info$pkgdown_concepts) > 1L, 9 | summary = "", 10 | print = "" 11 | ) 12 | 13 | if (!out$check_pass) { 14 | # out$summary <- paste0 ( 15 | # "Function documentation entries are not grouped by concept" 16 | # ) 17 | } 18 | 19 | return (out) 20 | } 21 | -------------------------------------------------------------------------------- /R/check-pkgname-available.R: -------------------------------------------------------------------------------- 1 | #' Check that package name is available and/or already on CRAN. 2 | #' 3 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 4 | #' \pkg{goodpractice} results. 5 | #' @noRd 6 | pkgchk_pkgname_available <- function (checks) { 7 | 8 | desc <- data.frame ( 9 | read.dcf (fs::path ( 10 | checks$pkg$path, 11 | "DESCRIPTION" 12 | )), 13 | stringsAsFactors = FALSE 14 | ) 15 | pkg <- desc$Package 16 | 17 | pkg_grepped <- grep ( 18 | .standard_regexps ()$valid_package_name, 19 | pkg, 20 | value = TRUE 21 | ) 22 | 23 | ap <- get_available_packages () 24 | 25 | return ( 26 | !pkg %in% ap$Package & 27 | pkg == pkg_grepped 28 | ) 29 | } 30 | 31 | output_pkgchk_pkgname <- function (checks) { 32 | 33 | out <- list ( 34 | check_pass = TRUE, 35 | summary = "", 36 | print = "" 37 | ) # no print method 38 | 39 | if (checks$checks$pkgname_available & !checks$checks$on_cran) { 40 | out$summary <- "Package name is available" 41 | } else if (checks$checks$on_cran) { 42 | out$summary <- "Package is already on CRAN." 43 | } else { 44 | out$check_pass <- FALSE 45 | out$summary <- "Package name is not available (on CRAN)." 46 | } 47 | 48 | return (out) 49 | } 50 | -------------------------------------------------------------------------------- /R/check-renv.R: -------------------------------------------------------------------------------- 1 | 2 | #' For packages which use 'renv', check that it is de-activated. 3 | #' 4 | #' Although we do not generally recommend 'renv' for package developement, 5 | #' packages may use it. They must, however, ensure that [`renv` is 6 | #' deactivated](https://rstudio.github.io/renv/reference/deactivate.html). 7 | #' 8 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 9 | #' \pkg{goodpractice} results. 10 | #' @return TRUE if `renv` is used; otherwise FALSE 11 | #' @noRd 12 | pkgchk_renv_activated <- function (checks) { 13 | 14 | return (checks$info$renv_activated) 15 | } 16 | 17 | output_pkgchk_renv_activated <- function (checks) { 18 | 19 | out <- list ( 20 | check_pass = !checks$checks$renv_activated, 21 | summary = "", 22 | print = "" 23 | ) 24 | 25 | if (!out$check_pass) { 26 | out$summary <- "Package has renv activated" 27 | } 28 | 29 | return (out) 30 | } 31 | -------------------------------------------------------------------------------- /R/check-scrap.R: -------------------------------------------------------------------------------- 1 | # https://github.com/yihui/knitr-examples/blob/master/113-externalization.Rmd 2 | 3 | # ---- pkgchk-scrap ---- 4 | #' Check whether the package contains any useless files like `.DS_Store`. 5 | #' 6 | #' Files currently considered "scrap" are: 7 | #' 8 | #' 1. ".DS_Store" 9 | #' 2. "Thumbs.db" 10 | #' 3. ".vscode" 11 | #' 4. ".o" files 12 | #' 13 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 14 | #' \pkg{goodpractice} results. 15 | #' @return Names of any items which should not be present; otherwise an empty 16 | #' character. 17 | #' @noRd 18 | pkgchk_has_scrap <- function (checks) { 19 | 20 | # Have to tryCatch because gert errors anywhere other than a git repo. This 21 | # means scrap can only be detected in git repos. 22 | contents <- tryCatch (gert::git_ls (repo = checks$pkg$path)$path, 23 | error = function (e) NULL 24 | ) 25 | 26 | if (is.null (contents)) { 27 | return (character (0)) 28 | } # not NULL! 29 | 30 | contents_short <- vapply ( 31 | decompose_path (contents), 32 | function (i) utils::tail (i, 1L), 33 | character (1) 34 | ) 35 | 36 | scrap <- function () { 37 | paste0 (c ( 38 | "^\\.DS_Store$", 39 | "^Thumbs.db$", 40 | "^\\.vscode$", 41 | "\\.o$" 42 | ), 43 | collapse = "|" 44 | ) 45 | } 46 | 47 | return (contents [grep (scrap (), contents_short)]) 48 | } 49 | 50 | # ---- output-pkgchk-scrap ---- 51 | output_pkgchk_has_scrap <- function (checks) { 52 | 53 | out <- list ( 54 | check_pass = length (checks$checks$has_scrap) == 0L, 55 | summary = "", 56 | print = "" 57 | ) 58 | 59 | if (!out$check_pass) { 60 | out$summary <- "Package contains unexpected files." 61 | out$print <- list ( 62 | msg_pre = paste0 ( 63 | "Package contains the ", 64 | "following unexpected files:" 65 | ), 66 | obj = checks$checks$has_scrap, 67 | msg_post = character (0) 68 | ) 69 | } 70 | 71 | return (out) 72 | } 73 | -------------------------------------------------------------------------------- /R/check-unique-fn-names.R: -------------------------------------------------------------------------------- 1 | #' Check whether all function names are unique. 2 | #' 3 | #' Uses the database of function names from all CRAN packages associated with 4 | #' [releases of the `pkgstats` 5 | #' package](https://github.com/ropensci-review-tools/pkgstats/releases). 6 | #' 7 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 8 | #' \pkg{goodpractice} results. 9 | #' @return Matrix of (package, fn_name) identifying any packages with duplicated 10 | #' function names. 11 | #' @noRd 12 | pkgchk_unique_fn_names <- function (checks) { 13 | 14 | # The cache_path is set to tempdir in tests. This tests is then switched off 15 | # to avoid downloading the database just for this test. 16 | cache_path <- Sys.getenv ("PKGCHECK_CACHE_DIR") 17 | cache_is_temp <- identical ( 18 | normalizePath (dirname (cache_path)), 19 | normalizePath (tempdir ()) 20 | ) 21 | 22 | f <- fn_names_cran <- NULL 23 | if (!cache_is_temp) { 24 | f <- tryCatch ( 25 | cache_fn_name_db (), 26 | error = function (e) NULL 27 | ) 28 | } 29 | 30 | if (!is.null (f)) { 31 | fn_names_cran <- tryCatch ( 32 | readRDS (f), 33 | error = function (e) NULL 34 | ) 35 | } 36 | 37 | # fail to read local data: 38 | if (is.null (fn_names_cran)) { 39 | return (data.frame ( 40 | package = character (0), 41 | version = character (0), 42 | fn_name = character (0) 43 | )) 44 | } 45 | 46 | index <- which (!fn_names_cran$package == checks$pkg$name) 47 | fn_names_cran <- fn_names_cran [index, ] 48 | 49 | fn_names <- checks$info$fn_names 50 | fn_names <- fn_names [which (fn_names$fn_name %in% fn_names_cran$fn_name), ] 51 | 52 | fn_names_cran [which (fn_names_cran$fn_name %in% fn_names$fn_name), ] 53 | } 54 | 55 | cache_fn_name_db <- function () { 56 | 57 | cache_path <- Sys.getenv ("PKGCHECK_CACHE_DIR") 58 | 59 | f <- file.path (cache_path, "pkgstats-fn-names.Rds") 60 | if (file.exists (f)) { 61 | return (f) 62 | } 63 | 64 | u <- paste0 ( 65 | "https://github.com/ropensci-review-tools/pkgstats/", 66 | "releases/download/v0.1.2/pkgstats-fn-names.Rds" 67 | ) 68 | utils::download.file (u, f, quiet = TRUE) 69 | 70 | return (f) 71 | } 72 | 73 | output_pkgchk_unique_fn_names <- function (checks) { # nolint 74 | 75 | out <- list ( 76 | check_pass = nrow (checks$checks$unique_fn_names) == 0L, 77 | summary = "", 78 | print = "" 79 | ) 80 | 81 | if (!out$check_pass) { 82 | out$summary <- "Function names are duplicated in other packages" 83 | 84 | obj <- checks$checks$unique_fn_names 85 | obj <- obj [order (obj$fn_name), ] 86 | 87 | nfns <- length (unique (obj$fn_name)) 88 | 89 | txt <- ifelse (nfns == 1L, 90 | "function name is ", 91 | paste0 (nfns, " function names are ") 92 | ) 93 | 94 | obj <- lapply ( 95 | split (obj, f = as.factor (obj$fn_name)), 96 | function (i) { 97 | paste0 ( 98 | " - `", 99 | i$fn_name [1], 100 | "` from ", 101 | paste0 (i$package, collapse = ", ") 102 | ) 103 | } 104 | ) 105 | 106 | out$print <- list ( 107 | msg_pre = paste0 ( 108 | "The following ", 109 | txt, 110 | "duplicated in other packages:" 111 | ), 112 | obj = obj, 113 | msg_post = "" 114 | ) 115 | } 116 | 117 | return (out) 118 | } 119 | -------------------------------------------------------------------------------- /R/check-urls.R: -------------------------------------------------------------------------------- 1 | #' Check that a package 'DESCRIPTION' file lists a valid URL. 2 | #' 3 | #' @noRd 4 | pkgchk_has_url <- function (checks) { 5 | 6 | out <- length (checks$pkg$url) > 0L 7 | if (out) { 8 | out <- !is.na (checks$pkg$url) & nzchar (checks$pkg$url) 9 | } 10 | 11 | return (out) 12 | } 13 | 14 | #' Check that a package 'DESCRIPTION' file lists a valid URL in the "BugReports" 15 | #' field. 16 | #' 17 | #' @noRd 18 | pkgchk_has_bugs <- function (checks) { 19 | 20 | out <- length (checks$pkg$BugReports) > 0L 21 | if (out) { 22 | out <- !is.na (checks$pkg$BugReports) & nzchar (checks$pkg$BugReports) 23 | } 24 | 25 | return (out) 26 | } 27 | 28 | output_pkgchk_has_url <- function (checks) { 29 | 30 | out <- list ( 31 | check_pass = checks$checks$has_url, 32 | summary = "", 33 | print = "" 34 | ) # no print method 35 | 36 | out$summary <- paste0 ( 37 | "'DESCRIPTION' ", 38 | ifelse (out$check_pass, "has", "does not have"), 39 | " a URL field." 40 | ) 41 | 42 | return (out) 43 | } 44 | 45 | output_pkgchk_has_bugs <- function (checks) { 46 | 47 | out <- list ( 48 | check_pass = checks$checks$has_bugs, 49 | summary = "", 50 | print = "" 51 | ) # no print method 52 | 53 | out$summary <- paste0 ( 54 | "'DESCRIPTION' ", 55 | ifelse (out$check_pass, "has", "does not have"), 56 | " a BugReports field." 57 | ) 58 | 59 | return (out) 60 | } 61 | 62 | #' Bob Rudis's URL checker function 63 | #' 64 | #' @param x a single URL 65 | #' @param non_2xx_return_value what to do if the site exists but the HTTP status 66 | #' code is not in the `2xx` range. Default is to return `FALSE`. 67 | #' @param quiet if not `FALSE`, then every time the `non_2xx_return_value` 68 | #' condition arises a warning message will be displayed. Default is `FALSE`. 69 | #' @param ... other params (`timeout()` would be a good one) passed directly to 70 | #' `httr::HEAD()` and/or `httr::GET()` 71 | #' 72 | #' @note 73 | #' https://stackoverflow.com/questions/52911812/check-if-url-exists-in-r 74 | #' @noRd 75 | url_exists <- function (x, non_2xx_return_value = FALSE, quiet = FALSE, ...) { 76 | 77 | # you don't need thse two functions if you're already using `purrr` 78 | # but `purrr` is a heavyweight compiled package that introduces 79 | # many other "tidyverse" dependencies and this doesnt. 80 | 81 | capture_error <- function (code, otherwise = NULL, quiet = TRUE) { 82 | tryCatch ( 83 | list (result = code, error = NULL), 84 | error = function (e) { 85 | if (!quiet) { 86 | message ("Error: ", e$message) 87 | } 88 | 89 | list (result = otherwise, error = e) 90 | }, 91 | interrupt = function (e) { 92 | stop ("Terminated by user", call. = FALSE) 93 | } 94 | ) 95 | } 96 | 97 | safely <- function (.f, otherwise = NULL, quiet = TRUE) { 98 | function (...) capture_error (.f (...), otherwise, quiet) 99 | } 100 | 101 | sHEAD <- safely (httr::HEAD) # nolint 102 | sGET <- safely (httr::GET) # nolint 103 | 104 | # Try HEAD first since it's lightweight 105 | res <- sHEAD (x, ...) 106 | 107 | if (is.null (res$result) || 108 | ((httr::status_code (res$result) %/% 200) != 1)) { 109 | res <- sGET (x, ...) 110 | 111 | if (is.null (res$result)) { 112 | return (NA) 113 | } # or whatever you want to return on "hard" errors 114 | 115 | if (((httr::status_code (res$result) %/% 200) != 1)) { 116 | if (!quiet) { 117 | warning (paste0 ( 118 | "Requests for [", 119 | x, 120 | "] responded but without an HTTP status ", 121 | "code in the 200-299 range" 122 | )) 123 | } 124 | return (non_2xx_return_value) 125 | } 126 | 127 | return (TRUE) 128 | } else { 129 | return (TRUE) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /R/check-uses-roxygen2.R: -------------------------------------------------------------------------------- 1 | #' Check whether all documntation has been generated by 'roxygen2' 2 | #' 3 | #' Use of 'roxygen2' to generate package documnetation is mandatory for rOpenSci 4 | #' packages, and is described in detail in [our *Packaging 5 | #' Guide*](https://devguide.ropensci.org/pkg_building.html#roxygen-2-use). 6 | #' 7 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 8 | #' \pkg{goodpractice} results. 9 | #' @return 'TRUE' if all files generated by 'roxygen2', otherwise 'FALSE' 10 | #' @noRd 11 | pkgchk_uses_roxygen2 <- function (checks) { 12 | 13 | rd <- list.files ( 14 | fs::path (checks$pkg$path, "man"), 15 | pattern = "\\.Rd$", 16 | full.names = TRUE 17 | ) 18 | 19 | chk <- vapply (rd, function (i) { 20 | l1 <- readLines (i, n = 1L, encoding = "UTF-8") 21 | grepl ("Generated by roxygen2", l1, ignore.case = TRUE) 22 | }, 23 | logical (1), 24 | USE.NAMES = FALSE 25 | ) 26 | 27 | return (all (chk)) 28 | } 29 | 30 | output_pkgchk_uses_roxygen2 <- function (checks) { 31 | 32 | out <- list ( 33 | check_pass = checks$checks$uses_roxygen2, 34 | summary = "", 35 | print = "" 36 | ) # no print method 37 | 38 | out$summary <- ifelse (out$check_pass, 39 | "uses 'roxygen2'.", 40 | "does not use 'roxygen2'." 41 | ) 42 | 43 | return (out) 44 | } 45 | -------------------------------------------------------------------------------- /R/check-vignette.R: -------------------------------------------------------------------------------- 1 | #' Check whether the package contains at least one vignette. 2 | #' 3 | #' @param checks A 'pkgcheck' object with full \pkg{pkgstats} summary and 4 | #' \pkg{goodpractice} results. 5 | #' @return Logical flag 6 | #' @noRd 7 | pkgchk_has_vignette <- function (checks) { 8 | 9 | # from pkgdown 10 | # https://github.com/r-lib/pkgdown/blob/705ff7c650bb1c7d46d35e72f27ad093689e2f29/R/package.r#L202 # nolint 11 | base <- fs::path (checks$pkg$path, "vignettes") 12 | 13 | vig_path <- character (0L) 14 | 15 | if (dir.exists (base)) { 16 | vig_path <- list.files (base, 17 | pattern = "\\.[rRq]md$", 18 | recursive = TRUE, 19 | full.names = TRUE 20 | ) 21 | } 22 | 23 | is_html <- function (f) { 24 | format <- rmarkdown::default_output_format (f)$name 25 | 26 | # See suffix dictionary at 27 | # ropensci-review-tools/pkgstats/R/type-dict.R 28 | # `.xht` is ignored here, otherwise the full list is 29 | # - .html.hl 30 | # - .htm 31 | # - .html 32 | # - .xhtml 33 | # - .phtml 34 | # - .rhtml 35 | # - .cshtml 36 | # - .distill_article 37 | # so the following pattern suffices: 38 | html_prefixes <- "htm(l?)|distill" 39 | 40 | grepl (html_prefixes, format, ignore.case = TRUE) 41 | } 42 | 43 | is_html <- unlist (lapply (vig_path, is_html)) 44 | 45 | return (any (is_html)) 46 | } 47 | 48 | output_pkgchk_has_vignette <- function (checks) { 49 | 50 | out <- list ( 51 | check_pass = checks$checks$has_vignette, 52 | summary = "", 53 | print = "" 54 | ) # no print method 55 | 56 | out$summary <- ifelse ( 57 | out$check_pass, 58 | "Package has at least one HTML vignette", 59 | "Package has no HTML vignettes" 60 | ) 61 | 62 | return (out) 63 | } 64 | -------------------------------------------------------------------------------- /R/checks-left-assign.R: -------------------------------------------------------------------------------- 1 | # left_assign checks only appear as cross in summary, and have neither a print 2 | # method, nor do they appear in markdown formatted reports. 3 | 4 | #' Check that left-assignment operators are used consistently throughout a 5 | #' package. 6 | #' 7 | #' Left-assign operators recognised by R are: ("=", "<-", "<<-", ":="), as 8 | #' defined in [the main grammar 9 | #' definition](https://github.com/wch/r-source/blob/trunk/src/main/gram.y). 10 | #' This actually checks the following two conditions: 11 | #' 12 | #' 1. Check that any "global assignment operators" ("<<-") are only used in 13 | #' appropriate contexts; and 14 | #' 2. Check that all left-assignment operators are consistent, so either all use 15 | #' "=", or all use "<-", but do not mix these two symbols. 16 | #' 17 | #' The `:=` operator is ignored in these checks. 18 | #' 19 | #' @inheritParams pkg_uses_roxygen2 20 | #' @return Named vector of 2 values tallying instances of usage of `<-` and `=`. 21 | #' @noRd 22 | pkgchk_left_assign <- function (checks) { 23 | 24 | rdir <- fs::path (checks$pkg$path, "R") 25 | if (!file.exists (rdir)) { 26 | return (list ( 27 | global = FALSE, 28 | usage = c ( 29 | "<-" = 0L, 30 | "=" = 0L 31 | ) 32 | )) 33 | } 34 | 35 | rdir <- normalizePath (rdir) 36 | flist <- list.files (rdir, 37 | full.names = TRUE, 38 | pattern = "\\.q$|\\.r$|\\.s$", 39 | ignore.case = TRUE 40 | ) 41 | 42 | assigns <- vapply (flist, function (i) { 43 | 44 | p <- tryCatch (utils::getParseData (parse (i)), 45 | error = function (e) NULL 46 | ) 47 | 48 | assigns <- c ( 49 | ":=" = 0L, 50 | "<-" = 0L, 51 | "<<-" = 0L, 52 | "=" = 0L 53 | ) 54 | 55 | if (is.null (p)) { 56 | return (assigns) 57 | } 58 | 59 | la <- table (p$text [which (p$token == "LEFT_ASSIGN")]) 60 | 61 | if (":=" %in% names (la)) { 62 | assigns [1] <- la [which (names (la) == ":=")] 63 | } 64 | if ("<-" %in% names (la)) { 65 | assigns [2] <- la [which (names (la) == "<-")] 66 | } 67 | if ("<<-" %in% names (la)) { 68 | assigns [3] <- la [which (names (la) == "<<-")] 69 | } 70 | if ("=" %in% names (la)) { 71 | assigns [4] <- la [which (names (la) == "=")] 72 | } 73 | 74 | return (assigns) 75 | }, 76 | integer (4), 77 | USE.NAMES = TRUE 78 | ) 79 | 80 | assigns <- rm_global_assign_in_ref_class (assigns, checks) 81 | assigns <- rm_global_assign_in_memoise (assigns, checks) 82 | 83 | assigns <- rowSums (assigns) 84 | # rm `:=`: 85 | assigns <- assigns [which (!names (assigns) == ":=")] 86 | 87 | out <- list (global = assigns [["<<-"]] > 0) 88 | assigns <- assigns [names (assigns) != "<<-"] 89 | out$usage <- assigns 90 | 91 | return (out) 92 | } 93 | 94 | # Allow global assign in RefClass statement (#145) 95 | rm_global_assign_in_ref_class <- function (assigns, checks) { 96 | 97 | global_row <- which (rownames (assigns) == "<<-") 98 | global <- assigns [global_row, ] 99 | global <- global [global > 0L] 100 | if (length (global) == 0L) { 101 | return (assigns) 102 | } 103 | 104 | global <- data.frame ( 105 | file = gsub (checks$pkg$path, "", names (global)), 106 | n = as.integer (global) 107 | ) 108 | global$file <- gsub (paste0 ("^", .Platform$file.sep), "", global$file) 109 | 110 | loc_stats <- utils::getFromNamespace ("loc_stats", "pkgstats") 111 | get_ctags <- utils::getFromNamespace ("get_ctags", "pkgstats") 112 | 113 | loc <- loc_stats (checks$pkg$path) 114 | has_tabs <- any (loc$ntabs > 0L) 115 | tags <- withr::with_dir (checks$pkg$path, get_ctags ("R", has_tabs)) 116 | tags <- tags [which (tags$file %in% global$file & 117 | grepl ("RefClass", tags$content)), ] 118 | 119 | if (nrow (tags) == 0L) { 120 | return (assigns) 121 | } 122 | 123 | for (i in seq (nrow (tags))) { 124 | 125 | f <- file.path (checks$pkg$path, tags$file [i]) 126 | if (!file.exists (f)) { 127 | next # should never happen 128 | } 129 | 130 | code <- suppressWarnings (readLines (f)) 131 | code <- code [seq (tags$start [i], tags$end [i])] 132 | code <- tryCatch ( 133 | utils::getParseData (parse (text = code)), 134 | error = function (e) NULL 135 | ) 136 | 137 | la <- table (code$text [which (code$token == "LEFT_ASSIGN")]) 138 | nglobal <- 0 139 | if ("<<-" %in% names (la)) { 140 | nglobal <- la [which (names (la) == "<<-")] 141 | } 142 | 143 | if (nglobal > 0L) { 144 | col_num <- match (f, colnames (assigns)) 145 | assigns [global_row, col_num] <- assigns [global_row, col_num] - nglobal 146 | } 147 | } 148 | 149 | return (assigns) 150 | } 151 | 152 | # Remove any memoise global assigns in `.onLoad` fucntions (#167) 153 | rm_global_assign_in_memoise <- function (assigns, checks) { 154 | 155 | global_row <- which (rownames (assigns) == "<<-") 156 | global <- assigns [global_row, ] 157 | global <- global [global > 0] 158 | if (length (global) == 0L) { 159 | return (assigns) 160 | } 161 | 162 | global <- data.frame ( 163 | file = gsub (checks$pkg$path, "", names (global)), 164 | n = as.integer (global) 165 | ) 166 | global$file <- gsub (paste0 ("^", .Platform$file.sep), "", global$file) 167 | 168 | loc_stats <- utils::getFromNamespace ("loc_stats", "pkgstats") 169 | get_ctags <- utils::getFromNamespace ("get_ctags", "pkgstats") 170 | 171 | loc <- loc_stats (checks$pkg$path) 172 | has_tabs <- any (loc$ntabs > 0L) 173 | tags <- withr::with_dir (checks$pkg$path, get_ctags ("R", has_tabs)) 174 | tags <- tags [which (tags$file %in% global$file), ] 175 | 176 | for (f in unique (tags$file)) { 177 | 178 | f_full <- fs::path (checks$pkg$path, f) 179 | tags_f <- tags [which (tags$file == f), ] 180 | onload_i <- which (tags_f$tag == ".onLoad") 181 | if (length (onload_i == 1L)) { 182 | onload_index <- seq (tags_f$start [onload_i], tags_f$end [onload_i]) 183 | tags_in_onload <- tags_f [tags_f$start %in% onload_index, ] 184 | memoise_assign <- grep ("<<\\-\\s?memoise", tags_in_onload$content) 185 | col_num <- match (f_full, colnames (assigns)) 186 | assigns [global_row, col_num] <- assigns [global_row, col_num] - length (memoise_assign) 187 | } 188 | } 189 | 190 | return (assigns) 191 | } 192 | 193 | 194 | output_pkgchk_global_assign <- function (checks) { 195 | 196 | out <- list ( 197 | check_pass = !checks$checks$left_assign$global, 198 | summary = "", 199 | print = "" 200 | ) # no print method 201 | 202 | if (!out$check_pass) { 203 | out$summary <- "Package uses global assignment operator ('<<-')." 204 | } 205 | 206 | return (out) 207 | } 208 | 209 | output_pkgchk_left_assign <- function (checks) { 210 | 211 | la <- checks$checks$left_assign$usage # tally of [`<-`, `=`] 212 | 213 | out <- list ( 214 | check_pass = any (la == 0), 215 | summary = "", 216 | print = "" 217 | ) # no print method 218 | 219 | if (!out$check_pass) { 220 | out$summary <- paste0 ( 221 | "Package uses inconsistent assignment operators (", 222 | la [names (la) == "<-"], " '<-' and ", 223 | la [names (la) == "="], " '=')." 224 | ) 225 | } 226 | 227 | return (out) 228 | } 229 | -------------------------------------------------------------------------------- /R/checks-srr.R: -------------------------------------------------------------------------------- 1 | #' Check that 'srr' documentation for statistics pacakges is complete. 2 | #' 3 | #' Procedures for preparing and submitting statistical packages are described in 4 | #' [our "*Stats Dev Guide*"](https://stats-devguide.ropensci.org). Statistical 5 | #' packages must use [the 'srr' (software review roclets) 6 | #' package](https://docs.ropensci.org/srr) to document compliance with our 7 | #' statistical standards. This check uses [the `srr::srr_stats_pre_submit()` 8 | #' function](https://docs.ropensci.org/srr/reference/srr_stats_pre_submit.html) 9 | #' to confirm that compliance with all relevant standards has been documented. 10 | #' 11 | #' @noRd 12 | pkgchk_srr_okay <- function (checks) { 13 | 14 | checks$info$srr$okay 15 | } 16 | 17 | output_pkgchk_srr_okay <- function (checks) { 18 | 19 | srr_okay <- "srr" %in% names (checks$info) 20 | if (srr_okay) { 21 | srr_okay <- checks$info$srr$okay 22 | } 23 | out <- list ( 24 | check_pass = srr_okay, 25 | summary = "", 26 | print = "" 27 | ) 28 | if (out$check_pass) { 29 | out$summary <- paste0 ( 30 | "This is a statistical package which ", 31 | "complies with all applicable standards" 32 | ) 33 | } 34 | 35 | return (out) 36 | } 37 | 38 | output_pkgchk_srr_todo <- function (checks) { 39 | 40 | out <- list ( 41 | check_pass = !any (grepl ( 42 | "still has TODO standards", 43 | checks$info$srr$message 44 | )), 45 | summary = grep ("still has TODO standards", 46 | checks$info$srr$message, 47 | value = TRUE 48 | ), 49 | print = "" 50 | ) 51 | 52 | return (out) 53 | } 54 | 55 | output_pkgchk_srr_missing <- function (checks) { 56 | 57 | srr <- checks$info$srr 58 | 59 | check_pass <- !any (grepl ( 60 | "following standards \\[v.*\\] are missing", 61 | srr$message 62 | )) 63 | 64 | out <- list ( 65 | check_pass = check_pass, 66 | summary = "", 67 | print = "" 68 | ) 69 | 70 | if (!out$check_pass) { 71 | out$summary <- "Some statistical standards are missing" 72 | } 73 | 74 | return (out) 75 | } 76 | 77 | output_pkgchk_srr_most_in_one_file <- function (checks) { 78 | 79 | srr <- checks$info$srr 80 | 81 | warn_msg <- "should be documented in" 82 | check_pass <- !any (grepl (warn_msg, srr$message)) 83 | 84 | out <- list ( 85 | check_pass = check_pass, 86 | summary = grep (warn_msg, srr$message, value = TRUE), 87 | print = "" 88 | ) 89 | 90 | return (out) 91 | } 92 | 93 | print_srr <- function (x) { 94 | 95 | cli::cli_h2 ("rOpenSci Statistical Standards") 96 | ncats <- length (x$info$srr$categories) # nolint 97 | cli::cli_alert_info ("The package is in the following {ncats} categor{?y/ies}:") # nolint 98 | cli::cli_li (x$info$srr$categories) 99 | cli::cli_text ("") 100 | cli::cli_alert_info ("Compliance with rOpenSci statistical standards:") 101 | 102 | while (!nzchar (x$info$srr$message [1])) { 103 | x$info$srr$message <- x$info$srr$message [-1] 104 | } 105 | 106 | if (x$info$srr$okay) { 107 | cli::cli_alert_success (x$info$srr$message) 108 | } else { 109 | cli::cli_alert_danger (x$info$srr$message [1]) 110 | if (length (x$info$srr$message) > 1) { 111 | m <- x$info$srr$message 112 | if (grepl ("missing from your code", m [1])) { 113 | m <- m [which (nzchar (m))] [-1] 114 | m <- paste0 (m, collapse = ", ") 115 | cli::cli_text (paste0 (m, ".")) 116 | } 117 | } 118 | return () 119 | } 120 | 121 | if (!is.null (x$info$srr$missing_stds)) { 122 | cli::cli_alert_warning ("The following standards are missing:") 123 | cli::cli_li (x$info$srr$missing_stds) 124 | } 125 | 126 | cli::cli_alert_info ("'srr' report is at [{x$info$srr$report_file}].") 127 | message ("") 128 | } 129 | 130 | #' Format `srr` checks in markdown 131 | #' @param checks Result of main \link{pkgcheck} function 132 | #' @noRd 133 | srr_checks_to_md <- function (checks) { 134 | 135 | if (is.null (checks$info$srr)) { 136 | return (NULL) 137 | } 138 | 139 | while (!nzchar (checks$info$srr$message [1])) { 140 | checks$info$srr$message <- checks$info$srr$message [-1] 141 | } 142 | 143 | sym <- ifelse (checks$info$srr$okay, symbol_tck (), symbol_crs ()) 144 | srr_msg <- paste (sym, checks$info$srr$message [1]) 145 | if (length (checks$info$srr$message) > 1L) { 146 | srr_msg <- paste0 (c ( 147 | srr_msg, 148 | checks$info$srr$message [-1], 149 | collapse = "\n" 150 | )) 151 | } 152 | 153 | cat_plural <- ifelse ( 154 | length (checks$info$srr$categories == 1), 155 | "category", 156 | "categories" 157 | ) 158 | cat_msg <- report_msg <- "" 159 | if (length (checks$info$srr$categories) > 0L) { 160 | cat_msg <- c ( 161 | paste0 ("This package is in the following ", cat_plural, ":"), 162 | "", 163 | paste0 ("- *", checks$info$srr$categories, "*") 164 | ) 165 | report_msg <- paste0 ( 166 | "Click to see the [report of author-reported ", 167 | "standards compliance of the package with links to ", 168 | "associated lines of code](", 169 | report_file (checks), 170 | "), which can be re-generated locally by running the ", 171 | "[`srr_report()` function]", 172 | "(https://docs.ropensci.org/srr/reference/srr_report.html) ", 173 | "from within a local clone of the repository." 174 | ) 175 | } 176 | 177 | c ( 178 | paste0 ( 179 | "### 1. rOpenSci Statistical Standards ", 180 | "([`srr` package]", 181 | "(https://github.com/ropensci-review-tools/srr))" 182 | ), 183 | "", 184 | cat_msg, 185 | "", 186 | srr_msg, 187 | "", 188 | report_msg, 189 | "", 190 | "---", 191 | "" 192 | ) 193 | } 194 | -------------------------------------------------------------------------------- /R/info-call-network.R: -------------------------------------------------------------------------------- 1 | 2 | #' Plot function call network from \pkg{pkgstats} objects 3 | #' 4 | #' @param s Result of \pkg{pkgstats} call 5 | #' @return Local path to 'visjs' HTML diagram of call network. 6 | #' @noRd 7 | fn_call_network <- function (s) { 8 | 9 | if (nrow (s$stats$network) == 0L && nrow (s$stats$objects) == 0L) { 10 | return (NULL) 11 | } 12 | 13 | visjs_dir <- fs::path ( 14 | Sys.getenv ("PKGCHECK_CACHE_DIR"), 15 | "static" 16 | ) 17 | if (!dir.exists (visjs_dir)) { 18 | dir.create (visjs_dir, recursive = TRUE) 19 | } 20 | 21 | visjs_file <- paste0 ( 22 | s$out$name, 23 | "_pkgstats", 24 | substring (s$out$git$HEAD, 1, 8), 25 | ".html" 26 | ) 27 | visjs_path <- fs::path (visjs_dir, visjs_file) 28 | 29 | # clean up any older ones 30 | flist <- list.files ( 31 | visjs_dir, 32 | pattern = paste0 (s$out$package, "_pkgstats"), 33 | full.names = TRUE 34 | ) 35 | 36 | if (!visjs_path %in% flist) { 37 | unlink (flist, recursive = TRUE) 38 | pkgstats::plot_network (s$stats, vis_save = visjs_path) 39 | # visNetwork renames the generic `lib` folder to the specific name, so 40 | # needs to be cleaned up: 41 | flist <- list.files ( 42 | visjs_dir, 43 | pattern = paste0 (s$out$package, "_pkgstats"), 44 | full.names = TRUE 45 | ) 46 | libdir <- flist [which (dir.exists (flist))] 47 | if (!"lib" %in% list.files (visjs_dir)) { 48 | if (length (libdir) > 0) { 49 | libdir <- libdir [1] 50 | fpath <- fs::path (libdir, "..") 51 | newlibdir <- fs::path (fs::path_norm (fpath), "lib") 52 | file.rename (libdir, newlibdir) 53 | } 54 | } else { 55 | unlink (libdir) 56 | } 57 | } 58 | 59 | return (visjs_path) 60 | } 61 | -------------------------------------------------------------------------------- /R/info-ci.R: -------------------------------------------------------------------------------- 1 | # CI checks have no print method, as they are currently only put into the full 2 | # html report, and otherwise only appear in summary. 3 | 4 | #' Get all CI badges from a repository, and check that jobs currently pass. 5 | #' 6 | #' This extracts badges directly from README files. If none are found and a repo 7 | #' is GitHub, then an additional function is called to check CI status directly 8 | #' from GitHub workflow files. 9 | #' 10 | #' @param u URL of repo 11 | #' @return Character vector of hyperlinked badge images 12 | #' @noRd 13 | pkgchk_ci_badges <- function (u) { 14 | 15 | if (!curl::has_internet ()) { 16 | return (NULL) 17 | } 18 | 19 | orgrepo <- strsplit (u, "\\/") [[1]] 20 | org <- utils::tail (orgrepo, 2) [1] 21 | repo <- utils::tail (orgrepo, 1) 22 | # note: default branch is github only, so will only work if repo is also 23 | # mirrored on github! 24 | branch <- get_default_github_branch (org, repo) 25 | 26 | if (grepl ("github", u)) { 27 | 28 | u_readme <- paste0 ( 29 | "https://raw.githubusercontent.com/", 30 | org, 31 | "/", 32 | repo, 33 | "/", 34 | branch, 35 | "/README.md" 36 | ) 37 | 38 | } else if (grepl ("gitlab", u)) { 39 | 40 | u_readme <- paste0 ( 41 | "https://gitlab.com/", 42 | org, 43 | "/", 44 | repo, 45 | "/-/raw/", 46 | branch, 47 | "/README.md" 48 | ) 49 | } 50 | 51 | if (!url_exists (u_readme, quiet = TRUE)) { 52 | return (NULL) 53 | } 54 | 55 | f <- tempfile (fileext = ".md") 56 | chk <- utils::download.file (u_readme, destfile = f, quiet = TRUE) # nolint 57 | readme <- rm_html_comments (readLines (f, encoding = "UTF-8")) 58 | 59 | badges <- unlist (regmatches ( 60 | readme, 61 | gregexpr ("https.*\\.svg", readme) 62 | )) 63 | if (length (badges) == 0) { 64 | return (NULL) 65 | } 66 | 67 | platforms <- paste0 ("https\\:\\/\\/", c ("github", "gitlab")) 68 | badges <- badges [grep ( 69 | paste0 (platforms, collapse = "|"), 70 | badges 71 | )] 72 | 73 | for (p in platforms) { 74 | 75 | index <- grep (p, badges) 76 | p_u <- p 77 | p_no_regex <- gsub ("\\\\", "", p) 78 | 79 | if (p == "https\\:\\/\\/github") { 80 | 81 | wf_nms <- vapply (badges [index], function (i) { 82 | utils::tail (strsplit (i, "/") [[1]], 2) [1] 83 | }, 84 | character (1), 85 | USE.NAMES = FALSE 86 | ) 87 | 88 | p_u <- paste0 ( 89 | "https://github.com/", 90 | org, 91 | "/", 92 | repo, 93 | "/actions" 94 | ) 95 | 96 | } 97 | 98 | badges [index] <- paste0 ( 99 | "[![", 100 | wf_nms, 101 | "](", 102 | badges [index], 103 | ")](", 104 | p_u, 105 | ")" 106 | ) 107 | } 108 | 109 | return (badges) 110 | } 111 | 112 | #' Remove all html-comment chunks from readme 113 | #' 114 | #' See #109: Some READMEs may have badges within html comments, and these should 115 | #' be removed prior to checking presence of CI badges. 116 | #' @param x Character vector of the README file. 117 | #' @noRd 118 | rm_html_comments <- function (x) { 119 | 120 | # First remove single-line comments: 121 | x <- gsub ("", "", x) 122 | 123 | # Then identify and remove multi-line html comments: 124 | cmt_open <- which (regexpr (" 0L) 125 | cmt_close <- which (regexpr ("\\-\\->", x) > 0L) 126 | 127 | if (length (cmt_open) == length (cmt_close) & length (cmt_open) > 0L) { 128 | g <- cbind (cmt_open, cmt_close) 129 | 130 | index <- unlist (apply (g, 1, function (i) seq (i [1], i [2]))) 131 | if (methods::is (index, "matrix")) { 132 | index <- sort (as.vector (index)) 133 | } 134 | 135 | x <- x [-index] 136 | } 137 | 138 | return (x) 139 | } 140 | 141 | 142 | #' CI results for GitHub only 143 | #' @inheritParams pkg_uses_roxygen2 144 | #' @return A 'data.frame' with one row for each GitHub workflow, and columns for 145 | #' name, the 'conclusion' status, the git 'sha', and the date. 146 | #' @noRd 147 | ci_results_gh <- function (path) { 148 | 149 | u <- pkginfo_url_from_desc (path) 150 | if (length (u) == 0L) { 151 | return (NULL) 152 | } 153 | 154 | if (grepl ("github\\.io", u)) { 155 | 156 | u <- pkginfo_url_from_desc (path, type = "BugReports") 157 | if (nzchar (u)) { 158 | u <- gsub ("\\/issues(\\/?)$", "", u) 159 | } else { 160 | return (NULL) 161 | } 162 | } 163 | 164 | url <- strsplit (u, "\\/") [[1]] 165 | org <- utils::tail (url, 2) [1] 166 | repo <- utils::tail (url, 1) 167 | 168 | url <- paste0 ( 169 | "https://api.github.com/repos/", 170 | org, 171 | "/", 172 | repo, 173 | "/actions/runs" 174 | ) 175 | 176 | runs <- jsonlite::fromJSON (url) 177 | 178 | if (!"total_count" %in% names (runs)) { 179 | return (NULL) 180 | } 181 | 182 | if (runs$total_count == 0) { 183 | return (NULL) 184 | } 185 | 186 | dat <- data.frame ( 187 | id = runs$workflow_runs$id, 188 | name = runs$workflow_runs$name, 189 | status = runs$workflow_runs$status, 190 | conclusion = runs$workflow_runs$conclusion, 191 | sha = runs$workflow_runs$head_sha, 192 | run_number = runs$workflow_runs$run_number, 193 | time = runs$workflow_runs$created_at, 194 | stringsAsFactors = FALSE 195 | ) 196 | 197 | dat$time <- strptime (dat$time, "%Y-%m-%dT%H:%M:%SZ") 198 | dat$time_dbl <- as.double (dat$time) 199 | # non-dply group_by %>% summarise: 200 | dat <- lapply ( 201 | split (dat, f = as.factor (dat$name)), 202 | function (i) { 203 | i [which.max (i$time_dbl), ] 204 | } 205 | ) 206 | dat <- do.call (rbind, dat) 207 | 208 | dat$sha <- substring (dat$sha, 1, 6) 209 | dat$date <- strftime (dat$time, "%Y-%m-%d") 210 | rownames (dat) <- dat$time_dbl <- dat$time <- dat$status <- NULL 211 | 212 | return (dat) 213 | } 214 | -------------------------------------------------------------------------------- /R/info-git.R: -------------------------------------------------------------------------------- 1 | 2 | repo_is_git <- function (path) { 3 | 4 | path <- convert_path (path) 5 | 6 | g <- tryCatch ( 7 | gert::git_find (path), 8 | error = function (e) e 9 | ) 10 | 11 | return (!methods::is (g, "libgit2_error")) 12 | } 13 | 14 | #' Return the $git item of main pkgcheck return result 15 | #' 16 | #' Note the prefix is `pkgcheck`, not `pkgchk_`: This is not a check, just a 17 | #' function to return summary data. 18 | #' @noRd 19 | pkginfo_git_info <- function (path) { 20 | 21 | path <- convert_path (path) 22 | 23 | u <- pkginfo_url_from_desc (path) 24 | 25 | branch <- NULL 26 | 27 | if (length (u) > 0L) { 28 | 29 | repo <- utils::tail (strsplit (u, "/") [[1]], 1) 30 | org <- utils::tail (strsplit (u, "/") [[1]], 2) [1] 31 | has_token <- length (get_gh_token ()) > 0L 32 | if (curl::has_internet () & has_token) { 33 | branch <- get_default_github_branch (org, repo) 34 | } 35 | } 36 | 37 | ret <- list () 38 | 39 | if (repo_is_git (path)) { 40 | 41 | gitlog <- gert::git_log (repo = path, max = 1e6) 42 | 43 | # use email addresses to identify unique authors 44 | auts <- gsub ("^.*<|>$", "", unique (gitlog$author)) 45 | 46 | if (is.null (branch)) { # no remote, so assume local head 47 | 48 | branch <- gert::git_info (path)$shorthand 49 | } 50 | 51 | ret <- list ( 52 | HEAD = gitlog$commit [1], 53 | branch = branch, 54 | num_commits = nrow (gitlog), 55 | since = min (gitlog$time), 56 | num_authors = length (unique (auts)) 57 | ) 58 | } 59 | 60 | return (ret) 61 | } 62 | -------------------------------------------------------------------------------- /R/info-pkgdown.R: -------------------------------------------------------------------------------- 1 | #' Get list of all 'concepts' used to group man entries. 2 | #' 3 | #' @param path Location of local repository to report on 4 | #' 5 | #' @noRd 6 | pkginfo_pkgdown <- function (path) { 7 | 8 | rd_files <- list.files ( 9 | fs::path (path, "man"), 10 | pattern = "\\.Rd$", 11 | full.names = TRUE 12 | ) 13 | 14 | concepts <- vapply (rd_files, function (i) { 15 | rd_i <- tools::parse_Rd (i, permissive = TRUE) 16 | get_Rd_meta (rd_i, "concept") [1] 17 | }, 18 | character (1), 19 | USE.NAMES = TRUE 20 | ) 21 | 22 | # Files with no concepts are NA, which is okay: 23 | unique (concepts [which (!is.na (concepts))]) 24 | } 25 | -------------------------------------------------------------------------------- /R/info-pkgstats.R: -------------------------------------------------------------------------------- 1 | # These functions provide information derived from \pkg{pkgstats} without 2 | # actually being checks 3 | 4 | pkginfo_url_from_desc <- function (path, type = "URL") { 5 | 6 | type <- match.arg (type, c ("URL", "BugReports")) 7 | 8 | desc <- fs::path (path, "DESCRIPTION") 9 | if (!file.exists (desc)) { 10 | return ("") 11 | } 12 | 13 | d <- data.frame ( 14 | read.dcf (desc), 15 | stringsAsFactors = FALSE 16 | ) 17 | if (!type %in% names (d)) { 18 | return ("") 19 | } 20 | 21 | u <- strsplit (d [[type]], "\\s+") [[1]] 22 | u <- grep ("^https", u, value = TRUE) 23 | if (length (u) > 1) { 24 | u <- grep ("git", u, value = TRUE) 25 | } 26 | if (length (u) > 1) { 27 | u <- u [which (!grepl ("\\.io", u))] 28 | } 29 | 30 | u <- gsub (",|\\s+", "", u) 31 | 32 | if (length (u) == 0L) { 33 | u <- "" 34 | } 35 | 36 | return (u [1]) 37 | } 38 | 39 | #' @param s Result of `pkgstats(path)` 40 | #' @noRd 41 | pkginfo_pkg_name <- function (s) { 42 | 43 | s$desc$package 44 | } 45 | 46 | pkginfo_pkg_version <- function (s) { 47 | 48 | s$desc$version 49 | } 50 | 51 | pkginfo_pkg_license <- function (s) { 52 | 53 | s$desc$license 54 | } 55 | 56 | pkginfo_pkgstats_summary <- function (s) { 57 | 58 | pkgstats <- fmt_pkgstats_info (s) 59 | 60 | num_exported_fns <- pkgstats$value [pkgstats$measure == "n_fns_r_exported"] 61 | num_non_exported_fns <- pkgstats$value [pkgstats$measure == 62 | "n_fns_r_not_exported"] 63 | num_src_fns <- sum (pkgstats$value [pkgstats$measure %in% 64 | c ("n_fns_src", "n_fns_inst")]) 65 | loc_exported_fns <- pkgstats$value [pkgstats$measure == 66 | "loc_per_fn_r_exp"] 67 | loc_non_exported_fns <- pkgstats$value [pkgstats$measure == 68 | "loc_per_fn_r_not_exp"] 69 | loc_src_fns <- stats::median (pkgstats$value [pkgstats$measure %in% 70 | c ("loc_per_fn_src", "loc_per_fn_inst")]) 71 | num_params_per_fn <- pkgstats$value [pkgstats$measure == 72 | "num_params_per_fn"] 73 | 74 | list ( 75 | num_authors = s$desc$aut, 76 | num_vignettes = unname (s$vignettes [1]), 77 | num_data = unname (s$data_stats [1]), 78 | imported_pkgs = length (strsplit (s$desc$imports, ",") [[1]]), 79 | num_exported_fns = as.integer (num_exported_fns), 80 | num_non_exported_fns = as.integer (num_non_exported_fns), 81 | num_src_fns = as.integer (num_src_fns), 82 | loc_exported_fns = as.integer (loc_exported_fns), 83 | loc_non_exported_fns = as.integer (loc_non_exported_fns), 84 | loc_src_fns = as.integer (loc_src_fns), 85 | num_params_per_fn = as.integer (num_params_per_fn), 86 | languages = attr (pkgstats, "language") 87 | ) 88 | } 89 | -------------------------------------------------------------------------------- /R/info-renv.R: -------------------------------------------------------------------------------- 1 | 2 | #' Check whether renv is activated 3 | #' 4 | #' The actual check is whether \package{renv} is actually activated, which is 5 | #' done by adding a line to a local `.Rprofile` file, `source("renv/init.R")`. 6 | #' 7 | #' @param path Location of local repository 8 | #' 9 | #' @noRd 10 | pkginfo_renv_activated <- function (path) { 11 | 12 | files <- list.files ( 13 | path, 14 | pattern = "renv\\.lock$", 15 | full.names = TRUE 16 | ) 17 | 18 | if (length (files) == 0L) { 19 | return (FALSE) 20 | } 21 | 22 | rprof <- list.files ( 23 | path, 24 | all.files = TRUE, 25 | pattern = "^\\.Rprofile$", 26 | full.names = TRUE 27 | ) 28 | if (length (rprof) == 0L) { 29 | return (FALSE) 30 | } 31 | 32 | rprof <- readLines (rprof) 33 | # from renv/R/infrastructure.R + json.R 34 | renv_path <- encodeString ("renv/activate.R", quote = "\"", justify = "none") 35 | ptn <- sprintf ("source(%s)", renv_path) 36 | source_line <- grep (ptn, rprof, fixed = TRUE, value = TRUE) 37 | 38 | if (length (source_line) != 1L) { # only ever 0 or 1 39 | return (FALSE) 40 | } 41 | 42 | return (!grepl ("^(\\s*?)#", source_line)) 43 | } 44 | 45 | #' Internal implementation of `renv::deactivate()` 46 | #' 47 | #' Source is 48 | #' \url{https://github.com/rstudio/renv/blob/main/R/deactivate.R}, 49 | #' which directly calls 50 | #' `renv_infrastructure_remove_rprofile()` in 51 | #' \url{https://github.com/rstudio/renv/blob/main/R/infrastructure.R}. 52 | #' @noRd 53 | renv_deactivate <- function (path) { 54 | 55 | file <- list.files ( 56 | path, 57 | all.files = TRUE, 58 | pattern = "\\.Rprofile$", 59 | full.names = TRUE 60 | ) 61 | if (!file.exists (file)) { 62 | return () 63 | } 64 | 65 | contents <- readLines (file) 66 | 67 | # Note this is a simplified pattern, and not the original! 68 | pattern <- sprintf ("^\\s*\\Q%s\\E\\s*", "source(\"renv") 69 | matches <- grepl (pattern, contents, perl = TRUE) 70 | 71 | rest <- contents [!matches] 72 | if (all (grepl ("^\\s*$", rest))) { 73 | return (unlink (file)) 74 | } 75 | 76 | replacement <- gsub ("^(\\s*)", "\\1# ", contents [matches], perl = TRUE) 77 | contents [matches] <- replacement 78 | 79 | writeLines (contents, file) 80 | } 81 | -------------------------------------------------------------------------------- /R/info-srr.R: -------------------------------------------------------------------------------- 1 | # These functions provide information derived from \pkg{srr} without 2 | # actually being checks 3 | 4 | #' Format the \pkg{srr} reporting section of the general editorial report. 5 | #' 6 | #' This is a complex multi-stage check which returns a list of sub-checks, 7 | #' including: 8 | #' 9 | #' \enumerate{ 10 | #' 11 | #' \item message: The main message from srr_stats_pre_submit confirming whether 12 | #' all standards have been documented or not 13 | #' \item categories: List of statistical categories addressed in the software 14 | #' \item missing_stds: List of any missing standards 15 | #' \item report_file: Link to the HTML version of the `srr` report file 16 | #' \item okay: Logical flag indicating whether or not all `srr` requirements 17 | #' have been met. 18 | #' 19 | #' } 20 | #' 21 | #' @param path Location of local repository to report on 22 | #' 23 | #' @note Unlike all other checks in this package, this one actually requires the 24 | #' package to be loaded (because it uses \pkg{roxygen2} which only works on 25 | #' loaded packages). 26 | #' 27 | #' @noRd 28 | pkginfo_srr_report <- function (path) { 29 | 30 | srr <- tryCatch ( 31 | srr::srr_stats_pre_submit (path, quiet = TRUE), 32 | error = function (e) e 33 | ) 34 | 35 | if (is.null (srr)) { 36 | return (NULL) 37 | } # Not an srr package 38 | 39 | srr_okay <- !methods::is (srr, "error") 40 | # Warning messages for standards in single directory, or most standards in 41 | # single file, from srr/R/pre-submit.R: 42 | warn_msg <- "should be documented in" 43 | if (srr_okay && any (grepl (warn_msg, srr))) { 44 | srr [grep (warn_msg, srr)] <- 45 | gsub ("^S", "Statistical s", srr [grep (warn_msg, srr)]) 46 | srr_okay <- FALSE 47 | } 48 | 49 | categories <- stds <- NULL 50 | 51 | # get path to report in cache dir: 52 | pkg_hash <- current_hash (path) 53 | static_dir <- fs::path ( 54 | Sys.getenv ("PKGCHECK_CACHE_DIR"), 55 | "static" 56 | ) 57 | if (!dir.exists (static_dir)) { 58 | dir.create (static_dir, recursive = TRUE) 59 | } 60 | 61 | f <- paste0 (pkg_hash [1], "_srr", pkg_hash [2]) 62 | srr_report_file <- fs::path (static_dir, paste0 (f, ".html")) 63 | srr_md_file <- fs::path (static_dir, paste0 (f, ".md")) 64 | 65 | flist <- list.files (static_dir, 66 | full.names = TRUE 67 | ) 68 | fnames <- vapply ( 69 | decompose_path (flist), function (i) { 70 | utils::tail (i, 1) 71 | }, 72 | character (1) 73 | ) 74 | flist <- flist [grep (f, fnames)] 75 | 76 | if (srr_md_file %in% flist) { 77 | 78 | srr_rep <- readLines (srr_md_file) 79 | } else { 80 | 81 | if (length (flist) > 0) { 82 | file.remove (flist) 83 | } 84 | 85 | srr_rep <- srr::srr_report ( 86 | path = path, 87 | view = FALSE 88 | ) 89 | 90 | srr_file_from <- attr (srr_rep, "file") 91 | 92 | if (!is.null (srr_file_from)) { 93 | 94 | if (!file.copy (srr_file_from, srr_report_file)) { 95 | warning ("srr html file not copied!") 96 | } 97 | # srr_report stored the .md as a .Rmd in tempdir(): 98 | srr_rmd <- tools::file_path_sans_ext (srr_file_from) 99 | srr_rmd <- paste0 (srr_rmd, ".Rmd") 100 | if (!file.copy (srr_rmd, srr_md_file)) { 101 | warning ("srr md file not copied!") 102 | } 103 | } 104 | } 105 | 106 | categories <- srr_categories_from_report (srr_rep) 107 | stds <- NULL # missing standards 108 | 109 | i <- grep ("standards \\[v.*\\] are missing from your code", srr) 110 | 111 | if (length (i) > 0) { 112 | 113 | stds <- srr [-seq (i)] 114 | blank <- which (nchar (stds) == 0) 115 | # srr_tail <- NULL 116 | if (length (blank) > 1) { 117 | stds_end <- blank [which (diff (blank) > 1) + 1] 118 | # srr_tail <- stds [seq (stds_end, length (stds))] 119 | stds <- stds [-seq (stds_end, length (stds))] 120 | } 121 | stds <- paste0 (stds [which (nchar (stds) > 0)], 122 | collapse = ", " 123 | ) 124 | } 125 | 126 | if (srr_okay) { 127 | srr_okay <- length (i) == 0 128 | } 129 | 130 | list ( 131 | message = srr, 132 | categories = categories, 133 | missing_stds = stds, 134 | report_file = srr_report_file, 135 | okay = srr_okay 136 | ) 137 | } 138 | 139 | #' Extract the statistical categories from the `srr` report 140 | #' @param s Result of main `srr:srr_report()` function 141 | #' @noRd 142 | srr_categories_from_report <- function (s) { 143 | 144 | cats <- regmatches (s, gregexpr ("[A-Z]+[0-9]+\\.[0-9]+([a-z]?)", s)) 145 | cats <- sort (unique (unlist (cats))) 146 | cats <- unique (gsub ("[0-9].*$", "", cats)) 147 | cats <- cats [which (!cats == "G")] 148 | 149 | prefixes <- c ("BS", "EA", "ML", "RE", "SP", "TS", "UL") 150 | categories <- c ( 151 | "Bayesian and Monte Carlo", 152 | "Exploratory Data Analysis", 153 | "Machine Learning", 154 | "Regression and Supervised Learning", 155 | "Spatial", 156 | "Time Series", 157 | "Dimensionality Reduction, Clustering and Unsupervised Learning" 158 | ) 159 | cats <- cats [which (cats %in% prefixes)] 160 | 161 | return (categories [match (cats, prefixes)]) 162 | } 163 | 164 | report_file <- function (checks) { 165 | 166 | Sys.getenv ("PKGCHECK_SRR_REPORT_FILE", checks$info$srr$report_file) 167 | } 168 | -------------------------------------------------------------------------------- /R/license-list.R: -------------------------------------------------------------------------------- 1 | # do not edit by hand; created by: 2 | # source ('./data-raw/license-list.r') 3 | # 4 | # rOpenSci recommendations: 5 | # https://devguide.ropensci.org/pkg_building.html#licence 6 | # links to cran list used to fill this license-list at 7 | # https://svn.r-project.org/R/trunk/share/licenses/license.db 8 | # plus OSI list which includes the extra CDDL-1.0 license. 9 | 10 | license_list <- function () { 11 | c ( 12 | "GPL", 13 | "LGPL", 14 | "AGPL", 15 | "Apache License", 16 | "Artistic License", 17 | "CeCILL", 18 | "FreeBSD", 19 | "MIT", 20 | "BSD", 21 | "BSD_2_clause", 22 | "BSD_3_clause", 23 | "MPL", 24 | "CPL", 25 | "EPL", 26 | "Lucent Public License", 27 | "EUPL", 28 | "CC BY-SA 2.0", 29 | "CC BY 3.0", 30 | "CC BY-SA 3.0", 31 | "CC BY-NC 3.0", 32 | "CC BY-NC-ND 3.0 US", 33 | "CC BY-NC-SA 3.0", 34 | "CC BY-SA 3.0 US", 35 | "CC BY 4.0", 36 | "CC BY-SA 4.0", 37 | "CC BY-NC 4.0", 38 | "CC BY-NC-SA 4.0", 39 | "CC BY-NC-ND 4.0", 40 | "CC0", 41 | "Zlib", 42 | "BSL", 43 | "ACM", 44 | "CDDL-1.0" 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /R/path-fns.R: -------------------------------------------------------------------------------- 1 | #' Check and convert path arguments 2 | #' 3 | #' Mostly called for side-effects of erroring when path is not root directory of 4 | #' an R package. 5 | #' @noRd 6 | convert_path <- function (path = ".") { 7 | 8 | path <- fs::path_norm (path) 9 | 10 | # see also https://github.com/r-lib/usethis/blob/master/R/proj.R 11 | git_root <- tryCatch ( 12 | rprojroot::find_root (rprojroot::is_git_root, path = path), 13 | error = function (e) NULL 14 | ) 15 | 16 | if (!is.null (git_root)) { 17 | path <- git_root 18 | } 19 | 20 | proj_root <- tryCatch ( 21 | rprojroot::find_package_root_file (path = path), 22 | error = function (e) NULL 23 | ) 24 | if (is.null (proj_root)) { 25 | subdirs <- fs::dir_ls (path, type = "directory") 26 | proj_root <- unlist (lapply (subdirs, function (d) { 27 | tryCatch ( 28 | rprojroot::find_root (rprojroot::is_r_package, path = d), 29 | error = function (e) NULL 30 | ) 31 | })) 32 | } 33 | 34 | if (length (proj_root) != 1L) { 35 | cli::cli_abort ( 36 | "Could not find unambiguous project root from {proj_root}" 37 | ) 38 | } 39 | 40 | return (fs::path (proj_root)) 41 | } 42 | -------------------------------------------------------------------------------- /R/pkg-deps.R: -------------------------------------------------------------------------------- 1 | 2 | #' Format package dependencies as a single table 3 | #' 4 | #' @param checks Result of main \link{pkgcheck} function 5 | #' @return A `data.frame` of internal and external dependency usage, primarily 6 | #' derived from the "external_calls" data from \pkg{pkgstats}. 7 | #' @noRd 8 | pkgdeps_as_table <- function (checks) { 9 | 10 | deps <- checks$pkg$dependencies 11 | 12 | deps$package [deps$package == "NA"] <- NA_character_ 13 | 14 | index <- which (deps$type == "depends" & is.na (deps$package)) 15 | if (length (index) > 0L) { 16 | deps <- deps [-index, ] 17 | } 18 | 19 | re_exports <- get_re_exports (checks$pkg$path) 20 | if (length (re_exports) > 0L) { 21 | index <- which ( 22 | deps$package %in% re_exports$package & 23 | is.na (deps$ncalls) 24 | ) 25 | if (length (index) > 0L) { 26 | deps <- deps [-index, ] 27 | } 28 | } 29 | 30 | for (ty in unique (deps$type)) { 31 | index <- which (deps$type == ty) 32 | deps [index, ] <- 33 | deps [index, ] [order (deps$ncalls [index], decreasing = TRUE), ] 34 | } 35 | 36 | rownames (deps) <- NULL 37 | 38 | ext <- checks$pkg$external_calls 39 | index <- which (!names (ext) %in% deps$package) 40 | internal <- data.frame ( 41 | type = character (0L), 42 | package = character (0L), 43 | ncalls = integer (0L) 44 | ) 45 | if (length (index) > 0L) { 46 | internal <- data.frame ( 47 | type = "internal", 48 | package = names (ext [index]), 49 | ncalls = as.integer (ext [index]) 50 | ) 51 | } 52 | 53 | deps <- rbind (internal, deps) 54 | 55 | return (deps) 56 | } 57 | 58 | #' Get re-exported functions 59 | #' 60 | #' Note that import & export statements can span multiple lines, and 61 | #' `importFrom` can accept multiple fns. 62 | #' 63 | #' @param path Local path to package source 64 | #' @return A `data.frame` of [package, fn] detailing original source of all 65 | #' re-exported functions. 66 | #' @noRd 67 | get_re_exports <- function (path) { 68 | 69 | nmsp <- readLines (file.path (path, "NAMESPACE")) 70 | 71 | op <- grep ("\\(", nmsp) 72 | cl <- grep ("\\)", nmsp) 73 | if (length (op) != length (cl)) { 74 | stop ("'NAMESPACE' file is incorrectly formatted") 75 | } 76 | index <- cbind (rev (op), rev (cl)) 77 | if (any ((cl - op) > 1L)) { 78 | nmsp <- apply (index, 1, function (i) { 79 | paste0 (nmsp [seq (i [1], i [2])], collapse = "") 80 | }) 81 | } 82 | 83 | imports <- grep ("^importFrom", nmsp, value = TRUE) 84 | import_fns <- gsub ("^importFrom\\(|\\)$", "", imports) 85 | import_pkgs <- vapply (import_fns, function (i) { 86 | strsplit (i, ",") [[1]] [1] 87 | }, 88 | character (1), 89 | USE.NAMES = FALSE 90 | ) 91 | import_pkgs <- gsub ("\"", "", import_pkgs) 92 | import_fns <- lapply (strsplit (import_fns, ","), function (i) i [-1]) 93 | import_fns <- lapply (seq_along (import_pkgs), function (i) { 94 | cbind ( 95 | rep ( 96 | import_pkgs [[i]], 97 | length (import_fns [[i]]) 98 | ), 99 | import_fns [[i]] 100 | ) 101 | }) 102 | import_fns <- do.call (rbind, import_fns) 103 | import_fns <- data.frame ( 104 | package = import_fns [, 1], 105 | fn = gsub ("^\\s*|#.*$", "", import_fns [, 2]) 106 | ) 107 | 108 | exports <- grep ("^export\\(", nmsp, value = TRUE) 109 | exports <- gsub ("^export\\(|\\)", "", exports) 110 | exports <- unlist (strsplit (exports, ",")) 111 | exports <- gsub ("^\\s*|\\t", "", exports) 112 | exports <- gsub ("#.*$", "", exports) 113 | 114 | re_exports <- exports [which (exports %in% import_fns$fn)] 115 | re_exports <- import_fns [match (re_exports, import_fns$fn), ] 116 | 117 | return (re_exports) 118 | } 119 | 120 | 121 | #' Format package function tallies as extra details sub-sections 122 | #' 123 | #' @param checks Result of main \link{pkgcheck} function 124 | #' @return A character vector to add to markdown output. 125 | #' @noRd 126 | pkgfns_as_details <- function (checks) { 127 | 128 | fns <- checks$pkg$external_fns 129 | 130 | out <- lapply (seq_along (fns), function (i) { 131 | tallies <- paste0 ( 132 | names (fns [[i]]), 133 | " (", fns [[i]], ")" 134 | ) 135 | c ( 136 | "
", 137 | "", 138 | paste0 ( 139 | "", 140 | names (fns) [i], 141 | "" 142 | ), 143 | "", 144 | "

", 145 | paste0 (tallies, collapse = ", "), 146 | "

" 147 | ) 148 | }) 149 | 150 | c ( 151 | paste0 ( 152 | "Click below for tallies of functions used in each package. ", 153 | "Locations of each call within this package may be generated ", 154 | "locally by running 's <- pkgstats::pkgstats()', ", 155 | "and examining the 'external_calls' table." 156 | ), 157 | "", 158 | unlist (out) 159 | ) 160 | } 161 | -------------------------------------------------------------------------------- /R/pkgcheck-bg.R: -------------------------------------------------------------------------------- 1 | #' Generate report on package compliance with rOpenSci Statistical Software 2 | #' requirements as background process 3 | #' 4 | #' @param path Path to local repository 5 | #' @return A \pkg{processx} object connecting to the background process 6 | #' generating the main \link{pkgcheck} results (see Note). 7 | #' 8 | #' @note The return object will by default display whether it is still running, 9 | #' or whether it has finished. Once it has finished, the results can be obtained 10 | #' by calling `$get_result()`, or the main \link{pkgcheck} function can be 11 | #' called to quickly retrieve the main results from local cache. 12 | #' 13 | #' @note This function does not accept the `extra_env` parameter of the main 14 | #' \link{pkgcheck} function, and can not be used to run extra, locally-defined 15 | #' checks. 16 | #' @family pkgcheck_fns 17 | #' @export 18 | #' @examples 19 | #' \dontrun{ 20 | #' # Foreground checks as "blocking" process which will return 21 | #' # only after all checks have finished: 22 | #' checks <- pkgcheck ("/path/to/my/package") 23 | #' 24 | #' # Or run process in background, do other things in the meantime, 25 | #' # and obtain checks once they have finished: 26 | #' ps <- pkgcheck_bg ("/path/to/my/package") 27 | #' ps # print status to screen, same as 'ps$print()' 28 | #' # To examine process state while running: 29 | #' f <- ps$get_output_file () 30 | #' readLines (f) # or directly open file with local file viewer 31 | #' # ... ultimately wait until 'running' changes to 'finished', then: 32 | #' checks <- ps$get_result () 33 | #' } 34 | pkgcheck_bg <- function (path) { 35 | 36 | requireNamespace ("callr") 37 | 38 | logfiles <- logfile_names (path) 39 | stopfile <- gsub ( 40 | "\\_stdout$", "_stop", 41 | logfiles$stdout 42 | ) 43 | if (file.exists (stopfile)) { 44 | file.remove (stopfile) 45 | } 46 | 47 | e <- c (callr::rcmd_safe_env (), 48 | "PKGCHECK_PXBG_STOP" = stopfile, 49 | "PKGCHECK_BG" = TRUE, 50 | "PKGCHECK_CACHE_DIR" = Sys.getenv ("PKGCHECK_CACHE_DIR") 51 | ) 52 | 53 | Sys.setenv ("PKGCHECK_PXBG_STOP" = stopfile) 54 | 55 | callr::r_bg ( 56 | func = pkgcheck::pkgcheck, 57 | args = list (path = path), 58 | stdout = logfiles$stdout, 59 | stderr = logfiles$stderr, 60 | env = e, 61 | package = TRUE 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /R/pkgcheck-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | #' @importFrom magrittr %>% 3 | #' @aliases pkgcheck-package 4 | "_PACKAGE" 5 | 6 | # The following block is used by usethis to automatically manage 7 | # roxygen namespace tags. Modify with care! 8 | ## usethis namespace: start 9 | ## usethis namespace: end 10 | NULL 11 | -------------------------------------------------------------------------------- /R/read-books.R: -------------------------------------------------------------------------------- 1 | #' Browse packaging guidelines 2 | #' 3 | #' A convenience function to automatically open the web page of rOpenSci's 4 | #' "Package Development Guide" in the default browser. 5 | #' 6 | #' @param which Whether to read the released or "dev" development version. 7 | #' @return Nothing. Function called purely for side-effect of opening web page 8 | #' with package guidelines. 9 | #' 10 | #' @export 11 | #' @examples 12 | #' \dontrun{ 13 | #' read_pkg_guide () 14 | #' } 15 | read_pkg_guide <- function (which = c ("release", "dev")) { 16 | which <- match.arg (which [1], 17 | c ("release", "dev"), 18 | several.ok = FALSE 19 | ) 20 | 21 | utils::browseURL (sprintf ("%sbuilding.html", devguide_url (which))) 22 | } 23 | 24 | 25 | devguide_url <- function (which) { 26 | switch (which, 27 | release = "https://devguide.ropensci.org/", 28 | dev = "https://devdevguide.netlify.app/" 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /R/stats-checks.R: -------------------------------------------------------------------------------- 1 | #' Compare 'pkgstats' summary with statistics from all CRAN packages. 2 | #' @param s Result of `pkgstats::pkgstats_summary` 3 | #' @param threshold Proportion threshold below which to report on statistically 4 | #' unusual properties. 5 | #' @return A 'data.frame' of selected statistical properties and percentiles in 6 | #' relation to all other packages on CRAN. 7 | #' @noRd 8 | stats_checks <- function (s, threshold = 0.05) { 9 | 10 | # npars is set to NA when there are none; replace with 0: 11 | if (is.na (s$npars_exported_mn)) { 12 | s$npars_exported_mn <- 0L 13 | } 14 | if (is.na (s$npars_exported_md)) { 15 | s$npars_exported_md <- 0L 16 | } 17 | 18 | 19 | dat <- get_pkgstats_data () 20 | 21 | # convert blank line measures into relative 22 | b_s <- grep ("^blank\\_lines", names (s)) 23 | b_d <- grep ("^blank\\_lines", names (dat)) 24 | c_s <- grep ("^loc\\_", names (s)) # also includes loc_per_fn stats 25 | c_d <- grep ("^loc\\_", names (dat)) 26 | rel_white_pkg <- rel_white_all <- list () 27 | 28 | for (i in seq_along (b_s)) { 29 | 30 | # get directory name: 31 | nm <- gsub ("blank\\_lines\\_", "", names (s) [b_s [i]]) 32 | rel_white_pkg [[nm]] <- as.numeric (unname ( 33 | s [[b_s [i]]] / s [[c_s [i]]] 34 | )) 35 | tmp <- as.numeric (unname (dat [[b_d [i]]] / s [[c_d [i]]])) 36 | rel_white_all [[nm]] <- tmp [which (!is.na (tmp))] 37 | } 38 | 39 | index <- which (!is.na (rel_white_pkg)) 40 | rel_white_score <- vapply ( 41 | seq_along (rel_white_pkg), function (i) { 42 | s <- sort (rel_white_all [[i]]) 43 | length (which (s < rel_white_pkg [[i]])) / 44 | length (s) 45 | }, 46 | numeric (1) 47 | ) 48 | names (rel_white_score) <- names (rel_white_pkg) 49 | rel_white_pkg <- unlist (rel_white_pkg) 50 | 51 | # temporary fixes until data are re-generated: 52 | dat$loc_R [dat$files_R == 0] <- NA_integer_ 53 | dat$loc_src [dat$files_src == 0] <- NA_integer_ 54 | dat$loc_inst [dat$files_inst == 0] <- NA_integer_ 55 | dat$loc_vignettes [dat$files_vignettes == 0] <- NA_integer_ 56 | dat$loc_tests [dat$files_tests == 0] <- NA_integer_ 57 | 58 | nms <- names (dat) 59 | index <- which (vapply ( 60 | nms, function (i) { 61 | is.numeric (dat [[i]]) 62 | }, 63 | logical (1) 64 | )) 65 | nms <- nms [index] 66 | # ptn <- "^desc_n|^num\\_|^files\\_inst|^files\\_src" 67 | ptn <- "^desc_n" 68 | nms <- nms [which (!grepl (ptn, nms))] 69 | dists <- lapply (nms, function (i) { 70 | sort (dat [[i]] [which (!is.na (dat [[i]]))]) 71 | }) 72 | names (dists) <- nms 73 | 74 | pc <- vapply (nms, function (i) { 75 | if (is.na (s [[i]])) { 76 | return (NA) 77 | } 78 | return (length (which (dists [[i]] < s [[i]])) / 79 | length (dists [[i]])) 80 | }, double (1)) 81 | 82 | index <- match (names (pc), names (s)) 83 | pc <- data.frame ( 84 | measure = names (pc), 85 | value = as.numeric (s [1, index]), 86 | percentile = pc, 87 | row.names = NULL, 88 | stringsAsFactors = FALSE 89 | ) 90 | 91 | keep <- c ( 92 | grep ("^files\\_", pc$measure), 93 | grep ("^loc\\_", pc$measure), 94 | grep ("^data\\_", pc$measure), 95 | grep ("^num\\_vignettes$", pc$measure), 96 | grep ("^n\\_fns\\_", pc$measure), 97 | grep ("^npars\\_", pc$measure), 98 | grep ("^doclines\\_", pc$measure), 99 | grep ("^n\\_edges", pc$measure) 100 | ) 101 | pc <- pc [sort (unique (keep)), ] 102 | pc <- pc [which (!is.na (pc$percentile)), ] 103 | pc <- pc [which (!grepl ("^n\\_edges\\_", pc$measure)), ] 104 | 105 | # reduce to median estimates only 106 | pc <- pc [which (!grepl ("\\_mn$", pc$measure)), ] 107 | pc$measure <- gsub ("\\_md$", "", pc$measure) 108 | 109 | # add relative white space metrics 110 | index <- which (!is.na (rel_white_pkg)) 111 | measure <- paste0 ("rel_whitespace_", names (rel_white_pkg)) 112 | rel_white <- data.frame ( 113 | measure = measure, 114 | value = 100 * rel_white_pkg, 115 | percentile = rel_white_score, 116 | stringsAsFactors = FALSE 117 | ) [index, ] 118 | i <- max (grep ("^loc_", pc$measure)) 119 | pc <- rbind ( 120 | pc [seq (i), ], 121 | rel_white, 122 | pc [-seq (i), ] 123 | ) 124 | 125 | rownames (pc) <- NULL 126 | 127 | # additional tidying & removal: 128 | if (pc$percentile [pc$measure == "data_size_total"] == 0.0) { 129 | pc <- pc [which (!grepl ("^data\\_", pc$measure)), ] 130 | } 131 | if (pc$value [pc$measure == "files_inst"] == 0.0) { 132 | pc <- pc [which (pc$measure != "files_inst"), ] 133 | } 134 | # rm src if no src present 135 | index <- which (grepl ("\\_src$", pc$measure) & pc$value == 0) 136 | if (length (index) > 0) { 137 | pc <- pc [-index, ] 138 | } 139 | 140 | pc$noteworthy <- FALSE 141 | index <- which (pc$percentile < threshold | 142 | pc$percentile > (1 - threshold)) 143 | pc$noteworthy [index] <- TRUE 144 | 145 | # renames: 146 | pc$measure [pc$measure == "npars_exported"] <- 147 | "num_params_per_fn" 148 | pc$measure [pc$measure == "n_edges"] <- 149 | "fn_call_network_size" 150 | 151 | # language summary: 152 | loc <- pkgstats::loc_stats (fs::path (attr (s, "path"))) 153 | loc <- loc [which (loc$dir %in% c ("R", "inst", "src")), ] 154 | loc <- vapply (unique (loc$language), function (i) { 155 | c ( 156 | sum (loc$ncode [loc$language == i]) / 157 | sum (loc$ncode) * 100, 158 | sum (loc$nfiles [loc$language == i]) 159 | ) 160 | }, numeric (2)) 161 | langs <- paste0 (colnames (loc), ": ", round (loc [1, ]), "%") 162 | files <- paste0 (colnames (loc), ": ", as.integer (loc [2, ])) 163 | 164 | attr (pc, "language") <- langs 165 | attr (pc, "files") <- files 166 | 167 | return (pc) 168 | } 169 | 170 | get_pkgstats_data <- function () { 171 | 172 | cache_path <- Sys.getenv ("PKGCHECK_CACHE_DIR") 173 | f_name <- "pkgstats-CRAN-current.Rds" 174 | f_path <- fs::path_norm (fs::path (cache_path, f_name)) 175 | 176 | f_path <- dl_pkgstats_data (f_path) 177 | 178 | readRDS (f_path) 179 | } 180 | 181 | dl_pkgstats_data <- function (f_path) { 182 | 183 | # The cache_path is set to tempdir in tests, in which case static data must 184 | # be used in order to generate reproducible test snapshots (see #204). 185 | # Default is otherwise to use daily updates of data which then constnatly 186 | # change snapshot results. 187 | cache_path <- Sys.getenv ("PKGCHECK_CACHE_DIR") 188 | cache_is_temp <- identical ( 189 | normalizePath (dirname (cache_path)), 190 | normalizePath (tempdir ()) 191 | ) 192 | 193 | pkgstats_remote <- "https://github.com/ropensci-review-tools/pkgstats/" 194 | u_tag <- ifelse ( 195 | cache_is_temp, 196 | "v0.1.2", 197 | utils::getFromNamespace ("RELEASE_TAG", "pkgstats") 198 | ) 199 | u_base <- paste0 (pkgstats_remote, "releases/download/", u_tag, "/") 200 | f_name <- "pkgstats-CRAN-current.Rds" 201 | url <- paste0 (u_base, f_name) 202 | 203 | latest <- FALSE 204 | # Data are updated if older than: 205 | update_days <- 7L 206 | if (fs::file_exists (f_path)) { 207 | latest <- difftime ( 208 | Sys.Date (), 209 | as.Date (fs::file_info (f_path)$modification_time), 210 | units = "days" 211 | ) <= update_days 212 | } 213 | 214 | if (!latest) { 215 | req <- httr2::request (url) |> 216 | httr2::req_headers ("Accept" = "application/octet-stream") 217 | resp <- httr2::req_perform (req) 218 | 219 | if (httr2::resp_is_error (resp)) { 220 | return (NULL) 221 | } 222 | 223 | writeBin (httr2::resp_body_raw (resp), f_path) 224 | } 225 | 226 | return (f_path) 227 | } 228 | -------------------------------------------------------------------------------- /R/summarise-checks.R: -------------------------------------------------------------------------------- 1 | # IMPORTANT: All sub-functions with `summarise_` prefixes summarise the actual 2 | # checks, and include a return value specifying either "tick or cross", or just 3 | # "cross only." The latter denotes checks which only appear when they fail, 4 | # while the former appear in the summary list of green ticks required for a 5 | # package to pass all checks. 6 | # 7 | # Any additional checks added must also specify `@return` values as either "tick 8 | # or cross" (important checks which must be pased) or "cross only" (less 9 | # important checks which only appear when failed). 10 | 11 | #' Summarise main checklist items for editor report 12 | #' @param checks Result of main \link{pkgcheck} function 13 | #' @noRd 14 | summarise_all_checks <- function (checks) { 15 | 16 | pkg_env <- asNamespace ("pkgcheck") 17 | pkg_fns <- ls (pkg_env) 18 | 19 | output_fns <- gsub ( 20 | "^output\\_pkgchk\\_", "", 21 | grep ("^output\\_pkgchk\\_", pkg_fns, value = TRUE) 22 | ) 23 | 24 | has_gp <- "goodpractice" %in% names (checks) 25 | if (!has_gp) { 26 | output_fns <- output_fns [which (!grepl ("covr", output_fns))] 27 | } 28 | ordered_checks <- order_checks (output_fns) 29 | out <- lapply ( 30 | ordered_checks, 31 | function (i) summarise_check (checks, i, pkg_env) 32 | ) 33 | # "watch" checks; issue #144 34 | index <- which (ordered_checks %in% watch_checks (output_fns)) 35 | out [index] <- 36 | gsub ("\\:heavy\\_multiplication\\_x\\:", ":eyes:", out [index]) 37 | 38 | out <- do.call (c, out) 39 | 40 | out <- c (out, summarise_extra_env_checks (checks)) 41 | 42 | if (has_gp) { 43 | gp <- summarise_gp_checks (checks) 44 | 45 | out <- c ( 46 | out, 47 | gp$rcmd_errs, 48 | gp$rcmd_warns 49 | ) 50 | } 51 | 52 | # re-order "watch" checks to bottom 53 | index1 <- grep ("\\:heavy\\_(multiplication\\_x|check\\_mark)\\:", out) 54 | index2 <- grep ("\\:eyes\\:", out) 55 | out <- out [c (index1, index2)] 56 | 57 | checks_okay <- !any (grepl (symbol_crs (), out)) 58 | if (!checks_okay) { 59 | out <- c ( 60 | out, 61 | "", 62 | paste0 ( 63 | "**Important:** All failing checks above ", 64 | "must be addressed prior to proceeding" 65 | ) 66 | ) 67 | } 68 | 69 | if (any (grepl ("\\:eyes\\:", out))) { 70 | out <- c ( 71 | out, 72 | "", 73 | "(Checks marked with :eyes: may be optionally addressed.)", 74 | "" 75 | ) 76 | } 77 | 78 | attr (out, "checks_okay") <- checks_okay 79 | 80 | return (out) 81 | } 82 | 83 | summarise_extra_env_checks <- function (checks) { 84 | 85 | extra_env <- options ("pkgcheck_extra_env") [[1]] 86 | if (is.null (extra_env)) { 87 | return (NULL) 88 | } 89 | if (!is.list (extra_env)) { 90 | extra_env <- list (extra_env) 91 | } 92 | 93 | extra_chks <- lapply (extra_env, function (e) { 94 | e <- env2namespace (e) 95 | output_fns <- grep ("^output\\_pkgchk\\_", ls (e), value = TRUE) 96 | output_fns <- gsub ("^output\\_pkgchk\\_", "", output_fns) 97 | output_fns <- output_fns [which (output_fns %in% names (checks$checks))] 98 | vapply (output_fns, 99 | function (i) summarise_check (checks, i, e), 100 | character (1), 101 | USE.NAMES = FALSE 102 | ) 103 | }) 104 | 105 | return (unlist (extra_chks)) 106 | } 107 | 108 | #' Function to specify the order in which checks appear in the summary method. 109 | #' 110 | #' @param fns List of output functions with prefixes `output_pkgchk_`, for which 111 | #' order is to be established. 112 | #' @return Modified version of input list with functions ordered in specified 113 | #' sequence. 114 | #' @noRd 115 | order_checks <- function (fns) { 116 | 117 | ord <- c ( 118 | "pkgname", 119 | "license", 120 | "has_citation", 121 | "has_codemeta", 122 | "has_contrib", 123 | "fns_have_return_vals", 124 | "uses_roxygen2", 125 | "pkgdown", 126 | "has_url", 127 | "has_bugs", 128 | "has_vignette", 129 | "fns_have_exs", 130 | "global_assign", 131 | "ci", 132 | "covr", 133 | "has_scrap", 134 | "left_assign", 135 | "renv_activated", 136 | "branch_is_master", 137 | "srr_okay", 138 | "srr_missing", 139 | "srr_todo", 140 | "srr_most_in_one_file", 141 | # These are "watch" checks, not outright fails; they must be 142 | # additionally explicitly listed below in `watch_checks()`: 143 | "obsolete_pkg_deps", 144 | "unique_fn_names", 145 | "num_imports" 146 | ) 147 | 148 | fns <- fns [which (fns %in% ord)] 149 | ord <- ord [which (ord %in% fns)] # b/c 'covr' is removed w/o gp 150 | fns <- fns [match (ord, fns)] 151 | 152 | return (fns) 153 | } 154 | 155 | watch_checks <- function (output_fns) { 156 | 157 | all_checks <- order_checks (output_fns) 158 | watch_list <- c ( 159 | "obsolete_pkg_deps", 160 | "unique_fn_names", 161 | "num_imports" 162 | ) 163 | 164 | all_checks [which (all_checks %in% watch_list)] 165 | } 166 | 167 | #' Generic function to summarise checks based on result of corresponding 168 | #' `output_pkgchk_` function. 169 | #' 170 | #' @param checks Full result of `pkgcheck()` call 171 | #' @param what Name of check which must also correspond to an internal function 172 | #' named `output_pkgchk_`. 173 | #' @param pkg_env A namespace environment generated by `env2namespace`. 174 | #' @return Check formatted to apepar in `summary` method 175 | #' @noRd 176 | summarise_check <- function (checks, what, pkg_env) { 177 | 178 | pkg_fns <- ls (pkg_env) 179 | summary_fn <- paste0 ("output_pkgchk_", what) 180 | 181 | if (!summary_fn %in% pkg_fns) { 182 | return (NULL) 183 | } 184 | 185 | chk_summary <- do.call (summary_fn, list (checks), envir = pkg_env) 186 | 187 | res <- NULL 188 | 189 | if (sum (nchar (chk_summary$summary)) > 0L) { 190 | res <- paste0 ( 191 | "- ", 192 | ifelse (chk_summary$check_pass, 193 | symbol_tck (), 194 | symbol_crs () 195 | ), 196 | " ", 197 | chk_summary$summary 198 | ) 199 | } 200 | 201 | return (res) 202 | } 203 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' Tick symbol for markdown output 2 | #' @noRd 3 | symbol_tck <- function () { 4 | 5 | ":heavy_check_mark:" 6 | } 7 | 8 | #' Cross symbol for markdown output 9 | #' @noRd 10 | symbol_crs <- function () { 11 | 12 | ":heavy_multiplication_x:" 13 | } 14 | 15 | get_Rd_meta <- utils::getFromNamespace (".Rd_get_metadata", "tools") # nolint 16 | 17 | #' Decompose file paths into character vectors of named directories and final 18 | #' file names 19 | #' 20 | #' @param f One of more file paths with system-dependent file separators 21 | #' @return List of equivalent character vectors from which paths can be 22 | #' reconstructed with \link{file.path} 23 | #' @noRd 24 | decompose_path <- function (f) { 25 | 26 | # https://github.com/r-lib/fs/blob/4cc4b56c26b9d7f177a676fbb331133bb2584b86/R/path.R # nolint 27 | strsplit (f, "^(?=/)(?!//)|(?%"): 71 | imports <- grep ("^importFrom\\s?\\(", nspace, value = TRUE) 72 | imports <- vapply (imports, 73 | function (i) { 74 | gsub ("\\)$", "", strsplit (i, ",") [[1]] [2]) 75 | }, 76 | character (1), 77 | USE.NAMES = FALSE 78 | ) 79 | imports <- gsub ("\\\"", "", imports) 80 | 81 | return (exports [which (!exports %in% imports)]) 82 | } 83 | 84 | #' Convert anything that is not an environment into one. 85 | #' 86 | #' Used in `collate_extra_env_checks` to convert package names into namespace 87 | #' environments. 88 | #' @noRd 89 | env2namespace <- function (e) { 90 | 91 | if (!is.environment (e)) { 92 | 93 | s <- search () 94 | if (any (grepl (paste0 (e, "$"), s))) { 95 | e <- s [grep (paste0 (e, "$"), s)] [1] # hard-code to 1st value 96 | e <- gsub ("package\\:", "", e) 97 | } 98 | 99 | e <- tryCatch ( 100 | asNamespace (e), 101 | error = function (err) NULL 102 | ) 103 | } 104 | 105 | return (e) 106 | } 107 | 108 | #' Try to get available.packages() 109 | #' 110 | #' That function fails when no CRAN mirror is set, which is generally the case 111 | #' on GitHub runners, even if set as "repos" option. In those cases, this 112 | #' returns `NULL`. 113 | #' @noRd 114 | get_available_packages <- function () { 115 | op <- options () 116 | if (is.null (getOption ("repos"))) { 117 | # Needed for GitHub runners, because avail.pkgs fails with no mirror set 118 | options (repos = c (CRAN = "https://cloud.r-project.org")) 119 | } 120 | ap <- tryCatch ( 121 | data.frame (utils::available.packages (), stringsAsFactors = FALSE), 122 | error = function (e) NULL 123 | ) 124 | options (op) 125 | 126 | return (ap) 127 | } 128 | 129 | is_test_env <- function () { 130 | 131 | is_gha <- identical (Sys.getenv ("GITHUB_ACTIONS", ""), "true") 132 | test_dir <- identical ( 133 | Sys.getenv ("PKGCHECK_CACHE_DIR", getwd ()), 134 | file.path (tempdir (), "pkgcheck") 135 | ) 136 | 137 | return (is_gha && test_dir) 138 | } 139 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | # nocov start 2 | .onLoad <- function (libname, pkgname) { # nolint 3 | 4 | cache_dir <- Sys.getenv ("PKGCHECK_CACHE_DIR") 5 | 6 | if (cache_dir == "") { 7 | cache_dir <- fs::path_expand (fs::path ( 8 | rappdirs::user_cache_dir (), 9 | "R", 10 | "pkgcheck" 11 | )) 12 | Sys.setenv ("PKGCHECK_CACHE_DIR" = cache_dir) 13 | Sys.setenv ("PKGCHECK_CACHE_DIR_UNSET" = "true") 14 | } 15 | } 16 | 17 | .onUnload <- function (libname, pkgname) { # nolint 18 | 19 | if (Sys.getenv ("PKGCHECK_CACHE_DIR_UNSET") == "true") { 20 | 21 | Sys.unsetenv ("PKGCHECK_CACHE_DIR") 22 | Sys.unsetenv ("PKGCHECK_CACHE_DIR_UNSET") 23 | } 24 | } 25 | # nocov end 26 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://docs.ropensci.org/pkgcheck 2 | 3 | figures: 4 | dev: grDevices::png 5 | 6 | reference: 7 | - title: Main functions 8 | contents: 9 | - has_concept("pkgcheck_fns") 10 | - title: GitHub-related functions 11 | contents: 12 | - has_concept("github") 13 | - title: Additional functions 14 | contents: 15 | - has_concept("extra") 16 | - title: Guides 17 | contents: 18 | - read_pkg_guide 19 | -------------------------------------------------------------------------------- /data-raw/insert-yaml-in-use-action.R: -------------------------------------------------------------------------------- 1 | # Insert the yaml inputs from pkgcheck-actions into the example code for the 2 | # use_github_action_pkgcheck function. 3 | 4 | # Grab inputs from pkgcheck-action/action.yaml 5 | action_dir <- normalizePath (file.path (here::here (), "..", "pkgcheck-action")) 6 | if (!dir.exists (action_dir)) { 7 | stop ("Directory [", action_dir, "] not found.") 8 | } 9 | 10 | y <- readLines (file.path (action_dir, "action.yaml")) 11 | index <- grep ("^[[:alpha:]].*\\:$", y) 12 | index <- index [which (index >= grep ("^inputs", y))] 13 | inputs <- y [seq (index [1], index [2] - 1)] 14 | inputs <- inputs [nzchar (inputs)] 15 | 16 | f <- file.path (here::here (), "R", "github.R") 17 | r_fn <- readLines (f) 18 | i1 <- grep ("^#'\\s\\`\\`\\`yaml$", r_fn) 19 | i2 <- grep ("^#'\\s\\`\\`\\`$", r_fn) 20 | i2 <- i2 [which (i2 > i1)] [1] 21 | r_fn <- c ( 22 | r_fn [seq (i1)], 23 | paste0 ("#' ", inputs), 24 | r_fn [i2:length (r_fn)] 25 | ) 26 | r_fn <- gsub ("\\s+$", "", r_fn) 27 | writeLines (r_fn, f) 28 | 29 | devtools::document () 30 | -------------------------------------------------------------------------------- /data-raw/license-list.R: -------------------------------------------------------------------------------- 1 | # Extracts lists of CRAN-acceptable licenses (plus one additional CDDL 2 | # license from the OSI), and writes to R/license-list.R to use in license 3 | # checks. 4 | # This should never have to be re-run. See revision history of the file at 5 | # https://github.com/ropensci-review-tools/pkgcheck/issues/73#issuecomment-972846817 6 | 7 | u <- "https://svn.r-project.org/R/trunk/share/licenses/license.db" 8 | f <- file.path (tempdir (), "license.db") 9 | download.file (u, f) 10 | 11 | x <- readLines (f) 12 | 13 | index <- rep (0, length (x)) 14 | index [which (nchar (x) == 0L)] <- 1 15 | index <- cumsum (index) 16 | x <- lapply ( 17 | split (x, f = factor (index)), 18 | function (i) i [which (nchar (i) > 0L)] 19 | ) 20 | licenses <- vapply (x, function (i) { 21 | nm <- gsub ( 22 | "^Name:\\s?", "", 23 | grep ("^Name\\:", i, value = TRUE) 24 | ) 25 | ab <- gsub ( 26 | "^Abbrev:\\s?", "", 27 | grep ("^Abbrev\\:", i, value = TRUE) 28 | ) 29 | if (length (ab) == 0L) { 30 | ab <- NA_character_ 31 | } 32 | c (name = nm, abbrev = ab) 33 | }, 34 | character (2), 35 | USE.NAMES = FALSE 36 | ) 37 | licenses <- data.frame (t (licenses)) 38 | names (licenses) <- c ("name", "abbrev") 39 | index <- which (is.na (licenses$abbrev)) 40 | licenses$abbrev [index] <- licenses$name [index] 41 | licenses <- unique (licenses$abbrev) 42 | licenses <- paste0 (" \"", licenses, "\",") 43 | 44 | f <- c ( 45 | "# do not edit by hand; created by:", 46 | "# source ('./data-raw/license-list.r')", 47 | "#", 48 | "# rOpenSci recommendations:", 49 | "# https://devguide.ropensci.org/pkg_building.html#licence", 50 | "# links to cran list used to fill this license-list at", 51 | "# https://svn.r-project.org/R/trunk/share/licenses/license.db", 52 | "# plus OSI list which includes the extra CDDL-1.0 license.", 53 | "", 54 | "license_list <- function () {", 55 | " c (", 56 | licenses, 57 | " \"CDDL-1.0\"", 58 | " )", 59 | "}" 60 | ) 61 | 62 | here <- rprojroot::find_root (rprojroot::is_r_package) 63 | writeLines (f, file.path (here, "R", "license-list.R")) 64 | -------------------------------------------------------------------------------- /inst/pkgcheck.yaml: -------------------------------------------------------------------------------- 1 | name: pkgcheck 2 | 3 | # This will cancel running jobs once a new run is triggered 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.head_ref }} 6 | cancel-in-progress: true 7 | 8 | on: 9 | # Manually trigger the Action under Actions/pkgcheck 10 | workflow_dispatch: 11 | # Run on every push to main 12 | push: 13 | branches: 14 | - main 15 | 16 | jobs: 17 | pkgcheck: 18 | runs-on: ubuntu-latest 19 | permissions: 20 | issues: write 21 | steps: 22 | - uses: ropensci-review-tools/pkgcheck-action@main 23 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | LFILE = README 2 | YFILE = data-raw/insert-yaml-in-use-action 3 | VIGNETTE = environment 4 | 5 | #all: insert knith #open 6 | all: init readme vignette 7 | 8 | init: 9 | echo "pkgdown::init_site()" | R --no-save -q 10 | 11 | readme: 12 | echo "pkgdown::build_home(preview=FALSE,,quiet=FALSE)" | R --no-save -q 13 | 14 | vignette: 15 | echo "pkgdown::build_article('$(VIGNETTE)',quiet=FALSE)" | R --no-save -q 16 | 17 | 18 | knith: $(LFILE).Rmd 19 | echo "rmarkdown::render('$(LFILE).Rmd',output_file='$(LFILE).html')" | R --no-save -q 20 | 21 | knitr: $(LFILE).Rmd 22 | echo "rmarkdown::render('$(LFILE).Rmd',output_format=rmarkdown::md_document(variant='gfm'))" | R --no-save -q 23 | 24 | #open: $(LFILE).html 25 | # xdg-open $(LFILE).html & 26 | open: 27 | xdg-open docs/articles/$(VIGNETTE).html & 28 | 29 | check: 30 | Rscript -e 'library(pkgcheck); checks <- pkgcheck(); print(checks); summary (checks)' 31 | 32 | insert: $(YFILE).R 33 | Rscript $(YFILE).R 34 | 35 | clean: 36 | rm -rf *.html *.png README_cache 37 | -------------------------------------------------------------------------------- /man/checks_to_markdown.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/format-checks.R 3 | \name{checks_to_markdown} 4 | \alias{checks_to_markdown} 5 | \title{Convert checks to markdown-formatted report} 6 | \usage{ 7 | checks_to_markdown(checks, render = FALSE) 8 | } 9 | \arguments{ 10 | \item{checks}{Result of main \link{pkgcheck} function} 11 | 12 | \item{render}{If \code{TRUE}, render output as \code{html} document and open in 13 | browser.} 14 | } 15 | \value{ 16 | Markdown-formatted version of check report 17 | } 18 | \description{ 19 | Convert checks to markdown-formatted report 20 | } 21 | \examples{ 22 | \dontrun{ 23 | checks <- pkgcheck ("/path/to/my/package") 24 | md <- checks_to_markdown (checks) # markdown-formatted character vector 25 | md <- checks_to_markdown (checks, render = TRUE) # HTML version 26 | } 27 | } 28 | \seealso{ 29 | Other extra: 30 | \code{\link{list_pkgchecks}()}, 31 | \code{\link{logfile_names}()}, 32 | \code{\link{render_md2html}()} 33 | } 34 | \concept{extra} 35 | -------------------------------------------------------------------------------- /man/get_default_github_branch.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/github.R 3 | \name{get_default_github_branch} 4 | \alias{get_default_github_branch} 5 | \title{get_default_github_branch} 6 | \usage{ 7 | get_default_github_branch(org, repo) 8 | } 9 | \arguments{ 10 | \item{org}{Github organization} 11 | 12 | \item{repo}{Github repository} 13 | } 14 | \value{ 15 | Name of default branch on GitHub 16 | } 17 | \description{ 18 | get_default_github_branch 19 | } 20 | \note{ 21 | This function is not intended to be called directly, and is only 22 | exported to enable it to be used within the \pkg{plumber} API. 23 | } 24 | \examples{ 25 | \dontrun{ 26 | org <- "ropensci-review-tools" 27 | repo <- "pkgcheck" 28 | branch <- get_default_github_branch (org, repo) 29 | } 30 | } 31 | \seealso{ 32 | Other github: 33 | \code{\link{get_gh_token}()}, 34 | \code{\link{get_latest_commit}()}, 35 | \code{\link{use_github_action_pkgcheck}()} 36 | } 37 | \concept{github} 38 | -------------------------------------------------------------------------------- /man/get_gh_token.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/github.R 3 | \name{get_gh_token} 4 | \alias{get_gh_token} 5 | \title{Get GitHub token} 6 | \usage{ 7 | get_gh_token(token_name = "") 8 | } 9 | \arguments{ 10 | \item{token_name}{Optional name of token to use} 11 | } 12 | \value{ 13 | The value of the GitHub access token extracted from environment 14 | variables. 15 | } 16 | \description{ 17 | Get GitHub token 18 | } 19 | \examples{ 20 | \dontrun{ 21 | token <- get_gh_token () 22 | } 23 | } 24 | \seealso{ 25 | Other github: 26 | \code{\link{get_default_github_branch}()}, 27 | \code{\link{get_latest_commit}()}, 28 | \code{\link{use_github_action_pkgcheck}()} 29 | } 30 | \concept{github} 31 | -------------------------------------------------------------------------------- /man/get_latest_commit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/github.R 3 | \name{get_latest_commit} 4 | \alias{get_latest_commit} 5 | \title{get_latest_commit} 6 | \usage{ 7 | get_latest_commit(org, repo, branch = NULL) 8 | } 9 | \arguments{ 10 | \item{org}{Github organization} 11 | 12 | \item{repo}{Github repository} 13 | 14 | \item{branch}{Branch from which to get latest commit} 15 | } 16 | \value{ 17 | Details of latest commit including OID hash 18 | } 19 | \description{ 20 | get_latest_commit 21 | } 22 | \note{ 23 | This returns the latest commit from the default branch as specified on 24 | GitHub, which will not necessarily be the same as information returned from 25 | \code{gert::git_info} if the \code{HEAD} of a local repository does not point to the 26 | same default branch. 27 | } 28 | \examples{ 29 | \dontrun{ 30 | org <- "ropensci-review-tools" 31 | repo <- "pkgcheck" 32 | commit <- get_latest_commit (org, repo) 33 | } 34 | } 35 | \seealso{ 36 | Other github: 37 | \code{\link{get_default_github_branch}()}, 38 | \code{\link{get_gh_token}()}, 39 | \code{\link{use_github_action_pkgcheck}()} 40 | } 41 | \concept{github} 42 | -------------------------------------------------------------------------------- /man/list_pkgchecks.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{list_pkgchecks} 4 | \alias{list_pkgchecks} 5 | \title{List all checks currently implemented} 6 | \usage{ 7 | list_pkgchecks(quiet = FALSE) 8 | } 9 | \arguments{ 10 | \item{quiet}{If \code{TRUE}, print all checks to screen. Function invisibly 11 | returns list of checks regardless.} 12 | } 13 | \value{ 14 | Character vector of names of all checks (invisibly) 15 | } 16 | \description{ 17 | List all checks currently implemented 18 | } 19 | \examples{ 20 | list_pkgchecks () 21 | } 22 | \seealso{ 23 | Other extra: 24 | \code{\link{checks_to_markdown}()}, 25 | \code{\link{logfile_names}()}, 26 | \code{\link{render_md2html}()} 27 | } 28 | \concept{extra} 29 | -------------------------------------------------------------------------------- /man/logfile_names.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cache.R 3 | \name{logfile_names} 4 | \alias{logfile_names} 5 | \title{Set up stdout & stderr cache files for \code{r_bg} process} 6 | \usage{ 7 | logfile_names(path) 8 | } 9 | \arguments{ 10 | \item{path}{Path to local repository} 11 | } 12 | \value{ 13 | Vector of two strings holding respective local paths to \code{stdout} and 14 | \code{stderr} files for \code{r_bg} process controlling the main \link{pkgcheck} 15 | function when executed in background mode. 16 | } 17 | \description{ 18 | Set up stdout & stderr cache files for \code{r_bg} process 19 | } 20 | \note{ 21 | These files are needed for the \pkg{callr} \code{r_bg} process which 22 | controls the main \link{pkgcheck}. The \code{stdout} and \code{stderr} pipes from the 23 | process are stored in the cache directory so they can be inspected via their 24 | own distinct endpoint calls. 25 | } 26 | \examples{ 27 | \dontrun{ 28 | logfiles <- logfiles_namnes ("/path/to/my/package") 29 | print (logfiles) 30 | } 31 | } 32 | \seealso{ 33 | Other extra: 34 | \code{\link{checks_to_markdown}()}, 35 | \code{\link{list_pkgchecks}()}, 36 | \code{\link{render_md2html}()} 37 | } 38 | \concept{extra} 39 | -------------------------------------------------------------------------------- /man/pkgcheck-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pkgcheck-package.R 3 | \docType{package} 4 | \name{pkgcheck-package} 5 | \alias{pkgcheck-package} 6 | \title{pkgcheck: rOpenSci Package Checks} 7 | \description{ 8 | Check whether a package is ready for submission to rOpenSci's peer review system. 9 | } 10 | \seealso{ 11 | Useful links: 12 | \itemize{ 13 | \item \url{https://docs.ropensci.org/pkgcheck/} 14 | \item \url{https://github.com/ropensci-review-tools/pkgcheck} 15 | \item Report bugs at \url{https://github.com/ropensci-review-tools/pkgcheck/issues} 16 | } 17 | 18 | } 19 | \author{ 20 | \strong{Maintainer}: Mark Padgham \email{mark.padgham@email.com} (\href{https://orcid.org/0000-0003-2172-5265}{ORCID}) 21 | 22 | Authors: 23 | \itemize{ 24 | \item Maëlle Salmon 25 | \item Jacob Wujciak-Jens \email{jacob@wujciak.de} (\href{https://orcid.org/0000-0002-7281-3989}{ORCID}) 26 | } 27 | 28 | } 29 | \keyword{internal} 30 | -------------------------------------------------------------------------------- /man/pkgcheck.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pkgcheck-fn.R 3 | \name{pkgcheck} 4 | \alias{pkgcheck} 5 | \title{Generate report on package compliance with rOpenSci Statistical Software 6 | requirements} 7 | \usage{ 8 | pkgcheck( 9 | path = ".", 10 | goodpractice = TRUE, 11 | use_cache = TRUE, 12 | extra_env = .GlobalEnv 13 | ) 14 | } 15 | \arguments{ 16 | \item{path}{Path to local repository} 17 | 18 | \item{goodpractice}{If \code{FALSE}, skip goodpractice checks. May be useful in 19 | development stages to more quickly check other aspects.} 20 | 21 | \item{use_cache}{Checks are cached for rapid retrieval, and only re-run if 22 | the git hash of the local repository changes. Setting \code{use_cache} to \code{FALSE} 23 | will force checks to be re-run even if the git hash has not changed.} 24 | 25 | \item{extra_env}{Additional environments from which to collate checks. Other 26 | package names may be appended using \code{c}, as in \code{c(.GlobalEnv, "mypkg")}.} 27 | } 28 | \value{ 29 | A \code{pkgcheck} object detailing all package assessments automatically 30 | applied to packages submitted for peer review. 31 | } 32 | \description{ 33 | Generate report on package compliance with rOpenSci Statistical Software 34 | requirements 35 | } 36 | \examples{ 37 | \dontrun{ 38 | checks <- pkgcheck ("/path/to/my/package") # default full check 39 | summary (checks) 40 | # Or to run only checks implemented in 'pkgcheck' and not the 41 | # additional \pkg{goodpractice} checks: 42 | checks <- pkgcheck ("/path/to/my/package", goodpractice = FALSE) 43 | summary (checks) 44 | } 45 | } 46 | \seealso{ 47 | Other pkgcheck_fns: 48 | \code{\link{pkgcheck_bg}()}, 49 | \code{\link{print.pkgcheck}()} 50 | } 51 | \concept{pkgcheck_fns} 52 | -------------------------------------------------------------------------------- /man/pkgcheck_bg.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pkgcheck-bg.R 3 | \name{pkgcheck_bg} 4 | \alias{pkgcheck_bg} 5 | \title{Generate report on package compliance with rOpenSci Statistical Software 6 | requirements as background process} 7 | \usage{ 8 | pkgcheck_bg(path) 9 | } 10 | \arguments{ 11 | \item{path}{Path to local repository} 12 | } 13 | \value{ 14 | A \pkg{processx} object connecting to the background process 15 | generating the main \link{pkgcheck} results (see Note). 16 | } 17 | \description{ 18 | Generate report on package compliance with rOpenSci Statistical Software 19 | requirements as background process 20 | } 21 | \note{ 22 | The return object will by default display whether it is still running, 23 | or whether it has finished. Once it has finished, the results can be obtained 24 | by calling \verb{$get_result()}, or the main \link{pkgcheck} function can be 25 | called to quickly retrieve the main results from local cache. 26 | 27 | This function does not accept the \code{extra_env} parameter of the main 28 | \link{pkgcheck} function, and can not be used to run extra, locally-defined 29 | checks. 30 | } 31 | \examples{ 32 | \dontrun{ 33 | # Foreground checks as "blocking" process which will return 34 | # only after all checks have finished: 35 | checks <- pkgcheck ("/path/to/my/package") 36 | 37 | # Or run process in background, do other things in the meantime, 38 | # and obtain checks once they have finished: 39 | ps <- pkgcheck_bg ("/path/to/my/package") 40 | ps # print status to screen, same as 'ps$print()' 41 | # To examine process state while running: 42 | f <- ps$get_output_file () 43 | readLines (f) # or directly open file with local file viewer 44 | # ... ultimately wait until 'running' changes to 'finished', then: 45 | checks <- ps$get_result () 46 | } 47 | } 48 | \seealso{ 49 | Other pkgcheck_fns: 50 | \code{\link{pkgcheck}()}, 51 | \code{\link{print.pkgcheck}()} 52 | } 53 | \concept{pkgcheck_fns} 54 | -------------------------------------------------------------------------------- /man/print.pkgcheck.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pkgcheck-methods.R 3 | \name{print.pkgcheck} 4 | \alias{print.pkgcheck} 5 | \title{Generic print method for 'pkgcheck' objects.} 6 | \usage{ 7 | \method{print}{pkgcheck}(x, deps = FALSE, ...) 8 | } 9 | \arguments{ 10 | \item{x}{A 'pkgcheck' object to be printed.} 11 | 12 | \item{deps}{If 'TRUE', include details of dependency packages and function 13 | usage.} 14 | 15 | \item{...}{Further arguments pass to or from other methods (not used here).} 16 | } 17 | \value{ 18 | Nothing. Method called purely for side-effect of printing to screen. 19 | } 20 | \description{ 21 | Generic print method for 'pkgcheck' objects. 22 | } 23 | \examples{ 24 | \dontrun{ 25 | checks <- pkgcheck ("/path/to/my/package") 26 | print (checks) # print full checks, starting with summary 27 | summary (checks) # print summary only 28 | } 29 | } 30 | \seealso{ 31 | Other pkgcheck_fns: 32 | \code{\link{pkgcheck}()}, 33 | \code{\link{pkgcheck_bg}()} 34 | } 35 | \concept{pkgcheck_fns} 36 | -------------------------------------------------------------------------------- /man/read_pkg_guide.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/read-books.R 3 | \name{read_pkg_guide} 4 | \alias{read_pkg_guide} 5 | \title{Browse packaging guidelines} 6 | \usage{ 7 | read_pkg_guide(which = c("release", "dev")) 8 | } 9 | \arguments{ 10 | \item{which}{Whether to read the released or "dev" development version.} 11 | } 12 | \value{ 13 | Nothing. Function called purely for side-effect of opening web page 14 | with package guidelines. 15 | } 16 | \description{ 17 | A convenience function to automatically open the web page of rOpenSci's 18 | "Package Development Guide" in the default browser. 19 | } 20 | \examples{ 21 | \dontrun{ 22 | read_pkg_guide () 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /man/render_md2html.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/format-checks.R 3 | \name{render_md2html} 4 | \alias{render_md2html} 5 | \title{render markdown-formatted input into 'html'} 6 | \usage{ 7 | render_md2html(md, open = TRUE) 8 | } 9 | \arguments{ 10 | \item{md}{Result of \link{checks_to_markdown} function.} 11 | 12 | \item{open}{If \code{TRUE}, open \code{hmtl}-rendered version in web browser.} 13 | } 14 | \value{ 15 | (invisible) Location of \code{.html}-formatted version of input. 16 | } 17 | \description{ 18 | render markdown-formatted input into 'html' 19 | } 20 | \examples{ 21 | \dontrun{ 22 | checks <- pkgcheck ("/path/to/my/package") 23 | # Generate standard markdown-formatted character vector: 24 | md <- checks_to_markdown (checks) 25 | 26 | # Directly generate HTML output: 27 | h <- checks_to_markdown (checks, render = TRUE) # HTML version 28 | 29 | # Or convert markdown-formatted version to HTML: 30 | h <- render_md2html (md) 31 | } 32 | } 33 | \seealso{ 34 | Other extra: 35 | \code{\link{checks_to_markdown}()}, 36 | \code{\link{list_pkgchecks}()}, 37 | \code{\link{logfile_names}()} 38 | } 39 | \concept{extra} 40 | -------------------------------------------------------------------------------- /man/use_github_action_pkgcheck.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/github.R 3 | \name{use_github_action_pkgcheck} 4 | \alias{use_github_action_pkgcheck} 5 | \title{Use pkgcheck Github Action} 6 | \usage{ 7 | use_github_action_pkgcheck( 8 | dir = ".github/workflows", 9 | overwrite = FALSE, 10 | file_name = "pkgcheck.yaml", 11 | branch = gert::git_branch(), 12 | inputs = NULL 13 | ) 14 | } 15 | \arguments{ 16 | \item{dir}{Directory the file is written to.} 17 | 18 | \item{overwrite}{Overwrite existing file?} 19 | 20 | \item{file_name}{Name of the workflow file.} 21 | 22 | \item{branch}{Name of git branch for checks to run; defaults to currently 23 | active branch.} 24 | 25 | \item{inputs}{Named list of inputs to the 26 | \code{ropensci-review-tools/pkgcheck-action}. See details below.} 27 | } 28 | \value{ 29 | The path to the new file, invisibly. 30 | } 31 | \description{ 32 | Creates a Github workflow file in \code{dir} integrate \code{\link[=pkgcheck]{pkgcheck()}} into your CI. 33 | } 34 | \details{ 35 | For more information on the action and advanced usage visit the 36 | action 37 | \href{https://github.com/ropensci-review-tools/pkgcheck-action}{repository}. 38 | } 39 | \section{Inputs}{ 40 | 41 | Inputs with description and default values. Pass all values as strings, see 42 | examples. 43 | 44 | \if{html}{\out{
}}\preformatted{inputs: 45 | ref: 46 | description: "The ref to checkout and check. Set to empty string to skip checkout." 47 | default: "$\{\{ github.ref \}\}" 48 | required: true 49 | post-to-issue: 50 | description: "Should the pkgcheck results be posted as an issue?" 51 | # If you use the 'pull_request' trigger and the PR is from outside the repo 52 | # (e.g. a fork), the job will fail due to permission issues 53 | # if this is set to 'true'. The default will prevent this. 54 | default: $\{\{ github.event_name != 'pull_request' \}\} 55 | required: true 56 | issue-title: 57 | description: "Name for the issue containing the pkgcheck results. Will be created or updated." 58 | # This will create a new issue for every branch, set it to something fixed 59 | # to only create one issue that is updated via edits. 60 | default: "pkgcheck results - $\{\{ github.ref_name \}\}" 61 | required: true 62 | summary-only: 63 | description: "Only post the check summary to issue. Set to false to get the full results in the issue." 64 | default: true 65 | required: true 66 | append-to-issue: 67 | description: "Should issue results be appended to existing issue, or posted in new issues." 68 | default: true 69 | required: true 70 | }\if{html}{\out{
}} 71 | } 72 | 73 | \examples{ 74 | \dontrun{ 75 | use_github_action_pkgcheck (inputs = list (`post-to-issue` = "false")) 76 | use_github_action_pkgcheck (branch = "main") 77 | } 78 | } 79 | \seealso{ 80 | Other github: 81 | \code{\link{get_default_github_branch}()}, 82 | \code{\link{get_gh_token}()}, 83 | \code{\link{get_latest_commit}()} 84 | } 85 | \concept{github} 86 | -------------------------------------------------------------------------------- /pkgcheck.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 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 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library (testthat) 2 | library (pkgcheck) 3 | library (visNetwork) 4 | 5 | options (repos = c ( 6 | ropenscireviewtools = "https://ropensci-review-tools.r-universe.dev", 7 | CRAN = "https://cloud.r-project.org" 8 | )) 9 | 10 | test_check ("pkgcheck") 11 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/extra-checks/checks-extra.md: -------------------------------------------------------------------------------- 1 | ## Checks for [pkgstats (v9.9)](https://github.com/ropensci-review-tools/pkgstats) 2 | 3 | git hash: [](https://github.com/ropensci-review-tools/pkgstats/tree/) 4 | 5 | - :heavy_check_mark: Package name is available 6 | - :heavy_multiplication_x: does not have a 'codemeta.json' file. 7 | - :heavy_multiplication_x: does not have a 'contributing' file. 8 | - :heavy_multiplication_x: The following functions have no documented return values: [ctags_install, desc_stats, rd_stats, tags_data] 9 | - :heavy_check_mark: uses 'roxygen2'. 10 | - :heavy_check_mark: 'DESCRIPTION' has a URL field. 11 | - :heavy_check_mark: 'DESCRIPTION' has a BugReports field. 12 | - :heavy_multiplication_x: Package has no HTML vignettes 13 | - :heavy_multiplication_x: These functions do not have examples: [pkgstats_from_archive]. 14 | - :heavy_multiplication_x: Package has no continuous integration checks. 15 | - :heavy_multiplication_x: Package contains unexpected files. 16 | - :heavy_multiplication_x: Default GitHub branch of 'master' is not acceptable. 17 | - :heavy_check_mark: This is a statistical package which complies with all applicable standards 18 | - :eyes: Package depends on the following obsolete packages: [blah,sp] 19 | 20 | **Important:** All failing checks above must be addressed prior to proceeding 21 | 22 | (Checks marked with :eyes: may be optionally addressed.) 23 | 24 | 25 | Package License: GPL-3 26 | 27 | --- 28 | 29 | ### 1. rOpenSci Statistical Standards ([`srr` package](https://github.com/ropensci-review-tools/srr)) 30 | 31 | 32 | 33 | :heavy_check_mark: srr message 34 | 35 | 36 | 37 | --- 38 | 39 | 40 | ### 2. Package Dependencies 41 | 42 |
43 | Details of Package Dependency Usage (click to open) 44 |

45 | 46 | The table below tallies all function calls to all packages ('ncalls'), both internal (r-base + recommended, along with the package itself), and external (imported and suggested packages). 'NA' values indicate packages to which no identified calls to R functions could be found. Note that these results are generated by an automated code-tagging system which may not be entirely accurate. 47 | 48 | |type |package | ncalls| 49 | |:----------|:----------|------:| 50 | |internal |base | 447| 51 | |internal |pkgstats | 99| 52 | |internal |stats | 16| 53 | |internal |graphics | 10| 54 | |internal |utils | 10| 55 | |internal |tools | 2| 56 | |imports |sys | 13| 57 | |imports |readr | 8| 58 | |imports |brio | 7| 59 | |imports |dplyr | 7| 60 | |imports |withr | 5| 61 | |imports |fs | 4| 62 | |imports |igraph | 3| 63 | |imports |checkmate | NA| 64 | |imports |methods | NA| 65 | |suggests |visNetwork | 3| 66 | |suggests |hms | 1| 67 | |suggests |pbapply | 1| 68 | |suggests |knitr | NA| 69 | |suggests |pkgbuild | NA| 70 | |suggests |Rcpp | NA| 71 | |suggests |rmarkdown | NA| 72 | |suggests |roxygen2 | NA| 73 | |suggests |testthat | NA| 74 | |linking_to |cpp11 | NA| 75 | 76 | Click below for tallies of functions used in each package. Locations of each call within this package may be generated locally by running 's <- pkgstats::pkgstats()', and examining the 'external_calls' table. 77 | 78 | 79 | 80 | **NOTE:** Some imported packages appear to have no associated function calls; please ensure with author that these 'Imports' are listed appropriately. 81 | 82 | 83 |

84 | 85 | --- 86 | 87 | 88 | ### 3. Statistical Properties 89 | 90 | This package features some noteworthy statistical properties which may need to be clarified by a handling editor prior to progressing. 91 | 92 |
93 | Details of statistical properties (click to open) 94 |

95 | 96 | The package has: 97 | 98 | - code in C++ (9% in 3 files) and R (91% in 19 files) 99 | - 1 authors 100 | - no vignette 101 | - no internal data file 102 | - 9 imported packages 103 | - 11 exported functions (median 43 lines of code) 104 | - 120 non-exported functions in R (median 21 lines of code) 105 | - 12 R functions (median 16 lines of code) 106 | 107 | --- 108 | 109 | Statistical properties of package structure as distributional percentiles in relation to all current CRAN packages 110 | The following terminology is used: 111 | 112 | - `loc` = "Lines of Code" 113 | - `fn` = "function" 114 | - `exp`/`not_exp` = exported / not exported 115 | 116 | All parameters are explained as tooltips in the locally-rendered HTML version of this report generated by [the `checks_to_markdown()` function](https://docs.ropensci.org/pkgcheck/reference/checks_to_markdown.html) 117 | 118 | 119 | The final measure (`fn_call_network_size`) is the total number of calls between functions (in R), or more abstract relationships between code objects in other languages. Values are flagged as "noteworthy" when they lie in the upper or lower 5th percentile. 120 | 121 | |measure | value| percentile|noteworthy | 122 | |:-----------------------|-----:|----------:|:----------| 123 | |files_R | 19| 79.5| | 124 | |files_src | 3| 85.4| | 125 | |files_vignettes | 0| 0.0|TRUE | 126 | |files_tests | 7| 85.6| | 127 | |loc_R | 2698| 88.7| | 128 | |loc_src | 277| 34.4| | 129 | |loc_tests | 266| 61.0| | 130 | |num_vignettes | 0| 0.0|TRUE | 131 | |n_fns_r | 131| 82.3| | 132 | |n_fns_r_exported | 11| 49.4| | 133 | |n_fns_r_not_exported | 120| 86.6| | 134 | |n_fns_src | 12| 42.5| | 135 | |n_fns_per_file_r | 4| 59.9| | 136 | |n_fns_per_file_src | 4| 47.5| | 137 | |num_params_per_fn | 1| 1.7|TRUE | 138 | |loc_per_fn_r | 23| 66.5| | 139 | |loc_per_fn_r_exp | 43| 75.4| | 140 | |loc_per_fn_r_not_exp | 22| 67.4| | 141 | |loc_per_fn_src | 16| 56.9| | 142 | |rel_whitespace_R | 19| 88.6| | 143 | |rel_whitespace_src | 24| 44.2| | 144 | |rel_whitespace_tests | 27| 64.5| | 145 | |doclines_per_fn_exp | 31| 36.1| | 146 | |doclines_per_fn_not_exp | 0| 0.0|TRUE | 147 | |fn_call_network_size | 111| 80.8| | 148 | 149 | --- 150 | 151 |

152 | 153 | 154 | ### 3a. Network visualisation 155 | 156 | An interactive visualisation of calls between objects in the package has been uploaded as a workflow artefact. To view it, click on results from the [latest 'pkgcheck' action](network.html), scroll to the bottom, and click on the 'visual-network' artefact. 157 | 158 | --- 159 | 160 | ### 4. `goodpractice` and other checks 161 | 162 | ('goodpractice' not included with these checks) 163 | 164 | --- 165 | 166 | ### 5. Other Checks 167 | 168 |
169 | Details of other checks (click to open) 170 |

171 | 172 | 173 | :heavy_multiplication_x: Package contains the following unexpected files: 174 | 175 | - a 176 | - b 177 | 178 | 179 | :heavy_multiplication_x: Package contains the following (potentially) obsolete packages: 180 | 181 | - blah 182 | - sp 183 | - rgdal 184 | 185 | 186 | See our [Recommended Scaffolding](https://devguide.ropensci.org/building.html?q=scaffol#recommended-scaffolding) for alternatives. 187 | 188 | 189 |

190 |
191 | 192 | 193 | --- 194 | 195 |
196 | Package Versions 197 |

198 | 199 | |package |version | 200 | |:--------|:------| 201 | |pkgstats |42 | 202 | |pkgcheck |42 | 203 | 204 |

205 |
206 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/extra-checks/checks-print.md: -------------------------------------------------------------------------------- 1 | 2 | -- pkgstats 9.9 ---------------------------------------------------------------- 3 | 4 | v Package name is available 5 | x does not have a 'codemeta.json' file. 6 | x does not have a 'contributing' file. 7 | x The following functions have no documented return values: [ctags_install, desc_stats, rd_stats, tags_data] 8 | v uses 'roxygen2'. 9 | v 'DESCRIPTION' has a URL field. 10 | v 'DESCRIPTION' has a BugReports field. 11 | x Package has no HTML vignettes 12 | x These functions do not have examples: [pkgstats_from_archive]. 13 | x Package has no continuous integration checks. 14 | x Package contains unexpected files. 15 | x Default GitHub branch of 'master' is not acceptable. 16 | v This is a statistical package which complies with all applicable standards 17 | i Package depends on the following obsolete packages: [blah,sp] 18 | 19 | i Current status: 20 | x This package is not ready to be submitted. 21 | 22 | 23 | -- git -- 24 | 25 | * HEAD: 26 | * Default branch: 27 | * Number of commits: 28 | * First commit: 29 | * Number of authors: 30 | 31 | 32 | -- rOpenSci Statistical Standards -- 33 | 34 | i The package is in the following 0 categories: 35 | 36 | i Compliance with rOpenSci statistical standards: 37 | v srr message 38 | i 'srr' report is at []. 39 | 40 | 41 | -- Package Structure -- 42 | 43 | i Package uses the following languages: 44 | * C++: 9% 45 | * R: 91% 46 | 47 | i Package has 48 | * 1 author. 49 | * 0 vignettes. 50 | * No internal data 51 | * 9 imported packages. 52 | * 11 exported functions (median 43 lines of code). 53 | * 120 non-exported functions (median 21 lines of code). 54 | * 12 C++ functions (median 16 lines of code). 55 | * 1 parameters per function (median). 56 | 57 | -- Package statistics -- 58 | 59 | 60 | i Package network diagram is not here. 61 | 62 | 63 | -- goodpractice -- 64 | 65 | i 'goodpractice' not included with these checks 66 | 67 | -- Other checks -- 68 | 69 | x Package contains the following unexpected files: 70 | * a 71 | * b 72 | x Package contains the following (potentially) obsolete packages: 73 | * blah 74 | * sp 75 | * rgdal 76 | 77 | See our [Recommended 78 | Scaffolding](https://devguide.ropensci.org/building.html?q=scaffol#recommended-scaffolding) 79 | for alternatives. 80 | 81 | -- Package Versions -- 82 | 83 | pkgstats: 42 84 | pkgcheck: 42 85 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/github.md: -------------------------------------------------------------------------------- 1 | # use_github_action_pkgcheck 2 | 3 | `file_name` must be a character argument 4 | 5 | --- 6 | 7 | `file_name` must be a single value 8 | 9 | --- 10 | 11 | `inputs` must be a named list! 12 | 13 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/github/pkgcheck.yaml: -------------------------------------------------------------------------------- 1 | name: pkgcheck 2 | 3 | # This will cancel running jobs once a new run is triggered 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.head_ref }} 6 | cancel-in-progress: true 7 | 8 | on: 9 | # Manually trigger the Action under Actions/pkgcheck 10 | workflow_dispatch: 11 | # Run on every push to main 12 | push: 13 | branches: 14 | - main 15 | 16 | jobs: 17 | pkgcheck: 18 | runs-on: ubuntu-latest 19 | permissions: 20 | issues: write 21 | steps: 22 | - uses: ropensci-review-tools/pkgcheck-action@main 23 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/github/with_inputs.yaml: -------------------------------------------------------------------------------- 1 | name: pkgcheck 2 | 3 | # This will cancel running jobs once a new run is triggered 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.head_ref }} 6 | cancel-in-progress: true 7 | 8 | on: 9 | # Manually trigger the Action under Actions/pkgcheck 10 | workflow_dispatch: 11 | # Run on every push to main 12 | push: 13 | branches: 14 | - main 15 | 16 | jobs: 17 | pkgcheck: 18 | runs-on: ubuntu-latest 19 | permissions: 20 | issues: write 21 | steps: 22 | - uses: ropensci-review-tools/pkgcheck-action@main 23 | with: 24 | post-to-issue: true 25 | summary-only: false 26 | ref: main 27 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/pkgcheck/checks.md: -------------------------------------------------------------------------------- 1 | ## Checks for [testpkgchecknotapkg (v0.0.0.9000)]() 2 | 3 | git hash: [](/tree/) 4 | 5 | - :heavy_check_mark: Package name is available 6 | - :heavy_multiplication_x: does not have a 'codemeta.json' file. 7 | - :heavy_multiplication_x: does not have a 'contributing' file. 8 | - :heavy_multiplication_x: The following function has no documented return value: [test_fn] 9 | - :heavy_check_mark: uses 'roxygen2'. 10 | - :heavy_multiplication_x: 'DESCRIPTION' does not have a URL field. 11 | - :heavy_multiplication_x: 'DESCRIPTION' does not have a BugReports field. 12 | - :heavy_multiplication_x: Package has no HTML vignettes 13 | - :heavy_multiplication_x: These functions do not have examples: [test_fn]. 14 | - :heavy_multiplication_x: Continuous integration checks unavailable (no URL in 'DESCRIPTION'). 15 | - :heavy_multiplication_x: Default GitHub branch of 'master' is not acceptable. 16 | - :heavy_multiplication_x: Some statistical standards are missing 17 | - :heavy_multiplication_x: This package still has TODO standards and can not be submitted 18 | - :heavy_multiplication_x: Statistical standards should be documented in most package files, yet are mostly only documented in one file. 19 | - :eyes: Package has unusually large number of 21 Imports (> 98% of all packages) 20 | 21 | **Important:** All failing checks above must be addressed prior to proceeding 22 | 23 | (Checks marked with :eyes: may be optionally addressed.) 24 | 25 | 26 | Package License: GPL-3 27 | 28 | --- 29 | 30 | ### 1. rOpenSci Statistical Standards ([`srr` package](https://github.com/ropensci-review-tools/srr)) 31 | 32 | This package is in the following category: 33 | 34 | - *Regression and Supervised Learning* 35 | 36 | :heavy_multiplication_x: This package still has TODO standards and can not be submitted 37 | Package can not be submitted because the following standards [v0.2.0] are missing from your code: 38 | 39 | G1.0 40 | G1.4a 41 | G1.6 42 | G2.0a 43 | G2.1a 44 | G2.2 45 | G2.3a 46 | G2.3b 47 | G2.4 48 | G2.4a 49 | G2.4b 50 | G2.4c 51 | G2.4d 52 | G2.4e 53 | G2.5 54 | G2.6 55 | G2.7 56 | G2.8 57 | G2.9 58 | G2.10 59 | G2.11 60 | G2.12 61 | G2.13 62 | G2.14 63 | G2.14a 64 | G2.14b 65 | G2.14c 66 | G2.15 67 | G2.16 68 | G3.0 69 | G3.1 70 | G3.1a 71 | G4.0 72 | G5.0 73 | G5.1 74 | G5.2 75 | G5.2a 76 | G5.2b 77 | G5.3 78 | G5.4 79 | G5.4a 80 | G5.4b 81 | G5.4c 82 | G5.5 83 | G5.6 84 | G5.6a 85 | G5.6b 86 | G5.7 87 | G5.8 88 | G5.8a 89 | G5.8b 90 | G5.8c 91 | G5.8d 92 | G5.9 93 | G5.9a 94 | G5.9b 95 | G5.10 96 | G5.11 97 | G5.11a 98 | G5.12 99 | RE1.0 100 | RE1.2 101 | RE1.3 102 | RE1.3a 103 | RE1.4 104 | RE2.0 105 | RE2.1 106 | RE2.3 107 | RE2.4 108 | RE2.4a 109 | RE2.4b 110 | RE3.0 111 | RE3.1 112 | RE3.2 113 | RE4.0 114 | RE4.1 115 | RE4.2 116 | RE4.3 117 | RE4.5 118 | RE4.6 119 | RE4.7 120 | RE4.8 121 | RE4.9 122 | RE4.10 123 | RE4.11 124 | RE4.12 125 | RE4.13 126 | RE4.14 127 | RE4.15 128 | RE4.16 129 | RE4.17 130 | RE4.18 131 | RE5.0 132 | RE6.0 133 | RE6.1 134 | RE6.2 135 | RE6.3 136 | RE7.0 137 | RE7.0a 138 | RE7.1 139 | RE7.1a 140 | RE7.2 141 | RE7.3 142 | RE7.4 143 | 144 | Statistical standards should be documented in most package files, yet are mostly only documented in one file. 145 | 146 | 147 | 148 | Click to see the [report of author-reported standards compliance of the package with links to associated lines of code](report.html), which can be re-generated locally by running the [`srr_report()` function](https://docs.ropensci.org/srr/reference/srr_report.html) from within a local clone of the repository. 149 | 150 | --- 151 | 152 | 153 | ### 2. Package Dependencies 154 | 155 |
156 | Details of Package Dependency Usage (click to open) 157 |

158 | 159 | The table below tallies all function calls to all packages ('ncalls'), both internal (r-base + recommended, along with the package itself), and external (imported and suggested packages). 'NA' values indicate packages to which no identified calls to R functions could be found. Note that these results are generated by an automated code-tagging system which may not be entirely accurate. 160 | 161 | |type |package | ncalls| 162 | |:----------|:--------|------:| 163 | |imports |memoise | NA| 164 | |imports |memoise | NA| 165 | |imports |memoise | NA| 166 | |imports |memoise | NA| 167 | |imports |memoise | NA| 168 | |imports |memoise | NA| 169 | |imports |memoise | NA| 170 | |imports |memoise | NA| 171 | |imports |memoise | NA| 172 | |imports |memoise | NA| 173 | |imports |memoise | NA| 174 | |imports |memoise | NA| 175 | |imports |memoise | NA| 176 | |imports |memoise | NA| 177 | |imports |memoise | NA| 178 | |imports |memoise | NA| 179 | |imports |memoise | NA| 180 | |imports |memoise | NA| 181 | |imports |memoise | NA| 182 | |imports |memoise | NA| 183 | |imports |Rcpp | NA| 184 | |suggests |testthat | NA| 185 | |linking_to |Rcpp | NA| 186 | 187 | Click below for tallies of functions used in each package. Locations of each call within this package may be generated locally by running 's <- pkgstats::pkgstats()', and examining the 'external_calls' table. 188 | 189 | 190 | 191 | **NOTE:** No imported packages appear to have associated function calls; please ensure with author that these 'Imports' are listed appropriately. 192 | 193 | 194 |

195 | 196 | --- 197 | 198 | 199 | ### 3. Statistical Properties 200 | 201 | This package features some noteworthy statistical properties which may need to be clarified by a handling editor prior to progressing. 202 | 203 |
204 | Details of statistical properties (click to open) 205 |

206 | 207 | The package has: 208 | 209 | - code in C++ (72% in 2 files) and R (28% in 4 files) 210 | - 1 authors 211 | - no vignette 212 | - no internal data file 213 | - 21 imported packages 214 | - 1 exported function (median 3 lines of code) 215 | - 2 non-exported functions in R (median 3 lines of code) 216 | - 2 R functions (median 5 lines of code) 217 | 218 | --- 219 | 220 | Statistical properties of package structure as distributional percentiles in relation to all current CRAN packages 221 | The following terminology is used: 222 | 223 | - `loc` = "Lines of Code" 224 | - `fn` = "function" 225 | - `exp`/`not_exp` = exported / not exported 226 | 227 | All parameters are explained as tooltips in the locally-rendered HTML version of this report generated by [the `checks_to_markdown()` function](https://docs.ropensci.org/pkgcheck/reference/checks_to_markdown.html) 228 | 229 | 230 | The final measure (`fn_call_network_size`) is the total number of calls between functions (in R), or more abstract relationships between code objects in other languages. Values are flagged as "noteworthy" when they lie in the upper or lower 5th percentile. 231 | 232 | |measure | value| percentile|noteworthy | 233 | |:-----------------------|-----:|----------:|:----------| 234 | |files_R | 4| 27.8| | 235 | |files_src | 2| 78.9| | 236 | |files_vignettes | 0| 0.0|TRUE | 237 | |files_tests | 2| 66.7| | 238 | |loc_R | 10| 1.7|TRUE | 239 | |loc_src | 26| 2.7|TRUE | 240 | |loc_tests | 6| 5.1| | 241 | |num_vignettes | 0| 0.0|TRUE | 242 | |n_fns_r | 3| 4.3|TRUE | 243 | |n_fns_r_exported | 1| 1.6|TRUE | 244 | |n_fns_r_not_exported | 2| 4.4|TRUE | 245 | |n_fns_src | 2| 12.0| | 246 | |n_fns_per_file_r | 1| 1.8|TRUE | 247 | |n_fns_per_file_src | 1| 7.3| | 248 | |num_params_per_fn | 0| 0.0|TRUE | 249 | |loc_per_fn_r | 3| 2.6|TRUE | 250 | |loc_per_fn_r_exp | 3| 3.0|TRUE | 251 | |loc_per_fn_r_not_exp | 3| 3.1|TRUE | 252 | |loc_per_fn_src | 5| 10.0| | 253 | |rel_whitespace_R | 40| 5.0|TRUE | 254 | |rel_whitespace_src | 27| 4.9|TRUE | 255 | |rel_whitespace_tests | 17| 2.8|TRUE | 256 | |doclines_per_fn_exp | 6| 2.3|TRUE | 257 | |doclines_per_fn_not_exp | 0| 0.0|TRUE | 258 | |fn_call_network_size | 1| 11.7| | 259 | 260 | --- 261 | 262 |

263 | 264 | 265 | ### 3a. Network visualisation 266 | 267 | An interactive visualisation of calls between objects in the package has been uploaded as a workflow artefact. To view it, click on results from the [latest 'pkgcheck' action](network.html), scroll to the bottom, and click on the 'visual-network' artefact. 268 | 269 | --- 270 | 271 | ### 4. `goodpractice` and other checks 272 | 273 | ('goodpractice' not included with these checks) 274 | 275 | --- 276 | 277 |
278 | Package Versions 279 |

280 | 281 | |package |version | 282 | |:--------|:------| 283 | |pkgstats |42 | 284 | |pkgcheck |42 | 285 | |srr |42 | 286 | 287 |

288 |
289 | -------------------------------------------------------------------------------- /tests/testthat/helper-snapshots-clean.R: -------------------------------------------------------------------------------- 1 | # Functions to clean files used for snapshot tests 2 | 3 | # some checks like rcmdcheck differ on different systems for things like 4 | # compilation flags, so the snapshot test excludes any rmcdcheck output. It 5 | # also reverts the final package versions to a generic number. 6 | # 7 | # print_method edits the output of `print.pkgcheck`. 8 | edit_markdown <- function (md, print_method = FALSE) { 9 | 10 | change_pkg_vers <- function (md, pkg = "pkgstats", to = "42") { 11 | i <- grep ("Package Versions", md) 12 | pkg_i <- grep (pkg, md) 13 | pkg_i <- pkg_i [pkg_i > i] [1] 14 | md [pkg_i] <- gsub ("([0-9]\\.)+[0-9]+", to, md [pkg_i]) 15 | # white space also changes with version numbers: 16 | md [pkg_i] <- gsub ( 17 | paste0 (to, "\\s+"), 18 | paste0 (to, " "), md [pkg_i] 19 | ) 20 | return (md) 21 | } 22 | md <- change_pkg_vers (md, "pkgstats") 23 | md <- change_pkg_vers (md, "pkgcheck") 24 | md <- change_pkg_vers (md, "srr") 25 | 26 | if (print_method) { 27 | 28 | # change path to visjs html file when generated locally: 29 | i <- grep ("network diagram", md) 30 | md [i] <- gsub ( 31 | "network\\sdiagram\\sis\\sat\\s.*$", 32 | "network diagram is not here.", 33 | md [i] 34 | ) 35 | 36 | return (md) 37 | } 38 | 39 | # The headers of those tables also change between machines and/or pandoc 40 | # versions, some stretching `-`s to fit text, some using fixed with. This 41 | # ensure regularity 42 | i <- grep ("Package Versions", md) 43 | vers_i <- grep ("version\\s+\\|$", md) 44 | vers_i <- vers_i [which (vers_i > i)] [1] 45 | md [vers_i] <- gsub ("version\\s+\\|$", "version |", md [vers_i]) 46 | hbar_i <- vers_i + 1 # always! 47 | md [hbar_i] <- gsub ("\\:\\-+\\|$", ":------|", md [hbar_i]) 48 | 49 | # change path to visjs html file when generated locally: 50 | i <- grep ("interactive network visualisation", md) 51 | md [i] <- gsub ("\\]\\(.*$", "](network.html)", md [i]) 52 | # or the equivalent version generated on GitHub actions: 53 | i <- grep ("An interactive visualisation", md) 54 | md [i] <- gsub ("\\]\\(.*\\)", "](network.html)", md [i]) 55 | 56 | # remove
sections of function usage because numbers of function 57 | # calls to dependency packages can be arbitrarily ordered when numbers of 58 | # calls are equal. 59 | i <- grep ("^
", md) 60 | j <- grep ("
", md) 61 | # These are the sub-sections: 62 | index <- which (j [-length (i)] > i [-1]) 63 | if (length (index) > 0L) { 64 | index <- seq (min (i [index + 1]), max (j [index])) 65 | md <- md [-index] 66 | } 67 | 68 | # and for some reason, the covr environment pulls in an extra external 69 | # package which must be removed to align snapshots: 70 | i <- grep ("\\|mgcv", md) 71 | if (length (i) > 0L) { 72 | md <- md [-i] 73 | } 74 | 75 | return (md) 76 | } 77 | 78 | # issue#111 79 | # html output is not generally reproducible, because all sorts of scripts get 80 | # inserted on different systems. This reduces the entire html file to the data 81 | # within the primary `
` containers only. 82 | # 83 | # Note that it presumed that `edit_markdown` has already been called to revert 84 | # package versions to generic values prior to rendering html version of that 85 | # report. 86 | # 87 | # @param f Name of html file in current tempdir 88 | edit_html <- function (f) { 89 | 90 | h <- readLines (f) 91 | 92 | # title includes path, so reset to generic value: 93 | i <- grep ("^", h) [1] 94 | h [i] <- "<title>pkgcheck.knit" 95 | 96 | # reduce down to only elements within the main `div` containers: 97 | i <- grep ("^$", h) 98 | j <- grep ("^<\\/div>$", h) 99 | len <- min (c (length (i), length (j))) 100 | ij <- cbind (i [seq (len)], j [seq (len)]) 101 | index <- apply (ij, 1, function (i) i [1]:i [2]) 102 | index <- sort (unique (unlist (index))) 103 | 104 | h <- h [index] 105 | 106 | # some machines/systems split `` items across multiple lines, 107 | # others concatenate, so concanate all regardless: 108 | i <- grep ("^$", h) 109 | j <- grep ("^<\\/summary>$", h) 110 | len <- min (c (length (i), length (j))) 111 | ij <- cbind (i [seq (len)], j [seq (len)]) 112 | # rm any which are on single line: 113 | ij <- ij [which (ij [, 2] > ij [, 1]), ] 114 | index <- apply (ij, 1, function (i) i [1]:i [2]) 115 | if (!is.list (index)) { 116 | index <- lapply (seq (ncol (index)), function (i) index [, i]) 117 | } 118 | index <- rev (index) 119 | for (i in index) { 120 | h [i [1]] <- paste0 (h [i], collapse = "") 121 | h <- h [-(i [-1])] 122 | } 123 | 124 | # some machines/systems generate a `colgroup` group specifying column 125 | # widths, while others do not (likely pandoc v1 - v2 difference), so remove 126 | # that: 127 | i <- grep ("^$", h) 128 | if (length (i) > 0L) { 129 | j <- grep ("^<\\/colgroup>$", h) 130 | ij <- apply ( 131 | cbind (i, j), 132 | 1, 133 | function (k) seq (k [1], k [2]), 134 | simplify = FALSE 135 | ) 136 | ij <- unlist (ij) 137 | h <- h [seq_along (h) [-ij]] 138 | } 139 | 140 | writeLines (h, con = f) 141 | } 142 | -------------------------------------------------------------------------------- /tests/testthat/test-extra-checks.R: -------------------------------------------------------------------------------- 1 | test_all <- identical (Sys.getenv ("MPADGE_LOCAL"), "true") || 2 | identical (Sys.getenv ("GITHUB_JOB"), "test-coverage") 3 | 4 | # These tests should not be skipped because the `!test_all` condition then 5 | # includes the `pkgcheck` workflow itself, which then reduces coverage. 6 | # skip_if (!test_all) 7 | testthat::skip_on_os ("windows") 8 | testthat::skip_on_os ("mac") 9 | 10 | test_that ("extra checks", { 11 | 12 | withr::local_envvar (list ("PKGCHECK_SRR_REPORT_FILE" = "report.html")) 13 | withr::local_envvar (list ("PKGCHECK_TEST_NETWORK_FILE" = "network.html")) 14 | withr::local_envvar (list ( 15 | "PKGCHECK_CACHE_DIR" = 16 | file.path (tempdir (), "pkgcheck") 17 | )) 18 | withr::local_envvar (list ("GITHUB_ACTIONS" = "true")) 19 | withr::local_envvar (list ("GITHUB_REPOSITORY" = "org/repo")) 20 | 21 | f <- system.file ("extdata", "pkgstats_9.9.tar.gz", package = "pkgstats") 22 | path <- pkgstats::extract_tarball (f) 23 | checks <- pkgcheck (path, goodpractice = FALSE) 24 | # Checks on systems without the right API keys may fail checks which rely on 25 | # URL queries, so these are manually reset here: 26 | checks$checks$pkgname_available <- TRUE 27 | checks$info$badges <- character (0) 28 | 29 | # Then fake the extra checks for the output methods: 30 | checks$checks$has_scrap <- c ("a", "b") 31 | checks$checks$obsolete_pkg_deps <- c ("blah", "sp", "rgdal") 32 | checks$info$srr <- list ( 33 | message = "srr message", 34 | okay = TRUE 35 | ) 36 | checks$checks$srr_okay <- TRUE 37 | 38 | md <- checks_to_markdown (checks) 39 | 40 | # ***************************************************************** 41 | # *********************** SNAPSHOT TEST *********************** 42 | # ***************************************************************** 43 | # 44 | md <- edit_markdown (md) # from clean-snapshots.R 45 | 46 | md_dir <- withr::local_tempdir () 47 | writeLines (md, con = file.path (md_dir, "checks-extra.md")) 48 | 49 | testthat::expect_snapshot_file (file.path (md_dir, "checks-extra.md")) 50 | 51 | h <- render_md2html (md, open = FALSE) 52 | f <- file.path (md_dir, "checks-extra.html") 53 | file.copy (h, f) 54 | edit_html (f) # from clean-snapshots.R 55 | 56 | testthat::expect_snapshot_file (f) 57 | 58 | # Then snapshot tests of print & summary methods 59 | # This loads goodpractice, so first do that to avoid load message 60 | requireNamespace ("goodpractice") 61 | f <- tempfile (fileext = ".md") 62 | x <- capture.output (print (checks), file = f, type = "message") 63 | 64 | md <- edit_markdown (readLines (f), print_method = TRUE) 65 | md_dir <- withr::local_tempdir () 66 | writeLines (md, con = file.path (md_dir, "checks-print.md")) 67 | 68 | testthat::expect_snapshot_file (file.path (md_dir, "checks-print.md")) 69 | }) 70 | -------------------------------------------------------------------------------- /tests/testthat/test-extra-local-check.R: -------------------------------------------------------------------------------- 1 | 2 | test_that ("pkgcheck", { 3 | 4 | skip_on_os ("windows") 5 | 6 | withr::local_envvar (list ( 7 | "PKGCHECK_CACHE_DIR" = 8 | file.path (tempdir (), "pkgcheck") 9 | )) 10 | 11 | pkgname <- paste0 ( 12 | sample (c (letters, LETTERS), 8), 13 | collapse = "" 14 | ) 15 | d <- srr::srr_stats_pkg_skeleton (pkg_name = pkgname) 16 | 17 | x <- capture.output ( 18 | roxygen2::roxygenise (d), 19 | type = "message" 20 | ) 21 | 22 | expect_output ( 23 | chk0 <- pkgcheck (d, goodpractice = FALSE) 24 | ) 25 | 26 | nchks0 <- length (chk0$checks) 27 | md <- checks_to_markdown (chk0, render = FALSE) 28 | nchks0_md <- length (grep ("^\\-\\s\\:heavy", md)) 29 | 30 | e <- new.env (parent = emptyenv ()) 31 | e$pkgchk_new_check <- function (checks) { 32 | return (FALSE) 33 | } 34 | 35 | e$output_pkgchk_new_check <- function (checks) { 36 | out <- list ( 37 | check_pass = checks$checks$new_check, 38 | summary = "", 39 | print = "" 40 | ) 41 | 42 | out$summary <- ifelse ( 43 | out$check_pass, 44 | "**NEW CHECK PASSES**", 45 | "**NEW CHECK DOES NOT PASS**" 46 | ) 47 | out$print <- list ( 48 | message = "New check output", 49 | obj = c ("new", "check") 50 | ) 51 | 52 | return (out) 53 | } 54 | 55 | # Test that those checks are picked up in the checks$checks result: 56 | chk1 <- pkgcheck (d, goodpractice = FALSE, extra_env = e) 57 | nchks1 <- length (chk1$checks) 58 | expect_equal (nchks1, nchks0 + 1) 59 | 60 | # And that they are also rendered in the summary check output: 61 | md <- checks_to_markdown (chk1, render = FALSE) 62 | nchks1_md <- length (grep ("^\\-\\s\\:heavy", md)) 63 | expect_equal (nchks1_md, nchks0_md + 1) 64 | }) 65 | -------------------------------------------------------------------------------- /tests/testthat/test-github.R: -------------------------------------------------------------------------------- 1 | test_that ("use_github_action_pkgcheck", { 2 | 3 | dir <- fs::path (fs::file_temp (pattern = "pkgcheck"), ".github") 4 | # nolint start 5 | expect_snapshot_error (use_github_action_pkgcheck (file_name = 23)) 6 | expect_snapshot_error (use_github_action_pkgcheck (file_name = c ("some", "files"))) 7 | 8 | dir.create (dir, recursive = TRUE) 9 | path <- fs::path (dir, "pkgcheck.yaml") 10 | file.create (path) 11 | expect_error (use_github_action_pkgcheck (dir = dir), "already exists") 12 | file.remove (path) 13 | expect_snapshot_error (use_github_action_pkgcheck (dir = dir, inputs = "not a list")) 14 | expect_error (use_github_action_pkgcheck (dir = dir, inputs = list (notaninput = 23)), "not valid") 15 | 16 | # Success - but skip on windows and mac because GHA machines use completely 17 | # different paths 18 | testthat::skip_on_os ("windows") 19 | testthat::skip_on_os ("mac") 20 | gha_path <- use_github_action_pkgcheck (dir = dir, branch = "main") 21 | expect_equal (path, gha_path) 22 | 23 | expect_snapshot_file (path) 24 | expect_snapshot_file ( 25 | use_github_action_pkgcheck (dir = dir, 26 | file_name = "with_inputs.yaml", 27 | branch = "main", 28 | inputs = list (`post-to-issue` = "true", 29 | `summary-only` = "false", 30 | ref = "main"))) 31 | 32 | }) 33 | 34 | test_that ("yaml branch", { 35 | 36 | yaml <- system.file ( 37 | "pkgcheck.yaml", 38 | package = "pkgcheck", 39 | mustWork = TRUE 40 | ) %>% readLines () 41 | 42 | expect_true (any (grepl ("^\\s+\\-\\s*main$", yaml))) 43 | 44 | # hard-code branch to avoid gert call, which may fail on GHA: 45 | yaml1 <- add_branch_to_yaml (yaml, branch = "main") 46 | expect_identical (yaml, yaml1) 47 | 48 | yaml2 <- add_branch_to_yaml (yaml, branch = "new-branch") 49 | expect_true (length (yaml2) > length (yaml1)) 50 | expect_equal (length (yaml2) - length (yaml1), 1L) 51 | expect_true (any (grepl ("^\\s+\\-\\s*new-branch$", yaml2))) 52 | 53 | pos1 <- grep ("^\\s+\\-\\s*main$", yaml) 54 | pos2 <- grep ("^\\s+\\-\\s*new-branch$", yaml2) 55 | expect_equal (pos2 - pos1, 1L) 56 | }) 57 | -------------------------------------------------------------------------------- /tests/testthat/test-goodpractice.R: -------------------------------------------------------------------------------- 1 | test_all <- (identical (Sys.getenv ("MPADGE_LOCAL"), "true") || 2 | identical (Sys.getenv ("GITHUB_JOB"), "test-coverage") || 3 | identical (Sys.getenv ("GITHUB_JOB"), "pkgcheck")) 4 | 5 | skip_if (!test_all) 6 | 7 | test_that ("goodpractice", { 8 | 9 | withr::local_envvar (list ("PKGCHECK_SRR_REPORT_FILE" = "report.html")) 10 | withr::local_envvar (list ("PKGCHECK_TEST_NETWORK_FILE" = "network.html")) 11 | withr::local_envvar (list ( 12 | "PKGCHECK_CACHE_DIR" = 13 | file.path (tempdir (), "pkgcheck") 14 | )) 15 | withr::local_envvar (list ("GITHUB_ACTIONS" = "true")) 16 | withr::local_envvar (list ("GITHUB_REPOSITORY" = "org/repo")) 17 | 18 | pkgname <- paste0 (sample (c (letters, LETTERS), 8), collapse = "") 19 | d <- srr::srr_stats_pkg_skeleton (pkg_name = pkgname) 20 | 21 | x <- capture.output ( 22 | roxygen2::roxygenise (d), 23 | type = "message" 24 | ) 25 | 26 | expect_output ( 27 | checks <- pkgcheck (d, use_cache = FALSE) 28 | ) 29 | 30 | gp <- summarise_gp_checks (checks) 31 | expect_type (gp, "list") 32 | expect_length (gp, 2L) 33 | expect_identical (names (gp), c ("rcmd_errs", "rcmd_warns")) 34 | 35 | md <- gp_checks_to_md (checks) 36 | expect_type (md, "character") 37 | expect_true (length (md) > 10L) 38 | expect_true (any (grepl ("`goodpractice` results", md))) 39 | expect_true (any (grepl ("R CMD check", md))) 40 | # expect_true (any (grepl ("Test Coverage", md))) 41 | expect_true (any (grepl ("Static code analyses", md))) 42 | 43 | checks$goodpractice$rcmdcheck <- try (stop ("nope"), silent = TRUE) 44 | gp <- summarise_gp_checks (checks) 45 | expect_null (gp$rcmd_warns) 46 | expect_true (grepl ("R CMD check process failed", gp$rcmd_errs)) 47 | }) 48 | -------------------------------------------------------------------------------- /tests/testthat/test-license-list.R: -------------------------------------------------------------------------------- 1 | 2 | test_that ("license-list", { 3 | 4 | expect_silent (x <- license_list ()) 5 | expect_type (x, "character") 6 | expect_length (x, 33L) 7 | }) 8 | -------------------------------------------------------------------------------- /tests/testthat/test-list-checks.R: -------------------------------------------------------------------------------- 1 | test_that ("list-checks", { 2 | expect_message ( 3 | chks <- list_pkgchecks (), 4 | "The following checks are currently implemented" 5 | ) 6 | expect_length (chks, 21L) 7 | 8 | expect_silent ( 9 | chks2 <- list_pkgchecks (quiet = TRUE) 10 | ) 11 | expect_identical (chks, chks2) 12 | }) 13 | -------------------------------------------------------------------------------- /tests/testthat/test-order-checks.R: -------------------------------------------------------------------------------- 1 | 2 | # New checks have to be added to the `order_checks` function in `summarise-checks.R`. 3 | # This test ensures that they are added by failing if not. 4 | 5 | test_that ("order checks", { 6 | 7 | pkg_env <- asNamespace ("pkgcheck") 8 | pkg_fns <- ls (pkg_env) 9 | 10 | fns <- gsub ( 11 | "^output\\_pkgchk\\_", "", 12 | grep ("^output\\_pkgchk\\_", pkg_fns, value = TRUE) 13 | ) 14 | 15 | checks <- order_checks (fns) 16 | 17 | expect_equal (length (fns), length (checks)) 18 | }) 19 | -------------------------------------------------------------------------------- /tests/testthat/test-path-fns.R: -------------------------------------------------------------------------------- 1 | testthat::skip_on_os ("windows") 2 | testthat::skip_on_os ("mac") 3 | 4 | # Results on non-Linux systems are equal paths, but not identical 5 | 6 | test_that ("pkgcheck", { 7 | 8 | pkgname <- paste0 ( 9 | sample (c (letters, LETTERS), 8), 10 | collapse = "" 11 | ) 12 | pkg_root <- fs::dir_create (fs::path_temp (), pkgname) 13 | path <- fs::dir_create (pkg_root, "subdir") 14 | f_desc <- fs::path (path, "DESCRIPTION") 15 | desc <- system.file ("DESCRIPTION", package = "pkgcheck") 16 | fs::file_copy (desc, f_desc, overwrite = TRUE) 17 | gert::git_init (pkg_root) 18 | 19 | path_conv <- convert_path (pkg_root) 20 | # path_conv should then be subdir: 21 | expect_identical (path_conv, path) 22 | 23 | fs::dir_delete (pkg_root) 24 | }) 25 | -------------------------------------------------------------------------------- /tests/testthat/test-pkgcheck.R: -------------------------------------------------------------------------------- 1 | test_all <- (identical (Sys.getenv ("MPADGE_LOCAL"), "true") || 2 | identical (Sys.getenv ("GITHUB_JOB"), "test-coverage") || 3 | identical (Sys.getenv ("GITHUB_JOB"), "pkgcheck")) 4 | 5 | skip_if (!test_all) 6 | 7 | test_that ("pkgcheck", { 8 | 9 | withr::local_envvar (list ("PKGCHECK_SRR_REPORT_FILE" = "report.html")) 10 | withr::local_envvar (list ("PKGCHECK_TEST_NETWORK_FILE" = "network.html")) 11 | withr::local_envvar (list ( 12 | "PKGCHECK_CACHE_DIR" = 13 | file.path (tempdir (), "pkgcheck") 14 | )) 15 | withr::local_envvar (list ("GITHUB_ACTIONS" = "true")) 16 | withr::local_envvar (list ("GITHUB_REPOSITORY" = "org/repo")) 17 | 18 | pkgname <- "testpkgchecknotapkg" 19 | d <- srr::srr_stats_pkg_skeleton (pkg_name = pkgname) 20 | # Add memoise to check global assign in memoised fns: 21 | f_desc <- fs::path (d, "DESCRIPTION") 22 | desc <- readLines (f_desc) 23 | i <- grep ("Rcpp$", desc) [1] 24 | 25 | # Add lots of 'Imports' to fail 'pkgcheck_num_imports' #218: 26 | num_deps_to_add <- 20L 27 | deps_add <- c (rep (" memoise,", num_deps_to_add), " Rcpp") 28 | 29 | desc <- c ( 30 | desc [seq_len (i - 1)], 31 | deps_add, 32 | desc [seq (i + 1, length (desc))] 33 | ) 34 | writeLines (desc, f_desc) 35 | # Define memoised fn in new 'zzz.R' file: 36 | f_zzz <- fs::path (d, "zzz.R") 37 | zzz <- c ( 38 | ".onLoad <- function(libname, pkgname) {", 39 | " test_fn <<- memoise::memoise(test_fn)", 40 | "}" 41 | ) 42 | writeLines (zzz, f_zzz) 43 | 44 | x <- capture.output ( 45 | roxygen2::roxygenise (d), 46 | type = "message" 47 | ) 48 | 49 | expect_true (length (x) > 10) 50 | expect_true (any (grepl ("srrstats", x))) 51 | 52 | expect_output ( 53 | checks <- pkgcheck (d) 54 | ) 55 | expect_type (checks, "list") 56 | 57 | # goodpractice -> rcmdcheck fails on some machines for reasons that can't be 58 | # controlled (such as not being able to find "MASS" pkg). 59 | checks$goodpractice <- NULL 60 | # Checks on systems without the right API keys may fail checks which rely on 61 | # URL queries, so these are manually reset here: 62 | checks$checks$pkgname_available <- TRUE 63 | checks$info$badges <- NULL # then fails CI checks 64 | 65 | items <- c ("pkg", "info", "checks", "meta") 66 | expect_identical (names (checks), items) 67 | 68 | items <- c ( 69 | "name", "path", "version", "url", "BugReports", 70 | "license", "summary", "dependencies", "external_calls", 71 | "external_fns" 72 | ) 73 | expect_identical (names (checks$pkg), items) 74 | 75 | items <- c ( 76 | "fn_names", 77 | "git", 78 | "network_file", 79 | "pkgdown_concepts", 80 | "pkgstats", 81 | "renv_activated", 82 | "srr" 83 | ) 84 | expect_identical (sort (names (checks$info)), sort (items)) 85 | 86 | md <- checks_to_markdown (checks, render = FALSE) 87 | 88 | a <- attributes (md) 89 | expect_true (length (a) > 0L) 90 | expect_true ( 91 | all (c ( 92 | "checks_okay", 93 | "is_noteworthy", 94 | "network_file", 95 | "srr_report_file" 96 | ) %in% names (a)) 97 | ) 98 | 99 | # ***************************************************************** 100 | # *********************** SNAPSHOT TEST *********************** 101 | # ***************************************************************** 102 | 103 | # paths in these snapshots are not stable on windows, so skipped here 104 | skip_on_os ("windows") 105 | 106 | md <- edit_markdown (md) # from clean-snapshots.R 107 | 108 | md_dir <- withr::local_tempdir () 109 | writeLines (md, con = file.path (md_dir, "checks.md")) 110 | 111 | # Redact out variable git hashes: 112 | testthat::expect_snapshot_file (file.path (md_dir, "checks.md")) 113 | 114 | h <- render_md2html (md, open = FALSE) 115 | f <- file.path (md_dir, "checks.html") 116 | file.rename (h, f) 117 | edit_html (f) # from clean-snapshots.R 118 | 119 | testthat::expect_snapshot_file (f) 120 | 121 | fs::dir_delete (d) 122 | }) 123 | 124 | test_that ("pkgcheck without goodpractice", { 125 | pkgname <- paste0 ( 126 | sample (c (letters, LETTERS), 8), 127 | collapse = "" 128 | ) 129 | d <- srr::srr_stats_pkg_skeleton (pkg_name = pkgname) 130 | 131 | x <- capture.output ( 132 | roxygen2::roxygenise (d), 133 | type = "message" 134 | ) 135 | 136 | withr::local_envvar (list ( 137 | "PKGCHECK_CACHE_DIR" = 138 | file.path (tempdir (), "pkgcheck") 139 | )) 140 | 141 | expect_output ( 142 | checks <- pkgcheck (d, goodpractice = FALSE) 143 | ) 144 | 145 | # items from above including goodpractice: 146 | items <- c ("pkg", "info", "checks", "meta", "goodpractice") 147 | expect_false (all (items %in% names (checks))) 148 | items <- c ("pkg", "info", "checks", "meta") 149 | expect_true (all (items %in% names (checks))) 150 | 151 | fs::dir_delete (d) 152 | }) 153 | -------------------------------------------------------------------------------- /tests/testthat/test-pkgcheck_bg.R: -------------------------------------------------------------------------------- 1 | 2 | # These tests fail on GHA on windows because of path issues 3 | skip_on_os ("windows") 4 | 5 | test_that ("pkgcheck_bg() works", { 6 | 7 | withr::local_envvar (list ( 8 | "PKGCHECK_CACHE_DIR" = 9 | file.path (tempdir (), "pkgcheck") 10 | )) 11 | 12 | pkgname <- paste0 (sample (c (letters, LETTERS), 8), collapse = "") 13 | if (dir.exists (file.path (tempdir (), pkgname))) { 14 | chk <- unlink (file.path (tempdir (), pkgname), 15 | recursive = TRUE 16 | ) 17 | } 18 | d <- srr::srr_stats_pkg_skeleton (pkg_name = pkgname) 19 | 20 | x <- capture.output ( 21 | roxygen2::roxygenise (d), 22 | type = "message" 23 | ) 24 | 25 | expect_true (length (x) > 10) 26 | expect_true (any (grepl ("srrstats", x))) 27 | 28 | x <- pkgcheck_bg (d) 29 | expect_s3_class (x, "r_process") 30 | expect_s3_class (x, "R6") 31 | 32 | pt1 <- system.time ({ 33 | while (x$is_alive ()) { 34 | Sys.sleep (0.1) 35 | } 36 | }) [3] 37 | 38 | out <- x$get_result () 39 | expect_type (out, "list") 40 | expect_length (out, 5L) 41 | 42 | # results should then be cached: 43 | pt2 <- system.time ( 44 | x2 <- pkgcheck (d) 45 | ) [3] 46 | 47 | # expect_true (pt2 < pt1) # not always fulfilled 48 | }) 49 | -------------------------------------------------------------------------------- /vignettes/_output.yml: -------------------------------------------------------------------------------- 1 | html_document: 2 | toc: true 3 | toc_float: true 4 | number_sections: false 5 | theme: flatly 6 | -------------------------------------------------------------------------------- /vignettes/checks.Rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropensci-review-tools/pkgcheck/4ee8c6678c655025dedda7837a9b53eab93571d0/vignettes/checks.Rds -------------------------------------------------------------------------------- /vignettes/environment.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "The 'pkgcheck' computational environment" 3 | author: 4 | - "Mark Padgham" 5 | date: "`r Sys.Date()`" 6 | vignette: > 7 | %\VignetteIndexEntry{The 'pkgcheck' computational environment} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ```{r setup, include=FALSE} 13 | knitr::opts_chunk$set ( 14 | collapse = TRUE, 15 | warning = TRUE, 16 | message = TRUE, 17 | width = 120, 18 | comment = "#>", 19 | fig.retina = 2, 20 | fig.path = "README-" 21 | ) 22 | options (repos = c ( 23 | ropenscireviewtools = "https://ropensci-review-tools.r-universe.dev", 24 | CRAN = "https://cloud.r-project.org" 25 | )) 26 | library (pkgcheck) 27 | ``` 28 | 29 | Results of running `pkgcheck` package on your local computer may differ from 30 | results generated by rOpenSci's automated checking system. The 31 | [`pkgcheck-action` GitHub 32 | action](https://github.com/ropensci-review-tools/pkgcheck-action) provides a 33 | standard computational system for running `pkgcheck`, and can be used to 34 | trigger checks on every push to GitHub. Some packages may nevertheless depend 35 | on other system libraries, or packages from other programming languages, that 36 | are not included in the standard environment used by the 37 | [`pkgcheck-action`](https://github.com/ropensci-review-tools/pkgcheck-action). 38 | This vignette describes the computational environment for `pkgcheck`, including 39 | advice on how it may be modified to ensure your package passes rOpenSci's 40 | automated checks. 41 | 42 | ## The `pkgcheck` Docker container 43 | 44 | Both 45 | [`pkgcheck-action`](https://github.com/ropensci-review-tools/pkgcheck-action) 46 | and rOpenSci's own checking system use the [Docker](https://docker.com) 47 | container provided in [the `pkgcheck` 48 | Dockerfile](https://github.com/ropensci-review-tools/pkgcheck/blob/main/Dockerfile). 49 | This Docker environment builds on the [GitHub "runner" 50 | images](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md), 51 | with the first couple of hundred lines installing all system libraries needed 52 | to replicate that environment. 53 | 54 | That Docker environment also includes installations of: 55 | 56 | - [NixOS](https://nixos.org/) 57 | - The [Julia language](https://julialang.org/), along with the [JULL 58 | installer](https://github.com/abelsiqueira/jill) to enable easy package 59 | installation. 60 | - A selection of pre-installed R packages, notably including several very large 61 | yet commonly installed packages. 62 | - The [`reticulate` package](https://rstudio.github.io/reticulate/), with an 63 | associated virtual python environment, and any python packages needed in 64 | rOpenSci's suite of packages. 65 | - [`quarto`](https://quarto.org/) to enable packages to use quarto vignettes, 66 | or documents in any other format provided by quarto. 67 | 68 | ## How to get your package to pass rOpenSci's 'pkgcheck' system 69 | 70 | If you are preparing a package for submission, and it is failing 71 | [`pkgcheck-action`](https://github.com/ropensci-review-tools/pkgcheck-action) 72 | because of missing software, whether system dependencies not otherwise covered 73 | in [`sysreqsdb`](https://github.com/r-hub/sysreqsdb#sysreqs), missing packages 74 | from either Julia or python, or some other system requirement, you can first 75 | modify your local [`pkgcheck-action` workflow 76 | file](https://github.com/ropensci-review-tools/pkgcheck-action) to install 77 | these requirements as additional "steps". Once you've got the `pkgcheck-action` 78 | passing on your local repository, you may also submit a pull request to update 79 | our own 80 | [Dockerfile](https://github.com/ropensci-review-tools/pkgcheck/blob/main/Dockerfile) 81 | with whatever additional system dependencies your package may require. 82 | -------------------------------------------------------------------------------- /vignettes/list-checks.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Current pkgcheck checks" 3 | author: 4 | - "Mark Padgham" 5 | date: "`r Sys.Date()`" 6 | vignette: > 7 | %\VignetteIndexEntry{Current checks} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ```{r setup, include=FALSE} 13 | knitr::opts_chunk$set ( 14 | collapse = TRUE, 15 | warning = TRUE, 16 | message = TRUE, 17 | width = 120, 18 | comment = "#>", 19 | fig.retina = 2, 20 | fig.path = "README-" 21 | ) 22 | options (repos = c ( 23 | ropenscireviewtools = "https://ropensci-review-tools.r-universe.dev", 24 | CRAN = "https://cloud.r-project.org" 25 | )) 26 | library (pkgcheck) 27 | requireNamespace ("roxygen2") 28 | ``` 29 | 30 | The following checks are currently implemented in the `pkgcheck` package. 31 | Several of these are by default only shown when they fail; absence from a 32 | resultant checklist may be interpreted to indicate successful checks. 33 | 34 | ```{r list-checks, results = 'asis', echo = FALSE} 35 | base_dir <- rprojroot::find_root (rprojroot::is_r_package) 36 | flist <- list.files (file.path (base_dir, "R"), full.names = TRUE, pattern = "\\.R$") 37 | blocks <- lapply (flist, function (i) roxygen2::parse_file (i, env = NULL)) 38 | blocks <- do.call (c, blocks) # flatten embedded lists 39 | 40 | fn_names <- vapply (blocks, function (i) { 41 | pd <- utils::getParseData (parse ( 42 | text = deparse (i$call), 43 | keep.source = TRUE, 44 | encoding = "UTF-8" 45 | )) 46 | if (!any (pd$token == "SYMBOL")) { 47 | return ("") 48 | } else { 49 | pd$text [which (pd$token == "SYMBOL") [1]] 50 | }}, 51 | character (1L), 52 | USE.NAMES = FALSE 53 | ) 54 | 55 | index <- grep ("^pkgchk\\_", fn_names) 56 | blocks <- blocks [index] 57 | fn_names <- fn_names [index] 58 | 59 | block_docs <- lapply (seq_along (blocks), function (i) { 60 | list ( 61 | fn_name = paste0 (i, ". ", fn_names [i]), 62 | title = roxygen2::block_get_tag (blocks [[i]], "title")$val, 63 | desc = roxygen2::block_get_tag (blocks [[i]], "description")$val, 64 | details = roxygen2::block_get_tag (blocks [[i]], "details")$val, 65 | notes = unlist (roxygen2::block_get_tags (blocks [[i]], "note")) 66 | ) 67 | }) 68 | # notes are not listed, only title, description, and detail fields. 69 | 70 | block_str <- unlist (lapply (block_docs, function (i) { 71 | ret <- c ( 72 | paste0 ("## *", i$fn_name, "*"), 73 | "", 74 | i$title, 75 | "", 76 | i$desc 77 | ) 78 | 79 | if (!is.null (i$details)) { 80 | ret <- c (ret, "", i$details) 81 | } 82 | 83 | c (ret, "") 84 | })) 85 | 86 | cat (paste0 (block_str, collapse = "\n")) 87 | ``` 88 | --------------------------------------------------------------------------------