LoginSignup
20
16

More than 5 years have passed since last update.

RuboCopのCustom Copを作る

Posted at

はじめに

rubocop-rspecを参考にCustom Copを作る方法です

今回はメソッド名がaaaであれば違反になるサンプルCopを作成します

# NG
def aaa
    1
end

Copの名前はBadMethodNameにして、gem化してみます

試したバージョン

  • Ruby - 2.1.1p76
  • RuboCop - 0.23.0
  • Bundler - 1.5.3

rubocop-sample

rubocop-sampleというGemを作成します

$ bundle gem rubocop-sample -t
      create  rubocop-sample/Gemfile
      create  rubocop-sample/Rakefile
      create  rubocop-sample/LICENSE.txt
      create  rubocop-sample/README.md
      create  rubocop-sample/.gitignore
      create  rubocop-sample/rubocop-sample.gemspec
      create  rubocop-sample/lib/rubocop/sample.rb
      create  rubocop-sample/lib/rubocop/sample/version.rb
      create  rubocop-sample/.rspec
      create  rubocop-sample/spec/spec_helper.rb
      create  rubocop-sample/spec/rubocop/sample_spec.rb
      create  rubocop-sample/.travis.yml
Initializing git repo in /Users/xxx/rubocop-sample

rubocop-sample.gemspec

RuboCopを追加しておきます

※ 0.19の時点でRubocopからRuboCopに変更した関係でバージョンを指定しているみたいです

spec.add_runtime_dependency('rubocop', '~> 0.19', '>= 0.19')

そのほかは適宜編集してください

編集後にbundle installで依存ライブラリをインストールしておきます

lib/rubocop-sample.rb

lib/rubocop/sample.rbをrequireするlib/rubocop-sample.rbを作成します

require 'rubocop/sample'

rubocop-rspecは実行時に--requireオプションを使って外部Copをrequireさせてます

その時にrubocop --require rubocop-sampleと実行できる為に作成しています、別にrubocop --require rubocop/sampleでもいい人ならいらないのかもしれません

※ Ruby歴数ヶ月だとこれ以上の意味がわかりませんでした

lib/rubocop/sample/inject.rb

RuboCopは外部Copを取り入れる仕組みがない?みたいなのでそれに対応するlib/rubocop/sample/inject.rbを作成します

RuboCopはCopリスト(rubocop/config/default.yml)をRuboCop::ConfigLoaderクラスの@default_configurationに保持しています

rubocop-rspecでは以下のような実装で@default_configurationを更新する事で対応していますのでそのまま実装します

module RuboCop
  module Sample
    module Inject
      DEFAULT_FILE = File.expand_path('../../../../config/default.yml'), __FILE__)

      def self.defaults!
        hash = YAML.load_file(DEFAULT_FILE)
        config = ConfigLoader.merge_with_default(hash, DEFAULT_FILE)
        ConfigLoader.instance_variable_set(:@default_configuration, config)
      end
    end
  end
end

config/default.yml

作成するCop名と説明(Description)と有効(Enabled)にしておきます

Style/BadMethodName:
  Description: 'aaaというメソッド名は利用してはいけない'
  Enabled: true

lib/rubocop/sample.rb

ruboco/sample.rbをrequireされた時点で作成したRuboCop::Sample::Inject.default!が実行されるようにします

require 'rubocop'

require 'rubocop/sample/version'
require 'rubocop/sample/inject'

RuboCop::Sample::Inject.defaults!

# 作成するCop
require 'rubocop/sample/cop/style/bad_method_name'

lib/rubocop/sample/cop/style/bad_method_name.rb

RuboCopはソースをASTに変換した後にParser::AST::Processoron_xxxを呼び出します

今回はメソッド名のチェックなのでon_def(インスタンスメソッド)を実装して名前をチェックします

module RuboCop
  module Cop
    module Style
      class BadMethodName < Cop
        def on_def(node)
          method_name, _other = *node
          if method_name.to_s == 'aaa'
            add_offense(node, :name, 'aaaは利用してはいけません!', :fatal)
          end
        end
      end
    end
  end
end

add_offenseに違反を追加します

※ 今回はサンプルの為そのまま実装しましたが、メソッドの違反チェック用にRuboCop::Cop::CheckMethodsが用意されているのでそれを利用した方がよいです

spec/spec_helper.rb

作成したgemとRuboCopのspec_helperをrequireしておきます

require File.join(Gem::Specification.find_by_name('rubocop').gem_dir, 'spec', 'spec_helper.rb')

spec/rubocop-sample.rb

そのままじゃエラーになるので修正しておきます(環境によって違うかも)

spec/rubocop/cop/style/bad_method_name_spec.rb

RuboCopのspec_helperをrequireした事でinspect_sourceが利用できるようになりますのでspecを書いていきます

require 'spec_helper'

describe RuboCop::Cop::Style::BadMethodName do
  subject(:cop) { RuboCop::Cop::Style::BadMethodName.new }

  it 'インスタンスメソッド名がaaaは利用できない' do
    inspect_source(cop, ['def aaa; 1;end'])
    expect(cop.offenses.size).to eq(1)
    expect(cop.messages).to eq(['aaaは利用してはいけません!'])
  end
end

rake spec

エラーが出なければOKです

$ rake spec
ruby -S rspec ./spec/rubocop/cop/style/bad_method_name_spec.rb ./spec/rubocop/sample_spec.rb

...
...

RuboCop::Cop::Style::BadMethodName
  インスタンスメソッド名がaaaは利用できない

Rubocop::Sample
  should have a version number

Finished in 0.00237 seconds
2 examples, 0 failures

rake install

作成したgemをローカルにインストールします

$ rake install
rubocop-sample 0.0.1 built to pkg/rubocop-sample-0.0.1.gem.
rubocop-sample (0.0.1) installed.

$ gem list rubocop-sample

*** LOCAL GEMS ***

rubocop-sample (0.0.1)

試す

src/sample.rb

class Sample
  def aaa
    1
  end
end

実行時には必ず--require rubocop-sampleオプションをつけて実行します

$ rubocop --require rubocop-sample --only BadMethodName sample.rb
Inspecting 1 file
F

Offenses:

test.rb:2:7: F: aaaは利用してはいけません!
  def aaa
      ^^^

1 file inspected, 1 offense detected

Links

20
16
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
20
16