RuboCopの新しいルールを追加する方法(Custom Copの作り方)
何度もレビューで指摘することはRuboCopのルールとして追加して、自動化しておくと便利です。
この記事では、RuboCopで新しいルールを作る方法を紹介します。
参考にしたページ
これがとても参考になった。
Copの書き方は独特なので、RuboCopリポジトリの lib/rubocop/cop
ディレクトリにある他Copをいくつか読むと参考になります。
Custom Cop の作り方
ここでは例としてMatzの名前を "Hiroyuki Matsumoto" と間違って書いたとき、検知できるCopを作ってみます。
まずは .rubocop.yml
に require
の行を追加します。
require: './lib/custom_cops/matz_name' AllCops: # 以下略...
次に、 lib/custom_cops/matz_name.rb
に新しいルールを書きます。
# frozen_string_literal: true module CustomCops # @example # # bad # MATZ_NAME = "Hiroyuki Matsumoto" # # # good # MATZ_NAME = "Yukihiro Matsumoto" class MatzName < RuboCop::Cop::Cop BAD_NAME = "Hiroyuki Matsumoto" GOOD_NAME = "Yukihiro Matsumoto" MSG = "Use '#{GOOD_NAME}' instead of '#{BAD_NAME}'." def on_str(node) add_offense(node) if node.source.include?(BAD_NAME) end def autocorrect(node) ->(corrector) { new_code = node.source.gsub(BAD_NAME, GOOD_NAME) corrector.replace(node.source_range, new_code) } end end end
いくつか独特なルールがあるので、簡単に説明します。
on_xxx
メソッドはRubyのコード内で該当するコードが見つかった時に呼ばれるメソッドです。
たくさんあるので興味ある人はRuboCopリポジトリのlib/rubocop/ast/traversal.rb
を読むと良いです。
基本的には「それっぽいメソッドを定義して、テスト書いてみて試す」とか「他Copの実装をパクる」で頑張ります。
add_offense
はルール違反を検知した時に呼び出します。
引数に渡している node
に対して、MSG
定数の文字列をメッセージとして表示します。
メッセージを動的に変えたい場合は message
オプションで変える事もできます。
add_offense(node, message: "foo")
autocorrect
メソッドは実装すると --auto-correct
に対応することができます。
コード例のように proc を返すようにする必要があります。
テストの書き方
spec/rails_helper.rb
で下記のようにCustom Copを読み込むコード、RSpecのヘルパーメソッドの読み込みを行います。
require "rubocop" Dir[Rails.root.join("lib/custom_cops/**/*.rb")].each { |f| require f } require "rubocop/rspec/support" RSpec.configure do |config| config.include(RuboCop::RSpec::ExpectOffense) end
RuboCopリポジトリのテストコードを参考にしながら、コードを書きます。
# frozen_string_literal: true require "rails_helper" RSpec.describe CustomCops::MatzName do subject(:cop) { described_class.new } it "registers an offense" do expect_offense(<<-RUBY.strip_indent) MATZ_NAME = "Hiroyuki Matsumoto" ^^^^^^^^^^^^^^^^^^^^ Use 'Yukihiro Matsumoto' instead of 'Hiroyuki Matsumoto'. RUBY end it "autocorrects" do new_source = autocorrect_source('MATZ_NAME = "Hiroyuki Matsumoto"') expect(new_source).to eq 'MATZ_NAME = "Yukihiro Matsumoto"' end end
できればRuboCopにプルリクを投げよう
もし有用なCopを実装できたら、RuboCopにプルリクを投げると良いです。
- RuboCopのメンバーがコードレビューしてくれる
- 世界中のRuboCopユーザーがIssue挙げたり、メンテしてくれる
などのメリットがあります。
普段からRuboCopを便利に使わせて頂いているので、たまには恩返しとしてプルリクを送りたいですね。
おわり
「人間が何度もレビューで指摘する」は真面目で良いことだとは思いますが、怠惰力が足りないです。
機械的にレビューできる箇所は機械に任せ、我々は人間にしかできない「自動化の難しいところのレビュー」や「ビールを飲みながらアニメを観る」などを頑張りたい。