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
andnow
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 theconst node
, after I examined it, was really anObject
as a substitute ofnil
, 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!