Skip to main content

lint-changed-lines.yml

Run lintr over the R files a pull request changes, but report only the lints on lines the PR actually added or modified – not whole changed files. This lets lint rules be adopted or tightened incrementally: new and edited code must comply, while untouched legacy code in the same file is left alone until a PR naturally edits it.

Contrast with a whole-file “lint changed files” gate, which fails on function definitions a PR never touched – forcing either a repo-wide reformat or disabling the linter. lint-changed-lines is the incremental alternative. The whole-file, whole-package, and whole-project forms live in lint-changed-files.yml, selected by a scope input; that capability does not fold this one in, because the added-lines diffing here is different machinery.

How it works

The workflow checks out the PR head commit (not the default refs/pull/N/merge ref), sets up R, installs the package (so object_usage_linter can resolve the package’s own functions), then runs lintr on each changed R file and keeps only the lints whose line falls on a line the PR added or modified. Those changed lines are parsed from the GitHub “list PR files” API patch, whose line numbers are relative to the PR head – which is why the checkout is pinned to the head commit, so on-disk line numbers match.

Must be triggered by pull_request (the changed-line set comes from the PR).

A self-hosted runner that already has R keeps it: the workflow skips setup-r, whose apt-get step needs passwordless sudo.

A repository with no DESCRIPTION in path, or with install-package: false, skips setup-r-dependencies: the workflow installs gh, lintr and any extra-packages from CRAN (plain package names only) and skips R CMD INSTALL. On Linux, when the runner sets no CRAN mirror, it installs Posit Package Manager binaries.

Inputs

Input Type Default Description
path string '.' Path to the R package root (the directory holding DESCRIPTION, if any).
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; passed to setup-r-dependencies, or installed from CRAN under use-renv: true or when path holds no DESCRIPTION or install-package is false). gh and lintr are always installed.
install-package boolean true Run R CMD INSTALL before linting so object_usage_linter can resolve the package’s own functions. Set false for a repo that is not an installable R package.
fail boolean true Fail the workflow when a lint is found on a changed line (rather than warn only).
file-pattern string '[.][Rr]$' Regular expression matched against each changed file’s path; only matching files are linted. Use '[.]([Rr]\|[Rr]md\|[Qq]md)$' to also lint the R chunks in Quarto and R Markdown documents.
lms-ref string '' Commit SHA (or tag) of Morrison-Lab/psc to install the lms package from. When set, changed lines are linted with lms::default_linters(nested_calls = TRUE) instead of the linters in the repository’s .lintr.R; the exclusions in .lintr.R still apply.
runs-on string empty (CI_RUNNER variable, else ubuntu-latest) Runner label for the job. Use a self-hosted label only for a private repository; never for a public one, whose pull requests can run arbitrary code on it (#1167).

Permissions

contents: read and pull-requests: read (to read the PR’s changed-file patches).

Example

# Copy to .github/workflows/lint-changed-lines.yml in your repo.
# Lints only the lines a pull request adds or modifies (not whole changed
# files), so lint rules can be adopted or tightened incrementally.
name: lint-changed-lines

on:
  pull_request:

jobs:
  lint:
    uses: Morrison-Lab/gha/.github/workflows/lint-changed-lines.yml@v2
    # with:
    #   path: '.'               # R package root (holding DESCRIPTION)
    #   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)
    #   install-package: false  # skip R CMD INSTALL for a non-package repo
    #   fail: false             # warn instead of failing on lints
    #   file-pattern: '[.]([Rr]|[Rr]md|[Qq]md)$'  # also lint R chunks in .qmd/.Rmd
    #   lms-ref: <commit SHA of Morrison-Lab/psc>  # lint with lms::default_linters(nested_calls = TRUE)
    #   runs-on: private-runner                # self-hosted runner label (private repositories only)

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