Jarl 0.6.0

I’m glad to announce the release of Jarl 0.6.0. Jarl is a very fast R linter, written in Rust. It finds inefficient, hard-to-read, and suspicious patterns of R code across dozens of files and thousands of lines of code in milliseconds. Install or update Jarl via the command line or install the Jarl extension in Positron, VS Code, Zed, and more.

A quick summary before diving into the details:

As usual, this release also comes with other small features and a whole lot of bug fixes. You can find the full list of changes in the changelog.

Find unused objects

Jarl 0.6.0 brings many new rules but unused_object probably is the most significant, and definitely was the most complicated to implement. unused_object is a rule that finds objects that are defined but never used, meaning that these definitions can be removed without affecting the rest of the code. Unused objects are usually pieces of code that were forgotten while refactoring some code, but they can also signal a bug somewhere if we expected this object to be used.

This rule is hard to implement for two reasons.

Semantic analysis

The first one is shared across all languages: we need to have a good representation of the meaning of the code.

So far, most rules rely on patterns. For example, if we find a piece of code that follows the pattern any(is.na(<other code>)), then we can report a violation of the any_is_na rule. This is relatively easy to implement because this is a small pattern that follows a logical construct: if we find a call to any() containing a call to is.na(), we report it.

In the case of unused_object, we need to find whether an object that was just defined is used anywhere in the subsequent code, whether it is called in a different file, whether it is overwritten before actually being used, etc.

We need to be able to analyze the relationship of all R objects in our code. In other words, we need to do some semantic analysis.

This is a big challenge that I haven’t tackled myself. Instead, I relied upon the work of Lionel Henry and Davis Vaughan in Ark (in particular the Oak project). With this, Jarl can now read the entire semantic structure of R code, which also requires handling the case of multi-file projects that share objects, such as R packages or scripts that call source(). Big thanks to both of them for their work!

Everyone can manipulate R

We have a strong foundation for our semantic analysis, and this already covers a lot of cases in real-world projects. Now comes the second reason why this rule is hard to implement: anyone can manipulate R code, and this leads to cases that slip through the cracks:

Nevertheless, despite these flaws, I believe that unused_object will be very useful in many projects.

Reminder: for now, you need to explicitly opt-in to use this rule.

New rules

16 new rules have been added since 0.5.0 (in addition to unused_object), thanks to several contributors. Most of these rules also exist in lintr, meaning that Jarl slowly but surely gets closer to feature parity, but a few of them are not found there. In particular, Jarl now detects possible mistakes in glue::glue(), such as calls without any string interpolation:

warning: glue
 --> foo.R:1:1
  |
1 | glue('{a}', .open = '<', .close = '>')
  | -------------------------------------- This `glue()`
  |      call isn't necessary because it performs no
  |      interpolation.
  |

Better CLI experience

The CLI received some small but useful improvements.

Autocomplete suggestions

It is now possible to press <TAB> to have suggestions of commands or rule names accepted by Jarl. For instance, jarl check . --select any<TAB> would suggest either any_is_na or any_duplicated.

This is supported in several shells, such as fish, zsh, and bash. See the documentation to know how to set this up with your shell.

jarl rule

Jarl now has a new command jarl rule <rulename> to print the documentation of a specific rule directly in the terminal, hence avoiding an extra trip to the website.

> jarl rule any_is_na
any_is_na
Categories: PERF
Enabled by default: yes
Fix: safe

Added in 0.0.8

## What it does

Checks for usage of `any(is.na(...))`, `NA %in% x`, and `NA %notin% x`.

[...]

Rule name suggestions

Jarl now suggests similar rule names when there is a typo in one of the names:

> jarl check . --select duplicated_argument,any_i_na

jarl failed
  Cause: Unknown rules in `--select`: duplicated_argument, any_i_na
  Help: Did you mean "duplicated_arguments"?
  Help: Did you mean "any_is_na"?

Exclude folders

Jarl can exclude folders with the --exclude argument, similar to the exclude argument in jarl.toml. Note that this argument must take a =:

> jarl check . --exclude=inst,tests

Better help page organization

The help page is now clearly split into various sections:

Check a set of files or directories

Usage: jarl check [OPTIONS] <FILES>...

Arguments:
  <FILES>...
          List of files or directories to check or fix lints, for example `jarl check .`.

File selection:
      --exclude <FILES>
          List of file patterns to exclude from linting, separated by a comma (no spaces).

      [TRUNCATED FOR CONCISENESS]


Rule selection:
  -s, --select <RULES>
          Names of rules to include, separated by a comma (no spaces). This also accepts names of groups of rules, such as "PERF".

          [default: ""]

      [TRUNCATED FOR CONCISENESS]

Fix options:
  -f, --fix
          Automatically fix issues detected by the linter.

      [TRUNCATED FOR CONCISENESS]

Other options:
  -w, --with-timing
          Show the time taken by the function.

      [TRUNCATED FOR CONCISENESS]

Global options:
      --log-level <LOG_LEVEL>
          The log level. One of: `error`, `warn`, `info`, `debug`, or `trace`. Defaults to `warn`

New ways to install Jarl

Jarl is now available on PyPI and conda-forge, meaning that it can be installed by uv, mise, pixi, and potentially more tools.

It can also be installed via Homebrew. See the installation instructions for more info.

Conclusion

Jarl 0.6.0 brings many exciting features, try them out! If you find any issue, have feature ideas, or want to contribute, head to the Github repository.

I’m glad that this release got code contributions from seven people (in addition to myself), and I want to thank them and everyone else who contributed one way or another: @atsyplenkov, @Bisaloo, @christopherkenny, @dieghernan, @fh-mthomson, @gisler, @Goldziher, @hfrick, @ilyaZar, @JosephBARBIERDARNAL, @JosiahParry, @lwjohnst86, @maelle, @mattkerlogue, @njtierney, @novica, @playmobilmeister, @randy3k, @sebffischer, @snystrom, and @Yousa-Mirage.