Linting and Auto-formatting Ruby Code With RuboCop
Tags: ruby
Published: 2022-06-29 17:20:09

*This article was originally written by [Ayooluwa Isaiah](https://www.honeybadger.io/blog/authors/ayoisaiah/) on the [Honeybadger Developer Blog](https://www.honeybadger.io/blog/linting-formatting-ruby/).*

Linting is the automated checking of source code for programmatic and stylistic errors. This checking is performed by a [static code analysis](https://en.wikipedia.org/wiki/Static_program_analysis) tool called a linter. A code formatter, however, is a tool concerned with formatting source code so that it strictly adheres to a pre-configured set of rules. A linter will typically report violations, but it's usually up to the programmer to fix the problem, while a code formatter tends to apply its rules directly to the source code, thus correcting formatting mistakes automatically.

The task of creating a more consistent code style in a project usually necessitates the introduction of a separate linting and formatting tools, but in some cases, a single tool will be capable of addressing both concerns. A good example of the latter is [RuboCop](https://rubocop.org/), which is the tool we'll consider extensively in this article. You'll learn how to set it up in your Ruby project and adjust its configuration options so that its output matches your expectations. Aside from integrating it into your local development process, you'll also learn how to make it a part of your continuous integration workflow.

## Installing RuboCop

[Installing RuboCop](https://docs.rubocop.org/rubocop/1.18/installation.html) is straightforward through RubyGems:

```bash
$ gem install rubocop
```

Check the version that was installed:

```bash
$ rubocop --version
1.18.3
```

If you'd rather use Bundler, place the snippet below in your `Gemfile` and then run `bundle install`. The `require: false` part tells `Bundler.require` not to require that specific gem in your code since it will only be used from the command line.

```plaintext
gem 'rubocop', require: false
```

Check the version that was installed:

```bash
$ bundle exec rubocop --version
1.18.3
```

## Running RuboCop

You can run RuboCop using its default settings on your project by typing `rubocop` (or `bundle exec rubocop` if installed with Bundler). If you don't pass any arguments to the command, it will check all the Ruby source files in the current directory, as well as all sub directories. Alternatively, you may pass a list of files and directories that should be analyzed.

```bash
$ bundle exec rubocop
$ bundle exec rubocop src/lib
```

Without any configuration, RuboCop enforces many of the guidelines outlined in the community-driven [Ruby Style Guide](https://rubystyle.guide). After running the command, you may get several errors (offenses). Each reported offense is decorated with all the information necessary to resolve it, such as a description of the offense, and the file and line number where it occurred.

![Terminal showing RuboCop offenses](https://www.honeybadger.io/images/blog/posts/linting-formatting-ruby/rubocop-errors.png)

At the bottom of the report, you'll see a line describing the number of inspected files, the total number of offenses, and how many of the offenses can be fixed automatically. If you append the `-a` or `--auto-correct` argument, RuboCop will try to automatically correct the problems found in your source files (those prefixed with `[Correctable]`).

```bash
$ bundle exec rubocop -a
```

![Terminal showing the RuboCop autocorrect feature](https://www.honeybadger.io/images/blog/posts/linting-formatting-ruby/rubocop-errors-corrected.png)

Notice how each corrected offense is now prefixed with `[Corrected]`. A summary of the number of corrected offenses is also presented at the bottom of the report. In the above example, there's another correctable offense that wasn't auto-fixed, even after appending the `-a` flag. This is because some automatic corrections might change the semantics of the code slightly, so RuboCop considers it to be [unsafe](https://docs.rubocop.org/rubocop/1.18/usage/auto_correct.html#safe-auto-correct). If you want to autocorrect these offenses as well, use the `-A` or `--auto-correct-all` flag.

```bash
$ bundle exec rubocop -A
```

![Terminal showing RuboCop autocorrecting unsafe cops](https://www.honeybadger.io/images/blog/posts/linting-formatting-ruby/rubocop-errors-correct-all.png)

A good rule of thumb to follow is to run your test suite after using the autocorrect functionality to ensure that the behavior of your code hasn't changed unexpectedly.

## Configuring RuboCop

RuboCop can be [configured](https://docs.rubocop.org/rubocop/1.18/configuration.htmlhttps://docs.rubocop.org/rubocop/1.18/configuration.html) through a `.rubocop.yml` file placed at the root of your project. If you want to use the same checks for all projects, you can place a global config file in your home directory (`~/.rubocop.yml`) or XDG config directory (`~/.config/rubocop/config.yml`). This global config file will be used if a locally scoped project configuration file is not found in the current directory or successive parent directories.

The [default configuration](https://github.com/rubocop/rubocop/blob/master/config/default.yml) for RuboCop is placed in its configuration home directory (`~/.config/rubocop/default.yml`), and all other config files inherit from it. This means that when setting up your project configuration, you only need to make changes that are different from the default. This could mean enabling or disable certain checks or altering their behavior if they accept any parameters.

RuboCop refers to each individual check as [cops](https://docs.rubocop.org/rubocop/1.18/cops.html), and each one is responsible for detecting a specific offense. The available cops are also grouped into the following departments:

- [Style cops](https://docs.rubocop.org/rubocop/1.18/cops_style.html) are mostly based on the aforementioned Ruby Style Guide, and they check the consistency of your code.
- [Layout cops](https://docs.rubocop.org/rubocop/1.18/cops_layout.html) catch issues related to formatting, such as the use of white space.
- [Lint cops](https://docs.rubocop.org/rubocop/1.18/cops_lint.html) detect possible errors in your code, similar to `ruby -w`, but with a host of additional checks.
- [Metric cops](https://docs.rubocop.org/rubocop/1.18/cops_metrics.html) deals with issues related to source code measurements such as class length and method length.
- [Naming cops](https://docs.rubocop.org/rubocop/1.18/cops_naming.html) are concerned with naming conventions.
- [Security cops](https://docs.rubocop.org/rubocop/1.18/cops_security.html) help with catching potential security issues.
- [Bundler cops](https://docs.rubocop.org/rubocop/1.18/cops_bundler.html) check for bad practices in Bundler files (such as `Gemfile`).
- [Gemspec cops](https://docs.rubocop.org/rubocop/1.18/cops_gemspec.html) check for bad practices in `.gemspec` files.

It's also possible to extend RuboCop through additional linters and formatters. You can [build your own extensions](https://docs.rubocop.org/rubocop/1.18/development.html) or take advantage of [existing ones](https://docs.rubocop.org/rubocop/1.18/extensions.html#cop-extensions) if they are relevant to your project. For example, a [Rails extension](https://github.com/rubocop/rubocop-rails) is available for the purpose of enforcing Rails best practices and coding conventions.

![RuboCop messages](https://www.honeybadger.io/images/blog/posts/linting-formatting-ruby/rubocop-allcops.png)

When you create your configuration file for the first time, you'll get a slew of messages warning you about the presence of new cops that were added but not configured. This is because RuboCop adds new cops on each release, and these are set to a special pending status until they are explicitly enabled or disabled in the user configuration. You can enable or disable each of the listed cops in the message individually or use the snippet below to enable all new cops (recommended). Afterwards, the messages will be suppressed.

```yaml
# .rubocop.yml
AllCops:
  NewCops: enable
```

If you don't want to fiddle with configuration files and the wealth of options provided by RuboCop, consider taking a look at the [Standard project](https://github.com/testdouble/standard). It's largely a pre-configured version of RuboCop that aims to enforce a consistent style in your Ruby project without allowing the customization of any of its rules. The [lightning talk](https://www.youtube.com/watch?v=uLyV5hOqGQ8) where it was first announced gives more details about its origin and motivations.

You can install it by adding the following line to your `Gemfile` and then run `bundle install`.

```plaintext
# Gemfile
gem "standard", group: [:development, :test]
```

Afterwards, you can execute Standard from the command line as follows:

```bash
$ bundle exec standardrb
```

## Adding RuboCop to an Existing Project

Most Rubyists do not have the luxury of working on greenfield projects. Much of our development time is spent in legacy codebases that may produce an overwhelming amount of linting offenses that cannot be tackled immediately. Fortunately, RuboCop has a useful feature that generates an allowlist of existing offenses, which can be addressed slowly over time. The benefit is that it allows you to introduce linting to your existing project without being bombarded with a mountain of unmanageable linting errors while flagging any new violations going forward.

```bash
$ bundle exec rubocop

523 files inspected, 1018 offenses detected
```

Creating the allowlist config file can be done through the command below:

```bash
$ bundle exec rubocop --auto-gen-config
Added inheritance from `.rubocop_todo.yml` in `.rubocop.yml`.
Created .rubocop_todo.yml.
```

The `--auto-gen-config` option collects all offenses and their counts and generates a `.rubocop_todo.yml` file in the current directory where all the current offenses are ignored. Finally, it causes `.rubocop.yml` to inherit from the `.rubocop_todo.yml` file so that running RuboCop on the codebase once again will not yield any offenses.

```bash
$ bundle exec rubocop
523 files inspected, no offenses detected
```

While generating the allowlist file, RuboCop will turn off a cop altogether if the number of violations exceeds a certain threshold (15 by default). This is typically not what you want because it prevents new code from being checked against that cop due to the number of existing violations. Fortunately, it's possible to increase the threshold so that cops are not disabled even if the number of violations is high.

```bash
$ bundle exec rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 10000
```

The `--auto-gen-only-exclude` option ensures that each cop in the allowlist has an `Exclude` block that lists all the files where a violation occurred, instead of `Max`, which sets the maximum number of excluded files for a cop. Setting the `--exclude-limit` also changes the maximum number of files that can be added to the `Exclude` block for each cop. Specifying an arbitrary number larger than the total number of files being examined ensures that no cops will be disabled outright, and any new code added to existing or new files will be checked accordingly.

### Fixing Existing Violations

After generating the `.rubocop_todo.yml` file, it's important not to forget the existing violations, but to slowly address them one after the other. You can do this by removing a file from the `Exclude` block of a cop, then fix the reported violations, run your test suite to avoid introducing bugs, and commit. Once you've removed all the files from a cop, you can delete the cop from the file manually, or regenerate the allowlist file once again. Don't forget to utilize the `--auto-correct` option where possible to make the process much faster.

## Adopting a Style Guide

RuboCop is very configurable, which makes it viable for any type of project. However, it may take a long time to configure the rules to your requirements, especially if you disagree with many of the default rules. In such circumstances, adopting an existing style guide may be beneficial. Several companies have already released their Ruby style guides for public consumption, such as [Shopify](https://ruby-style-guide.shopify.dev) and [Airbnb](https://ruby-style-guide.shopify.dev). Utilizing your preferred style guide in RuboCop can be achieved by adding the relevant gem to your `Gemfile`:

```plaintext
# Gemfile
gem "rubocop-shopify", require: false
```

Then, require it in your project configuration:

```yaml
# .rubocop.yml
inherit_gem:
  rubocop-shopify: rubocop.yml
```

## Suppressing Linting Errors

Although RuboCop is great tool, it can yield false positives from time to time or suggest fixing the code in a way that is detrimental to the intent of the programmer. When such situations arise, you can [ignore the violation](https://docs.rubocop.org/rubocop/1.18/configuration.html#disabling-cops-within-source-code) with a comment in the source code. You can mention the individual cops or departments to be disabled, as shown below:

```ruby
# rubocop:disable Layout/LineLength, Style
[..]
# rubocop:enable Layout/LineLength, Style
```

Or you can disable all cops for a section of the code in one fell swoop:

```ruby
# rubocop:disable all
[..]
# rubocop:enable all
```

If you use an end of line comment, the specified cops will be disabled on that line alone.

```ruby
for x in (0..10) # rubocop:disable Style/For
```

## Editor Integration

It's handy to view the warnings and errors produced by RuboCop as you type code in the editor instead of having to run the checks through the command line every time. Thankfully, [RuboCop integration](https://docs.rubocop.org/rubocop/1.18/integration_with_other_tools.html) is available in most of the popular code editors and IDEs, mostly through third-party plugins. In Visual Studio Code, all you need to do is install this [Ruby](https://marketplace.visualstudio.com/items?itemName=rebornix.Ruby) extension and place the following in your user `settings.json` file:

```json
{
  "ruby.lint": {
    "rubocop": true
  }
}
```

If you use Vim or Neovim, you can display RuboCop's diagnostics through [coc.nvim](https://github.com/neoclide/coc.nvim). You need to install the [Solargraph](https://solargraph.org) language server (`gem install solargraph`), followed by the [coc-solargraph extension](https://github.com/neoclide/coc-solargraph) (`:CocInstall coc-solargraph`). Afterwards, configure your `coc-settings.json` file as shown below:

```json
{
  "coc.preferences.formatOnSaveFiletypes": ["ruby"],
  "solargraph.autoformat": true,
  "solargraph.diagnostics": true,
  "solargraph.formatting": true
}
```

![RuboCop integration in Neovim](https://www.honeybadger.io/images/blog/posts/linting-formatting-ruby/rubocop-vim.png)

## Setting Up a Pre-commit Hook

A great way to ensure that all Ruby code in a project is linted and formatted properly before being checked into source control is by setting up a Git pre-commit hook that runs RuboCop on each staged file. This article will show you how to set it up with [Overcommit](https://github.com/sds/overcommit), a tool for managing and configuring Git pre-commit hooks, but you can also integrate RuboCop with other tools if you already have an existing pre-commit workflow.

First, install Overcommit through RubyGems and then install it in your project:

```bash
$ gem install overcommit
$ overcommit --install # at the root of your project
```

The second command above will create a repo-specific settings file (`.overcommit.yml`) in the current directory and back up any existing hooks. This file extends the [default configuration](https://github.com/sds/overcommit/blob/master/config/default.yml), so you only need to specify your configuration with respect to the defaults. For example, you may enable the RuboCop pre-commit hook through the following snippet:

```yml
# .overcommit.yml
PreCommit:
  RuboCop:
    enabled: true
    on_warn: fail
    problem_on_unmodified_line: ignore
    command: ['bundle', 'exec', 'rubocop']
```

The `on_warn: fail` setting causes Overcommit to treat warnings as failures, while `problem_on_unmodified_line: ignore` causes warnings and errors on lines that were not staged to be ignored. You can browse all the available [hook options](https://github.com/sds/overcommit#hook-options) and their range of acceptable values on the project's GitHub page. You may need to run `overcommit --sign` after changing your configuration file for the changes to take effect.

![Terminal showing RuboCop pre-commit in action](https://www.honeybadger.io/images/blog/posts/linting-formatting-ruby/rubocop-precommit.png)

Occasionally, if you want to commit a file that does not pass all the checks (such as a work-in-progress), you can skip individual checks on a case-by-case basis:

```bash
$ SKIP=RuboCop git commit -m "WIP: Unfinished work"
```

## Adding RuboCop to Your CI Workflow

Running RuboCop checks on each pull request is another way to prevent badly formatted code from being merged into your project. Although you can set it up with any CI tool, this article will only discuss how to run RuboCop through [GitHub Actions](https://github.com/features/actions).

The first step is to create a `.github/workflows` directory at the root of your project and a `rubocop.yml` file within the new directory. Open up the file in your editor and update it as follows:

```yaml
# .github/workflows/rubocop.yml
name: Lint code with RuboCop

on: [push, pull_request]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [macos-latest, ubuntu-latest, windows-latest]

    steps:
    - uses: actions/checkout@v2

    - name: Setup Ruby
      uses: ruby/setup-ruby@v1
      with:
        ruby-version: '3.0'
        bundler-cache: true

    - name: Run RuboCop
      run: bundle exec rubocop
```

The workflow file above describes a single job that will be executed when code is pushed to GitHub or when a pull request is made against any branch. A `job` is a sequence of steps that run sequentially. This particular job will run once on the latest Ubuntu, MacOS, and Windows versions provided by GitHub Actions (as defined by `runs-on` and `strategy.matrix`). The first step checks out the code in the repository, while the next one sets up the Ruby tool chain and dependencies, and the last one executes RuboCop.

Once you're done editing the file, save it, commit, and push to GitHub. Afterwards, you’ll get an inline display of any reported issues on subsequent check-ins and pull requests.

![GitHub Actions failing checks](https://www.honeybadger.io/images/blog/posts/linting-formatting-ruby/rubocop-ci.png)

## Alternative Auto-formatters

Although RuboCop provides comprehensive auto-formatting capabilities, it's also important to be aware of alternative tools in case it doesn't fulfill your needs adequately.

### Prettier

[Prettier](https://prettier.io/) started out as an opinionated code formatter for JavaScript, but it now supports many other languages, including Ruby. Installing its [Ruby plugin](https://github.com/prettier/plugin-ruby) is straight forward: add the `prettier` gem to your `Gemfile` and then run `bundle`.

```plaintext
# Gemfile
gem 'prettier'
```

At this point, you can format your Ruby code with Prettier through the following command:

```bash
$ bundle exec rbprettier --write '**/*.rb'
```

Some of Prettier's rules conflict with RuboCop's, so it's necessary to disable the formatting checks of the latter so that it does not interfere with Prettier. Luckily, it's easy to turn off the RuboCop checks that conflict or are unnecessary with Prettier. All you need to do is inherit [Prettier's RuboCop configuration](https://github.com/prettier/plugin-ruby#configuration) at the top of your project's `.rubocop.yml` file:

```yaml
# .rubocop.yml
inherit_gem:
  prettier: rubocop.yml
```

When you run RuboCop (with `bundle exec rubocop`) from now on, it won't report any layout related offenses, paving the way for Prettier to correct them according to its own rules. You can also [configure Prettier's output](https://github.com/prettier/plugin-ruby#configuration) through its [configuration file](https://prettier.io/docs/en/configuration.html), which can be shared between JavaScript and Ruby code in the same project.

### RubyFmt

[RubyFmt](https://github.com/penelopezone/rubyfmt) is a brand-new code formatter that's written in Rust and currently under active development. Like Prettier, it is intended to be a formatter and not a code analysis tool. It hasn't seen a stable release just yet, so you should probably hold off on adopting it right now, but it's definitely one to keep an eye on.

## Conclusion

Linting and auto-formatting code brings so many benefits to a code base, especially in the context of a team of developers. Even if you don't like being told how to format your code, you need to keep in mind that linting isn't just for you. It is also for the other people you collaborate with so that everyone can stick to the same conventions, thus eliminating the drawbacks of dealing with multiple coding styles in the same project.

It's also important not to treat the linter's output as gospel, so endeavor to configure it in a way that provides the most benefits to you without distracting from your main objectives. With RuboCop's extensive configuration settings, this should not be a problem. However, if you find that configuring RuboCop is taking too much time of your time, you can use a predefined style guide, as discussed earlier, or adopt Standard for a no-config alternative that everyone can just use instead of worrying about the little details.

Thanks for reading, and happy coding!