Adding custom cops & auto-correct to RuboCop
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
MyCops
deparment. - 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 theon_return
method. Similarly, for anif
keyword, we can use theon_if
method, for a method, we can useon_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:
- Auto-correct must extend the
AutoCorrector
module. - The
corrector
should be a part of theadd_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:
- 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. - 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.