In the previous post we looked at how to apply a linter and styler to a Python Project. Now we’re going to do the same for an R project. We’ll use the {precommit}
R package to make the setup a breeze.
Install
First install the pre_commit
Python package.
pip install pre-commit
Install the {precommit}
R package.
install.packages("precommit")
You’ll want to also install a couple of other R packages.
install.packages(c("styler", "lintr"))
Setup
Setup precommit
for a project.
precommit::use_precommit()
That will create the .pre-commit-config.yaml
configuration file and, if present, also add it to .Rbuildignore
.
The content of the .pre-commit-config.yaml
file should look something like this (I’ve stripped out comments for brevity):
🚨 Your .pre-commit-config.yaml
might have a more recent version specified in the rev
field. I found that I ran into dependency issues with more recent versions, but the one I have selected below works reliably for me. YMMV.
repos:
- repo: https://github.com/lorenzwalthert/precommit
rev: v0.4.2
hooks:
- id: style-files
args: [--style_pkg=styler, --style_fun=tidyverse_style]
- id: roxygenize
- id: use-tidy-description
- id: spell-check
exclude: >
(?x)^(
.*\.[rR]|
.*\.feather|
.*\.jpeg|
.*\.pdf|
.*\.png|
.*\.py|
.*\.RData|
.*\.rds|
.*\.Rds|
.*\.Rproj|
.*\.sh|
(.*/|)\.gitignore|
(.*/|)\.gitlab-ci\.yml|
(.*/|)\.lintr|
(.*/|)\.pre-commit-.*|
(.*/|)\.Rbuildignore|
(.*/|)\.Renviron|
(.*/|)\.Rprofile|
(.*/|)\.travis\.yml|
(.*/|)appveyor\.yml|
(.*/|)NAMESPACE|
(.*/|)renv/settings\.dcf|
(.*/|)renv\.lock|
(.*/|)WORDLIST|
\.github/workflows/.*|
data/.*|
)$
- id: lintr
- id: readme-rmd-rendered
- id: parsable-R
- id: no-browser-statement
- id: no-debug-statement
- id: deps-in-desc
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: check-added-large-files
args: ['--maxkb=200']
- id: file-contents-sorter
files: '^\.Rbuildignore$'
- id: end-of-file-fixer
exclude: '\.Rd'
- repo: https://github.com/pre-commit-ci/pre-commit-ci-config
rev: v1.5.1
hooks:
- id: check-pre-commit-ci-config
- repo: local
hooks:
- id: forbid-to-commit
name: Don't commit common R artifacts
entry: Cannot commit .Rhistory, .RData, .Rds or .rds.
language: fail
files: '\.(Rhistory|RData|Rds|rds)$'
# `exclude: <regex>` to allow committing specific files
ci:
autoupdate_schedule: monthly
If you’re using {roxygen2}
then you might be prompted to run the following:
precommit::snippet_generate('additional-deps-roxygenize')
That will probably generate additional instructions about changes you need to make to the id: roxygenize
key in the .pre-commit-config.yaml
file. Apply those.
📢 I actually ended up removing the roxygenize
entry because I found that it occasionally sent me into a never ending commit loop.
💡 The .pre-commit-config.yaml
file should be staged and committed to the repository so that everybody who works on the code applies the same rules.
Extra Hooks
You might also want to add the trailing-whitespace
and check-yaml
rules to .pre-commit-config.yaml
under the https://github.com/pre-commit/pre-commit-hooks repository.
Commit
Once you’ve configured {precommit}
, stage the .pre-commit-config.yaml
and .Rbuildignore
files and then try to commit. You might need to work fairly hard to get all of the checks passing, especially if there’s quite a lot of code in the repository. Just be systematic in addressing each of the errors raised by the hook processes.
You might get an error about /usr/lib/R/Rscript
not being found. This means that pre-commit is looking in the wrong place for Rscript
. I simply made a link from /usr/lib/R/Rscript
to the actual location of Rscript
.
The {lintr} Package
The {lintr}
package will perform static analysis on your R code and help identify syntactic problems.
Install
Install {lintr}
.
install.packages("lintr")
Configure
Create a .lintr
file in the project root.
lintr::use_lintr()
You can update the .lintr
file to tweak the way that {lintr}
will treat your files.
linters: linters_with_defaults(
line_length_linter(120),
object_usage_linter = NULL,
object_name_linter(c("snake_case", "SNAKE_CASE")),
commented_code_linter = NULL
)
exclusions: list()
encoding: "UTF-8"
You might want to add in "dotted.case"
as another argument to object_name_linter()
.
Excluding Code
You can exclude chunks of code from linting by adding nolint
hints as comments.
# The following line of code will be ignored by {lintr}.
n <- 42 # nolint
# The following block of code will be ignored by {lintr}.
# nolint start
n <- 41
n <- n + 1
# nolint end
The {styler} Package
By default the {precommit}
package will invoke the {styler}
package and apply the Tidyverse style (via tidyverse_style
).
Install
Install {styler}
.
install.packages("styler")
Flourish
With the {lintr}
and {styler}
packages installed and kicked off on every commit via the pre-commit framework you can be confident that the code you push is both syntactically correct and consistently formatted.