Friday, April 19, 2024
HomeRuby On RailsCreate a Customized Rubocop Cop - FastRuby.io

Create a Customized Rubocop Cop – FastRuby.io


Not too long ago I labored on a shopper challenge that required me to implement good code conventions throughout the challenge.
One of many duties moreover implementing the Rubocop commonplace cops was to put in writing a customized cop for 2 completely different Datetime strategies,
so on this article I’ll clarify how I created a customized Rubocop cop that solved that downside.

What’s rubocop?

RuboCop is a Ruby static code analyzer (a.okay.a. linter) and code formatter. Out of the field it is going to implement lots of the tips outlined in the neighborhood Ruby Model Information. Other than reporting the issues found in your code, RuboCop can even routinely repair a lot of them for you.

Why a customized cop?

You is likely to be questioning: “Why would I’ve a customized cop if out of the field Rubocop has many of the cops I’ll want?”

Nicely, there are occasions when the rubocop requirements usually are not sufficient to cowl group, firm or challenge requirements that you simply got here up with and also you wish to implement them in the identical means Rubocop enforces the Ruby requirements.

Making a cop for Datetime.now

On this part I’ll create a cop to verify the utilization of Datetime.now and suggest using Datetime.present as a substitute.

If you wish to be taught extra concerning the variations between Datetime present and now verify this text.

First I create a category that inherits from Rubocop::Cop::Base

module CustomCops
  class DateTimeNowUsage < RuboCop::Cop::Base
    def on_send(node)
      # do stuff with the AST node
    finish
  finish
finish

I’m puting our class contained in the CustomCops module simply to namespace issues and keep away from future identify collisions with Rubocop commonplace cops if ever…

The def_node_matcher technique

Rubocop has a macro referred to as def_node_matcher that receives a reputation and a sample to match the Ruby AST node you wish to mark as an “offense”.

There’s a number of methods to get the AST node matcher for def_node_matcher, I may use the Node Sample or just go the node supply string to it.

I used the ruby-parse gem to get the node supply string of my offensing code. i.e:

$ ruby-parse -e "Datetime.now"

(ship
  (const nil :Datetime) :now)

Then I exploit the output as a patttern in def_node_matcher

module CustomCops
  class DateTimeNowUsage < RuboCop::Cop::Base
    def_node_matcher :on_datetime_now, <<~PATTERN
      # ruby-parse output
      (ship (... :Datetime) :now)
    PATTERN

    def on_send(node)
      # do stuff with the AST node
    finish
  finish
finish

NOTE: I’m utilizing (ship (... :Datetime) :now) as a substitute of (ship (const nil :Datetime) :now). It’s because the const node, after I examined it, was really an Object as a substitute of nil, as ruby-parse confirmed us. I seen this as a result of the sample was not being matched by Rubocop after I tried to run the customized cop. With the ... it is going to match any node.

Add the offense

Now, when rubocop finds any occurence of Datetime.now I wish to add it as a “Rubocop offense”.

module CustomCops
  class DateTimeNowUsage < RuboCop::Cop::Base
    MSG = "You're utilizing `Datetime.now` please exchange it with `Datetime.present`"

    def_node_matcher :on_datetime_now, <<~PATTERN
      # ruby-parse output
      (ship (... :Datetime) :now)
    PATTERN

    def on_send(node)
      on_datetime_now(node) do
        add_offense(node, message: MSG)
      finish
    finish
  finish
finish

Auto appropriate

Okay. When you have adopted all of the steps it is best to have a customized Rubocop cop that may set off an offense whenever you use Datetime.now


$ rubocop --only CustomCops/DateTimeNowUsage

Inspecting 138 information
.........C.....................................................................................................C..........................

Offenses:

app/controllers/roadmap_payments_controller.rb:3:5: C: CustomCops/DateTimeNowUsage: You're utilizing Datetime.now please exchange it with Datetime.present
    Datetime.now
    ^^^^^^^^^^^^
^^^^^^^^^^^^

However we will make it higher, we may add the autocorrect characteristic that rubocop has inbuilt.

module CustomCops
  class DateTimeNowUsage < RuboCop::Cop::Base
    lengthen RuboCop::Cop::AutoCorrector

    MSG = "You're utilizing `Datetime.now` please exchange it with `Datetime.present`"

    def_node_matcher :on_datetime_now, <<~PATTERN
      (ship (... :Datetime) :now)
    PATTERN


    def on_send(node)
      on_datetime_now(node) do
        add_offense(node, message: MSG) do |corrector|
          corrector.exchange(node, "Datetime.present")
        finish
      finish
    finish
  finish
finish

Now, if we run our cop with the autocorrect flag, the cop will replace our code with Datetime.present:

✗ rubocop -A --only CustomCops/DateTimeNowUsage

Inspecting 138 information
.........C........................................................................................................................
........

Offenses:

app/controllers/roadmap_payments_controller.rb:3:5: C: [Corrected] CustomCops/DateTimeNowUsage: You're utilizing Datetime.now please exchange it with Datetime.present
    Datetime.now
    ^^^^^^^^^^^^

138 information inspected, 1 offense detected, 1 offense corrected

Conclusion

Studying about an AST and Rubocop internals may appear intimidating, however Rubocop has nice documentation. You may be taught extra about it right here.

Thanks for studying. I hope you discover this weblog publish useful!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments