RuboCopに出したPull Request #7272 について
きっかけ
ある日、有効にしたはずのないRuboCopのCopで違反が検知されていることに気づきました。原因は、以下スクショのように、EnabledにfalseではなくStringを渡していたためです。
自然言語的には正しいのですが(ん、本当にそうか?)、ここには true/false 以外を渡すべきではありません。
どこで検知するか
これは、そもそも Enabled という YAMLのkeyに対して true/false 以外の値を渡すべきではないので、どうにかそれを検知すればよいわけです。
ということで、RuboCopにおいて .rubocop.yml が読みこまれる過程をざっくりと追いかけます。
まず、 bin/exe/rubocop を見ると、RuboCop::CLI に処理の実体がありそうです。
その先、 rubocop/cli.rb 内で act_on_options という関数により、 ConfigStore を経由して ConfigLoader.load_file が呼ばれます。
その後、 load_yaml_configuration 内部でYAMLを読みこんでいます。ここに処理を挟んでみましょう。
どのように検知するか
.rubocop.yml は YAML.safe_load によってRubyのオブジェクトになります。この結果のHashを再帰的に掘っていき、 Enabled という key に対応する value が TrueClassでもFalseClassでもない場合に警告を発すればよさそうです。
という処理のために書いた、初期のコードがこれです。
def check_enabled_value(hash, parent=nil)
hash.each do |key, value|
check_enabled_value(value, key) if value.is_a?(Hash)
if key == 'Enabled' && value.class == String
warn Rainbow("Cop `#{parent}` is enabled by value '#{value}' specified. It may unexpected behavior unless set ture or false.").yellow
end
end
end
これによって、このように true/false でない値の場合に警告を出すようになります。
レビューによる仕様の変更
上記内容でPull Requestを作成したところ、bbatsov氏からレビューにてアドバイスを頂きました。そして最終的には以下のような仕様に変更を行いました。
・Enabled, Safe, SafeAutoCorrect, AutoCorrect の key について value を検証する
・true/false 以外の値が設定されていた場合には、エラーメッセージを出力して exit status 1 で終了する
def check_cop_config_value(hash, parent = nil)
hash.each do |key, value|
check_cop_config_value(value, key) if value.is_a?(Hash)
next unless %w[Enabled
Safe
SafeAutoCorrect
AutoCorrect].include?(key) && value.is_a?(String)
abort(
"Property #{Rainbow(key).yellow} of cop #{Rainbow(parent).yellow}" \
" is supposed to be a boolean and #{Rainbow(value).yellow} is not."
)
end
end
まとめ
そんなこんなで、僕のPull Requestが取り込まれたRuboCop v0.75.0 がリリースされました。
投げ銭していただけるととても嬉しいです