Jarl: just another R linter

Remove bad patterns in your R code in the blink of an eye.
infrastructure
software development
Author

Etienne Bacher

Published

November 20, 2025

Original post here: Jarl: just another R linter

I’m very excited to introduce Jarl1, a new R linter. A linter is a tool that statically parses code (meaning that it doesn’t run the code in question) and searches for patterns that are inefficient, hard to read, or likely bugs. Jarl can parse dozens of files and thousands of lines of code in milliseconds. Here is an example of Jarl running on r-source (approximately 1000 files and 200k lines of R code) in about 700 milliseconds:


On top of that, Jarl can apply automatic fixes2:

Suppose that we have the following file foo.R:

x <- any(is.na(mtcars))

f <- function(x) {
  apply(x, 1, mean)
}

There are two rule violations in this file:

  1. any(is.na(mtcars)) should be replaced by anyNA(mtcars)3
  2. apply(x, 1, mean) should be replaced by rowMeans(x)4

Instead of fixing those cases by hand, we can run the following command in the terminal (not in the R console):

jarl check foo.R --fix

After running this, foo.R now contains the following code:

x <- anyNA(mtcars)
f <- function(x) {
  rowMeans(x)
}

(Note that f is now useless since it is equivalent to rowMeans().)


Jarl stands on the shoulders of giants, in particular:

Jarl is a single binary, meaning that it doesn’t need an R installation to work. This makes it a very attractive option for continuous integration for instance, since it takes less than 10 seconds to download the binary and run it on the repository.

Using Jarl

There are two ways to use Jarl:

  1. via the terminal, using jarl check [OPTIONS];
  2. using the integration in your coding editor (at the time of writing, a Jarl extension is available in VS Code, Positron, and Zed).

The Jarl extension enables code highlighting and quick fixes. The former means that code that violates any of the rules in your setup (more on this below) will be underlined and will show the exact violation when hovered.


The latter adds a lightbulb button next to rule violations, allowing you to selectively apply fixes.

In the future, those extensions could have a “Fix on save” feature similar to the “Format on save” functionality provided by Air.

Configuring Jarl

By default, Jarl will report violations for almost of its rules. It is possible to configure its behavior using a configuration file named jarl.toml. In particular, in this file, you can specify:

  • the rules you want to apply,
  • the files to include or exclude,
  • the rules for which you want to apply automatic fixes,

and more.

Conclusion

Jarl is in its early days, there are more rules and options to add. Still, it can already be used in interactive use or in continuous integration (check out the setup-jarl workflow!). Eventually, many lintr rules should be supported in Jarl, but the end goal is not to have perfect compatibility. lintr provides many rules related to code formatting (e.g. spaces_inside_linter). Those will not be integrated in Jarl since they are already covered by Air. Additionally (for now), Jarl cannot perform semantic analysis5, meaning that some lintr rules are out of scope (e.g. unreachable_code_linter).

This was a very light introduction, go to the Jarl website for more information.

If you want to help developing Jarl, check out the “Contributing” page. Jarl is written in Rust, which may be a barrier to contributing but is also a very powerful language which is a real pleasure to use. I will add a more detailed tutorial soon so that this can also be a nice introduction to this language. You can also contribute to the documentation!

Acknowledgements

As I said above, Jarl depends enormously on the work of lintr and Air developers, so thank you!

Jarl is also very inspired by similar tools in other languages, in particular Ruff in Python and Cargo clippy in Rust.

Finally, thanks to the R Consortium for funding part of the development of Jarl via the ISC Grant Program.

And thank you, Maëlle, for improving the draft!

Footnotes

  1. Jarl stands for “Just Another R Linter”.↩︎

  2. This is not always possible, it depends on the rule.↩︎

  3. anyNA(x) is more efficient than any(is.na(x)).↩︎

  4. rowMeans(x) is more efficient than apply(x, 1, mean).↩︎

  5. Semantic analysis refers to using the context surrounding an expression to explore rule violations.↩︎