Keep your R code clean and consistent with Air

vscode
development
R
Author

Yohann Mansiaux

Published

May 19, 2025

Consistent code formatting is a crucial aspect of collaborative development that often gets overlooked. Well-formatted code provides several significant benefits:

Air formatter for R brings these benefits to R codebases with minimal setup effort, helping teams establish and maintain a consistent coding style automatically. While this guide focuses specifically on Air, the principles and benefits apply to any code formatter - the most important factor is having all team members use the same formatting tool with consistent settings.

Requirements

  • Air installed
  • Depending on your IDE, you may also need to install some extensions (as as VSCode user I had to install the Air - R Language Support extension).
  • To test if the formatter is working, you can use the following command: air format . in a Terminal (not the R console) !

Please read the following resources before starting:

  • https://posit-dev.github.io/air/formatter.html
  • https://www.tidyverse.org/blog/2025/02/air/

Warning for VSCode users

Before using Air I was using the REditorSupport formatter provided with the R extension for VSCode. Air was not working, because I forgot to edit correctly my settings.json file.

If you wish Air to be used as your default formatter, please comment or remove the line "editor.defaultFormatter": "REditorSupport.r" in your settings.json file.

  "[r]": {
    // "editor.defaultFormatter": "REditorSupport.r",
    "editor.formatOnSave": true
  }
  "[rmd]": {
    // "editor.defaultFormatter": "REditorSupport.r",
    "editor.formatOnSave": true
  }
  "[quarto]": {
    // "editor.defaultFormatter": "REditorSupport.r",
    "editor.formatOnSave": true
  }

As set in the settings.json file, Air is used as the default formatter as will be applied on save of any R file. There are other ways to use Air, the pre-commit or the Github Actions workflow.

Pre-commit hook

A pre-commit hook is an operation that is performed before a commit is made. If you are a R package developer, you sometimes already have been complaning about a message telling that both README-related files: README.md and README.Rmd must be staged together in a specific commit.

This pre-commit hook is added automatically to your project when you are using usethis::use_readme_rmd() !

How to add a pre-commit hook

usethis::use_git_hook(
  "pre-commit",
  "air format ."
  )

This command implies that for each commit you will make, the entire codebase of your project will be reformated.

Pre-commit hooks are located in a hidden folder of your repository. If you want to take a look at the generated/modified file you must look for the following file (starting from the root of your project): .git/hooks/pre-commit

The pros of pre-commit hooks are:

  • Ensures all code is formatted before it’s committed to the repository
  • Prevents unformatted code from entering the codebase
  • Enforces consistent code style across the project

The cons of pre-commit hooks are:

  • They are local to each developer’s machine and must be set up individually
  • Can be bypassed with git commit --no-verify if a developer chooses to
  • May slow down the commit process for large codebases
  • Requires Air to be installed on each developer’s machine
  • If formatting rules change, all developers need to update their local setup

If you are already using the option "editor.formatOnSave": true, the pre-commit hook might be useless (except for the first time you will be using it, by formatting the entire codebase), and you might be more interested in using the Github Actions workflow !

Using Github Actions

In the main branch of a project I want to have only “bullet-proof” code, i.e a code in which the devtools::check() returns no error, no warning, no note. Each time I’m opening a Pull Request from a branch to the main branch, I run a Github Action which calls devtools::check(). I allow merging the proposed code only if this Github Action pass.

To ensure that the merged code has also the expected format, I’ve completed my Github Action yaml configuration file to call the Air formatter on my codebase only if the devtools::check() was successful.

This very convenient when you are working with other colleagues on the same project, with potentially different IDE, to ensure the code format is the same between everyone and hence to avoid merge conflicts because of formatting.

Contrary to pre-commit hooks which are “computer-specific”, Github Actions workflow will be run remotely, ensuring that any modification to the code formatter settings will be applied to any code submitted to the main codebase !

To use this Github Actions workflow, please create the following folder: .github/workflow/ and add the following in a .yaml file.

# Workflow derived from https://github.com/posit-dev/setup-air/tree/main/examples
on:
  pull_request:
    branches: [main, master]

name: check_and_formatting.yml

permissions:
  contents: write

jobs:
  R-CMD-check:
    runs-on: ${{ matrix.config.os }}

    name: ${{ matrix.config.os }} (${{ matrix.config.r }})

    strategy:
      fail-fast: false
      matrix:
        config:
          - { os: ubuntu-latest, r: "release" }

    env:
      GITHUB_PAT: ${{ secrets.GH_TOKEN }}
      R_KEEP_PKG_SOURCE: yes

    steps:
      - uses: actions/checkout@v4

      - uses: r-lib/actions/setup-pandoc@v2

      - uses: quarto-dev/quarto-actions/setup@v2

      - uses: r-lib/actions/setup-r@v2
        with:
          r-version: ${{ matrix.config.r }}
          http-user-agent: ${{ matrix.config.http-user-agent }}
          use-public-rspm: true

      - uses: r-lib/actions/setup-r-dependencies@v2
        with:
          extra-packages: any::rcmdcheck
          needs: check

      - uses: r-lib/actions/check-r-package@v2
        with:
          upload-snapshots: true
          build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")'

  format-check:
    name: format-check
    needs: R-CMD-check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install
        uses: posit-dev/setup-air@v1

      - name: Check
        run: air format .

      - name: Commit changes
        run: |
          git config --global user.name "github-actions[bot]"
          git config --global user.email "github-actions[bot]@users.noreply.github.com"
          git add .
          git diff --cached --quiet || git commit -m "style: auto-format code via air format"

      - name: Push changes
        if: github.event_name == 'pull_request' && github.head_ref != ''
        run: |
          git pull --rebase origin ${{ github.head_ref }}
          git push origin HEAD:${{ github.head_ref }}

Conclusion

Implementing automated code formatting with Air provides immediate benefits for both individual developers and teams. Whether you choose to use the formatter through your IDE, pre-commit hooks, or GitHub Actions, the result is the same: consistently formatted code that’s easier to read, maintain, and collaborate on.

By incorporating Air into your workflow:

  • You eliminate time-consuming style discussions and manual formatting
  • Your codebase maintains a consistent style regardless of who contributes
  • Code reviews can focus on substance rather than style
  • New team members can quickly adapt to your project’s conventions

Don’t hesitate to ask me any question about this workflow or any idea of improvement !