はじめに
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::Processorのon_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