LoginSignup
0
0

rubocop custom copの作成

Last updated at Posted at 2023-11-21

背景

Time.zone.nowTime.currentに変更したため、ついにルールとしてRuboCopに追加しました。

環境

❯ ./bin/rails --version
Rails 7.1.1

❯ bundle exec rubocop --version
1.57.2

Custom Copを追加

  • lib/custom_cops/ban_time_zone_now.rb

install parser (AST用)

❯ gem install parser
Fetching ast-2.4.2.gem
Fetching parser-3.2.2.4.gem
Successfully installed ast-2.4.2
Successfully installed parser-3.2.2.4
Parsing documentation for ast-2.4.2
Installing ri documentation for ast-2.4.2
Parsing documentation for parser-3.2.2.4
Installing ri documentation for parser-3.2.2.4
Done installing documentation for ast, parser after 26 seconds
2 gems installed

patternを定義する(Time.zone.now)

 ruby-parse -e 'Time.zone.now'
(send
  (send
    (const nil :Time) :zone) :now)

nilnil?にするべきなので、patternは下のようになります。

(send (send (const nil? :Time) :zone) :now)

RESTRICT_ON_SEND

これは特に定義しなくても動作しますが、RuboCopをより効率的に動かすために、定義しておくべきです。

定数 RESTRICT_ON_SEND は最適化のための特別な配列です。この中に含まれるメソッドが呼び出されたときだけ on_send を実行するように制限します。この制限がない場合、すべてのメソッドに対して on_send を呼び出し、パターンマッチの計算を行うために実行時間が増えてしまいます。

注意点

上記を見ると、zonenowのどちらかを入れても良さそうですが、実はzoneを使用すると問題があります。

ソースコードを読めば、これが理解できるはずです。下のnode.method_namenowであるためです。

 def operator?(node)
   (node.send_type? && RESTRICT_ON_SEND.include?(node.method_name)) || node.operator_keyword?
end

Custom Cop Class

# frozen_string_literal: true

return unless defined?(RuboCop)

module CustomCops
  class BanTimeZoneNow < RuboCop::Cop::Base

    def_node_matcher :time_zone_now_call?, <<~PATTERN
      (send (send (const nil? :Time) :zone) :now)
    PATTERN

    MSG = 'Do not use Time.zone.now. Use Time.current instead.'

    def on_send(node)
      # マッチしない場合は、そのままreturnします
      return unless time_zone_now_call?(node)

      add_offense(node, message: MSG)
    end
  end
end

Auto correctを追加

  • extend RuboCop::Cop::AutoCorrector
extend RuboCop::Cop::AutoCorrector
  • replace pattern
add_offense(node, message: MSG) do |corrector|
  corrector.replace(node, 'Time.current')
end

最終的なコード

# frozen_string_literal: true

return unless defined?(RuboCop)

module CustomCops
  class BanTimeZoneNow < RuboCop::Cop::Base
    extend RuboCop::Cop::AutoCorrector

    def_node_matcher :time_zone_now_call?, <<~PATTERN
      (send (send (const nil? :Time) :zone) :now)
    PATTERN

    MSG = 'Do not use Time.zone.now. Use Time.current instead.'
    RESTRICT_ON_SEND = [:now].freeze

    def on_send(node)
      # マッチしない場合は、そのままreturnします
      return unless time_zone_now_call?(node)

      add_offense(node, message: MSG) do |corrector|
        corrector.replace(node, 'Time.current')
      end
    end
  end
end

Custom Copを適用

  • .rubocop.yml
require:
  - rubocop-rails
  - ./lib/custom_cops/ban_time_zone_now.rb

CustomCops/BanTimeZoneNow:
  Enabled: true

テスト

 rubocop
Inspecting 241 files
................................................................................C................................................................................................................................................................

Offenses:

app/models/program.rb:46:47: C: [Correctable] CustomCops/BanTimeZoneNow: Do not use Time.zone.now. Use Time.current instead.
  scope :unexpired, -> { where('end_at >= ?', Time.zone.now) }
                                              ^^^^^^^^^^^^^

241 files inspected, 1 offense detected, 1 offense autocorrectable

参照

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0