RuboCopの次のバージョンから、.rubocop.yml
の重複エントリが警告されます。Pull Requestはこちら。
Problem
RuboCopでは、.rubocop.yml
に重複したエントリがあることが原因であるIssueが度々上がってきていました。
例えば#6728などです。これは以下のような.rubocop.yml
が存在したと推測できます。
Metrics/BlockLength: Max: 50 Metrics/BlockLength: Max: 25
この.rubocop.yml
にはMetrics/BlockLength
が2回指定されています。RuboCopが使っているYAMLのパーサであるPsychを使ってPsych.load
とすると{"Metrics/BlockLength"=>{"Max"=>25}}
を返します。
つまり、先に指定されたMax: 50
は後に指定されたMax: 25
に上書きされてしまいます。1
警告やエラーも特に発生しないため、ユーザーは重複したエントリに気が付かない限り混乱してしまうでしょう。
Solution
このような重複をRuboCopが発見し警告することで、重複に気が付きやすくします。
現時点でのRuboCopのmaster(97e4ffc8a71e9e5239a927c6a534dfc1e0da917f)を使うと、この.rubocop.yml
に対してRuboCopは次のように警告します。
$ rubocop .rubocop.yml:1: `Metrics/BlockLength` is concealed by line 5 Inspecting 0 files 0 files inspected, no offenses detected
Implementation
重複コードの実装は30行程度のとても簡単なものです。
module RuboCop # Find duplicated keys from YAML. module YAMLDuplicationChecker def self.check(yaml_string, filename, &on_duplicated) # Specify filename to display helpful message when it raises an error. tree = YAML.parse(yaml_string, filename: filename) return unless tree traverse(tree, &on_duplicated) end def self.traverse(tree, &on_duplicated) case tree when Psych::Nodes::Mapping tree.children.each_slice(2).with_object([]) do |(key, value), keys| exist = keys.find { |key2| key2.value == key.value } on_duplicated.call(exist, key) if exist keys << key traverse(value, &on_duplicated) end else children = tree.children return unless children children.each { |c| traverse(c, &on_duplicated) } end end private_class_method :traverse end end
前述の通りPsych.load
を使うと重複エントリが取り除かれてしまうため、重複の判定を行うことは出来ません。そこでYAMLの構文木を得るPsych.parse
メソッドを使用しています。
そしてその構文木をトラバースし、重複したキーがあった場合はブロックを呼び出すようなコードになっています。
これによってRuboCopに同梱されている設定のYAMLファイルにも重複エントリがあることが見つかりました。2
RuboCopの他のYAMLを使ったツールにも、このような機能があると便利かもしれませんね。3 Psych本体に重複の検出が実装されていてもよいかもしれません。
-
https://github.com/rubocop-hq/rubocop/pull/6733#discussion_r253907838↩
-
このコードのライセンスはMITなので、比較的ご自由にお使いいただけます。 https://github.com/rubocop-hq/rubocop/blob/master/LICENSE.txt↩