pockestrap

Programmer's memo

RuboCopの次のバージョンから、.rubocop.ymlの重複エントリが警告されます

RuboCopの次のバージョンから、.rubocop.ymlの重複エントリが警告されます。Pull Requestはこちら。

github.com

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行程度のとても簡単なものです。

https://github.com/rubocop-hq/rubocop/blob/97e4ffc8a71e9e5239a927c6a534dfc1e0da917f/lib/rubocop/yaml_duplication_checker.rb

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本体に重複の検出が実装されていてもよいかもしれません。


  1. 多分YAMLの仕様だと思いますが、仕様を読んでないので詳しくはわかりません。

  2. https://github.com/rubocop-hq/rubocop/pull/6733#discussion_r253907838

  3. このコードのライセンスはMITなので、比較的ご自由にお使いいただけます。 https://github.com/rubocop-hq/rubocop/blob/master/LICENSE.txt