Posted at

RuboCopのCustom Copを作る

More than 5 years have passed since last update.


はじめに

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