Linting and Auto-formatting Ruby Code With RuboCop

honeybadger_staff

Honeybadger Staff

Posted on June 29, 2022

Linting and Auto-formatting Ruby Code With RuboCop

This article was originally written by Ayooluwa Isaiah on the Honeybadger Developer Blog.

Linting is the automated checking of source code for programmatic and stylistic errors. This checking is performed by a static code 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, 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 is straightforward through RubyGems:

$ gem install rubocop
Enter fullscreen mode Exit fullscreen mode

Check the version that was installed:

$ rubocop --version
1.18.3
Enter fullscreen mode Exit fullscreen mode

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.

gem 'rubocop', require: false
Enter fullscreen mode Exit fullscreen mode

Check the version that was installed:

$ bundle exec rubocop --version
1.18.3
Enter fullscreen mode Exit fullscreen mode

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.

$ bundle exec rubocop
$ bundle exec rubocop src/lib
Enter fullscreen mode Exit fullscreen mode

Without any configuration, RuboCop enforces many of the guidelines outlined in the community-driven Ruby Style 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

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]).

$ bundle exec rubocop -a
Enter fullscreen mode Exit fullscreen mode

Terminal showing the RuboCop autocorrect feature

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. If you want to autocorrect these offenses as well, use the -A or --auto-correct-all flag.

$ bundle exec rubocop -A
Enter fullscreen mode Exit fullscreen mode

Terminal showing RuboCop autocorrecting unsafe cops

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 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 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, and each one is responsible for detecting a specific offense. The available cops are also grouped into the following departments:

  • Style cops are mostly based on the aforementioned Ruby Style Guide, and they check the consistency of your code.
  • Layout cops catch issues related to formatting, such as the use of white space.
  • Lint cops detect possible errors in your code, similar to ruby -w, but with a host of additional checks.
  • Metric cops deals with issues related to source code measurements such as class length and method length.
  • Naming cops are concerned with naming conventions.
  • Security cops help with catching potential security issues.
  • Bundler cops check for bad practices in Bundler files (such as Gemfile).
  • Gemspec cops 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 or take advantage of existing ones if they are relevant to your project. For example, a Rails extension is available for the purpose of enforcing Rails best practices and coding conventions.

RuboCop messages

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.

# .rubocop.yml
AllCops:
  NewCops: enable
Enter fullscreen mode Exit fullscreen mode

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. 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 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.

# Gemfile
gem "standard", group: [:development, :test]
Enter fullscreen mode Exit fullscreen mode

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

$ bundle exec standardrb
Enter fullscreen mode Exit fullscreen mode

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.

$ bundle exec rubocop

523 files inspected, 1018 offenses detected
Enter fullscreen mode Exit fullscreen mode

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

$ bundle exec rubocop --auto-gen-config
Added inheritance from `.rubocop_todo.yml` in `.rubocop.yml`.
Created .rubocop_todo.yml.
Enter fullscreen mode Exit fullscreen mode

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.

$ bundle exec rubocop
523 files inspected, no offenses detected
Enter fullscreen mode Exit fullscreen mode

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.

$ bundle exec rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 10000
Enter fullscreen mode Exit fullscreen mode

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 and Airbnb. Utilizing your preferred style guide in RuboCop can be achieved by adding the relevant gem to your Gemfile:

# Gemfile
gem "rubocop-shopify", require: false
Enter fullscreen mode Exit fullscreen mode

Then, require it in your project configuration:

# .rubocop.yml
inherit_gem:
  rubocop-shopify: rubocop.yml
Enter fullscreen mode Exit fullscreen mode

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 with a comment in the source code. You can mention the individual cops or departments to be disabled, as shown below:

# rubocop:disable Layout/LineLength, Style
[..]
# rubocop:enable Layout/LineLength, Style
Enter fullscreen mode Exit fullscreen mode

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

# rubocop:disable all
[..]
# rubocop:enable all
Enter fullscreen mode Exit fullscreen mode

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

for x in (0..10) # rubocop:disable Style/For
Enter fullscreen mode Exit fullscreen mode

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 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 extension and place the following in your user settings.json file:

{
  "ruby.lint": {
    "rubocop": true
  }
}
Enter fullscreen mode Exit fullscreen mode

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

{
  "coc.preferences.formatOnSaveFiletypes": ["ruby"],
  "solargraph.autoformat": true,
  "solargraph.diagnostics": true,
  "solargraph.formatting": true
}
Enter fullscreen mode Exit fullscreen mode

RuboCop integration in Neovim

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, 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:

$ gem install overcommit
$ overcommit --install # at the root of your project
Enter fullscreen mode Exit fullscreen mode

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, 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:

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

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 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

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:

$ SKIP=RuboCop git commit -m "WIP: Unfinished work"
Enter fullscreen mode Exit fullscreen mode

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.

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:

# .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
Enter fullscreen mode Exit fullscreen mode

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

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 started out as an opinionated code formatter for JavaScript, but it now supports many other languages, including Ruby. Installing its Ruby plugin is straight forward: add the prettier gem to your Gemfile and then run bundle.

# Gemfile
gem 'prettier'
Enter fullscreen mode Exit fullscreen mode

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

$ bundle exec rbprettier --write '**/*.rb'
Enter fullscreen mode Exit fullscreen mode

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 at the top of your project's .rubocop.yml file:

# .rubocop.yml
inherit_gem:
  prettier: rubocop.yml
Enter fullscreen mode Exit fullscreen mode

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 through its configuration file, which can be shared between JavaScript and Ruby code in the same project.

RubyFmt

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!

💖 💪 🙅 🚩
honeybadger_staff
Honeybadger Staff

Posted on June 29, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related