lint-changed-files.yml

Run lintr at one of three scopes, selected by the scope input:

lintr is installed from CRAN (any::lintr / install.packages()), never from r-lib/lintr on GitHub HEAD. An unpinned HEAD install is the one dependency IndrajeetPatil/workflows leaves floating; an upstream commit can break callers’ CI with no change on either side.

This is a whole-file gate. For the incremental alternative that reports only the lints on lines a PR added or modified, see lint-changed-lines.yml. That capability stays separate: its added-lines diffing is different machinery from these three scopes.

How it works

The reusable workflow checks out the PR head when scope is changed-files (the changed-file set comes from the GitHub “list PR files” API, whose paths are relative to the head commit) and github.sha otherwise (the merge commit on a pull_request event, matching the r-lib example and the qwt/qbt whole-project workflows). It then sets up R, installs lintr from CRAN, optionally installs the package (so object_usage_linter can resolve the package’s own functions; skipped when path has no DESCRIPTION), and runs lintr at the requested scope.

changed-files requires a pull_request event. It lints with lint_package() when path holds a DESCRIPTION (the rpt form) and with lint_dir() otherwise (the win/qwt form). Unchanged files are passed as lintr exclusions, including dotfiles, so an unchanged .lintr.R is not re-linted on every PR. The GitHub API call uses .limit = Inf so a PR that changes more files than one API page is not silently under-linted.

package and project can run on push as well as pull_request.

Inputs

Input Type Default Description
scope string 'changed-files' What to lint: changed-files, package, or project.
path string '.' Path to the R package or project root (the directory holding DESCRIPTION, when there is one).
linter-file string '' Path to a lintr config (.lintr or .lintr.R). Empty lets lintr auto-detect .lintr / .lintr.R.
install-quarto boolean false Install Quarto when setting up R dependencies.
use-renv boolean false Restore dependencies with renv (setup-renv) instead of DESCRIPTION-based setup-r-dependencies.
renv-cache-version string '1' Cache version suffix passed to setup-renv.
apt-packages string '' Extra apt packages to install before setting up dependencies (space-separated).
extra-packages string '' Extra R packages to install (space- or comma-separated). gh and lintr are always installed from CRAN.
install-package boolean true Run R CMD INSTALL before linting so object_usage_linter can resolve the package’s own functions. Skipped when path has no DESCRIPTION. Set false to skip even when DESCRIPTION is present.
fail boolean true Fail the workflow when lintr reports a lint (rather than warn only).

Permissions

contents: read and pull-requests: read (to read the PR’s changed-file list when scope is changed-files). package / project do not need the pull-request grant; it is still read-only, so no caller permissions: block is required.

Example

# Copy to .github/workflows/lint-changed-files.yml in your repo.
# Default: lint the files a pull request changed.
name: lint-changed-files

on:
  pull_request:

jobs:
  lint:
    uses: Morrison-Lab/gha/.github/workflows/lint-changed-files.yml@v2
    # with:
    #   scope: 'changed-files'  # or 'package' / 'project'
    #   path: '.'               # R package or project root
    #   linter-file: ''         # empty: lintr auto-detects .lintr / .lintr.R
    #   install-quarto: false   # true to install Quarto with R dependencies
    #   use-renv: false         # true to restore dependencies with setup-renv
    #   renv-cache-version: '1' # bump to invalidate renv cache
    #   apt-packages: ''        # system packages required for compilation
    #   extra-packages: ''      # extra R deps (gh + lintr always installed from CRAN)
    #   install-package: false  # skip R CMD INSTALL even when DESCRIPTION is present
    #   fail: false             # warn instead of failing on lints

See the examples/ directory for the full caller stub (lint-changed-files.yml).

Pin @v2: this capability was added after @v1 was frozen, so it does not exist at that tag.