最近我参与了一个客户项目,该项目要求我在整个项目中实施良好的代码惯例。 除了实施Rubocop标准警察外,其中一项任务是为两个不同的Datetime 方法编写自定义警察,因此在本文中我将解释我如何创建一个自定义Rubocop警察来解决这个问题。
什么是Rubocop?
RuboCop是一个Ruby静态代码分析器(又称linter)和代码格式化器。开箱即用,它将执行社区Ruby风格指南中概述的许多准则。除了报告在你的代码中发现的问题,RuboCop还可以自动为你修复许多问题。
为什么要有一个自定义的cop?
你可能想知道。"如果开箱即用的Rubocop有大部分我需要的警察,我为什么要有一个自定义的警察?"
好吧,有些时候,rubocop的标准不足以涵盖你想出的组织、公司或项目的标准,你想用Rubocop执行Ruby标准的方式来执行它们。
为 Datetime.now 创建一个副本
在本节中,我将创建一个cop来检查Datetime.now 的使用情况,并建议使用Datetime.current 来代替。
首先,我创建了一个继承于Rubocop::Cop::Base
module CustomCops
class DateTimeNowUsage < RuboCop::Cop::Base
def on_send(node)
# do stuff with the AST node
end
end
end
我把我们的类放在
CustomCops模块里,只是为了给东西命名,避免将来与Rubocop标准警察的名字发生冲突,如果有的话。
def_node_matcher方法
Rubocop有一个叫做def_node_matcher 的宏,它接收一个名字和一个模式来匹配你想标记为 "冒犯 "的RubyAST节点。
有几种方法可以获得def_node_matcher 的AST节点匹配器,我可以使用Node Pattern或者简单地将node source 字符串传递给它。
我使用ruby-parse gem来获取我的offensing代码的节点源字符串。即。
$ gem intsall ruby-parse
$ ruby-parse -e "Datetime.now"
(send
(const nil :Datetime) :now)
然后我把输出作为一个patttern在def_node_matcher
module CustomCops
class DateTimeNowUsage < RuboCop::Cop::Base
def_node_matcher :on_datetime_now, <<~PATTERN
# ruby-parse output
(send (... :Datetime) :now)
PATTERN
def on_send(node)
# do stuff with the AST node
end
end
end
注意:我使用
(send (... :Datetime) :now),而不是(send (const nil :Datetime) :now)。这是因为const node,当我测试它时,实际上是一个Object,而不是nil,正如ruby-parse显示的那样。我注意到了这一点,因为当我试图运行自定义的cop时,该模式没有被Rubocop匹配。有了...,它就可以匹配任何节点。
增加违法行为
现在,当rubocop发现任何出现的Datetime.now ,我想把它添加为 "Rubocop的罪行"。
module CustomCops
class DateTimeNowUsage < RuboCop::Cop::Base
MSG = "You are using `Datetime.now` please replace it with `Datetime.current`"
def_node_matcher :on_datetime_now, <<~PATTERN
# ruby-parse output
(send (... :Datetime) :now)
PATTERN
def on_send(node)
on_datetime_now(node) do
add_offense(node, message: MSG)
end
end
end
end
自动更正
好的,如果你已经遵循了所有的步骤,你应该有一个自定义的Rubocop警察,当你使用时将会触发一个违法行为。Datetime.now
$ rubocop --only CustomCops/DateTimeNowUsage
Inspecting 138 files
.........C.....................................................................................................C..........................
Offenses:
app/controllers/roadmap_payments_controller.rb:3:5: C: CustomCops/DateTimeNowUsage: You are using Datetime.now please replace it with Datetime.current
Datetime.now
^^^^^^^^^^^^
^^^^^^^^^^^^
但我们可以让它变得更好,我们可以添加rubocop内置的autocorrect 功能。
module CustomCops
class DateTimeNowUsage < RuboCop::Cop::Base
extend RuboCop::Cop::AutoCorrector
MSG = "You are using `Datetime.now` please replace it with `Datetime.current`"
def_node_matcher :on_datetime_now, <<~PATTERN
(send (... :Datetime) :now)
PATTERN
def on_send(node)
on_datetime_now(node) do
add_offense(node, message: MSG) do |corrector|
corrector.replace(node, "Datetime.current")
end
end
end
end
end
现在,如果我们用自动更正标志运行我们的警察,警察将用Datetime.current 来更新我们的代码。
✗ rubocop -A --only CustomCops/DateTimeNowUsage
Inspecting 138 files
.........C........................................................................................................................
........
Offenses:
app/controllers/roadmap_payments_controller.rb:3:5: C: [Corrected] CustomCops/DateTimeNowUsage: You are using Datetime.now please replace it with Datetime.current
Datetime.now
^^^^^^^^^^^^
138 files inspected, 1 offense detected, 1 offense corrected
结论
学习AST和Rubocop的内部结构似乎令人生畏,但Rubocop有很好的文档。