Adding custom cops & auto-correct to RuboCop

Ravi Teja Gannavarapu
3 min readDec 20, 2020

RuboCop is a powerful static code analysis tool, but sometimes you just might feel the need of adding a custom cop to check a specific type of patterns or conditions in your code, and preferably auto-correct it. Luckily, adding custom cops & auto-corrects to RuboCop is super easy.

In this article, we’ll be implementing a custom cop that will catch return statements with arguments at the top level.

  • First, RuboCop has this concept of department for cops, wherein they are usually divided based on the type of offense they catch. The departments include Lint, Style, Layout, Security, Performance, etc. But for our cop, we will include it into the MyCopsdeparment.
  • RuboCop provides AST for the source file, and it also provides great methods to run a cop at the right keywords or conditions or patterns. For example, if we want to check for the occurrence of the return keyword, we can use the on_return method. Similarly, for an if keyword, we can use the on_if method, for a method, we can use on_def method and so on. These are automatically invoked when their corresponding keywords appear in the code.

Coming back to our custom cop, we do stuff when we encounter the return keyword. And as you might have guessed, we write our code inside the on_return method. add_offense method is used to report an offense (or in simpler terms, highlight the error).

In the above snippet, we add_offense if the return_node has no arguments and has no ancestors that belong to the block, def, defs type (it should be present at the root level, remember?).

To better understand what’s written above, let’s look at a code snippet.

The above code, in AST, translates to:

s(:begin,
s(:return,
s(:int, 1),
s(:int, 2),
s(:int, 3)),
s(:return),
s(:def, :some_method,
s(:args),
s(:begin,
s(:send, nil, :do_something_here),
s(:return,
s(:str, "a_string")))))

As we can see, the first return statement has three arguments as its children, whereas the second one has none, and the third one has one argument. The third return statement is inside a def block and thus is not at the top level.

This explains why we add_offense when return statement has arguments and is not inside a block.

Now, let’s go ahead and implement an auto-correct for this. Some points to note while implementing an auto-correct:

  1. Auto-correct must extend the AutoCorrector module.
  2. The corrector should be a part of the add_offense method.

Let’s implement it now:

Here, we see that the both the above points have been taken care of. Also, in case the auto-correct functionality is huge, you can move it all into a private method and pass on the arguments (corrector, node) into it, like so:

We are done implementing the logic for catching offenses and auto-correcting them. To test this code on a file, run RuboCop by requiring our custom cop file, like so:

rubocop --require ./custom_cop.rb --only MyCops/ReturnWithArgumentAtTopLevel <filename.rb>

To run auto-correct on the above file, just run RuboCop with the --auto-correct flag:

rubocop --auto-correct --require ./custom_cop.rb --only MyCops/ReturnWithArgumentAtTopLevel <filename.rb>

Few points worth mentioning:

  1. You might run into errors while development of the cop. Enable the debug mode using the -d flag to view the error in a detailed way.
  2. RuboCop caches the results, which saves time by avoiding re-inspecting the file, and instead re-using the stored results from previous runs. I prefer disabling cache when testing my custom cops. To disable cache, use --cache false flag.

To sum it up, use the following command:

rubocop -d --cache false --auto-correct --require ./custom_cop.rb --only MyCops/ReturnWithArgumentAtTopLevel <filename.rb>

Feel free to suggest any changes! I hope this helps.

PS: The cop we implemented above, is now part of RuboCop, and thus you’ll find this error popping up already while using RuboCop either via CLI or in text editors with appropriate plugins installed. You can find the concerned PR here.

--

--