如何创建一个自定义的Rubocop警察

125 阅读3分钟

最近我参与了一个客户项目,该项目要求我在整个项目中实施良好的代码惯例。 除了实施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有很好的文档。