RuboCopの設定に書く相対パスはどこからの相対パスか?


RuboCopのv0.67で確認している話です。以前・以降のバージョンではもしかしたら違うかもしれないので注意してください。

document: https://docs.rubocop.org/en/latest/configuration/

TL;DR

inherit_fromに書く相対パス

inherit_fromが書かれた設定ファイルからの相対パス

Include/Excludeに書く相対パス

  • それが書かれた設定ファイルが.rubocopで始まるファイル名の場合
    • その設定ファイルからの相対パス
  • 設定ファイル名が.rubocopで始まらない場合
    • rubocopコマンドを実行したディレクトリからの相対パス

答えとしては上記で終わりですが、これがどういうケースで問題になりやすいのか(ハマりはやすいのか)、というのも書いているので良かったら最後まで見てください

RuboCopでパスが書ける部分

inherit_from: .rubocop_todo.yml

Style/CollectionMethods:
  Exclude:
    - 'foo/bar.rb'
  Include:
    - 'hoge/huga.rb'
  • inherit_from
    • 共通の設定ファイルなどを継承できる
  • Include/Exclude
    • Copを実行する対象・実行しない対象を宣言できる

RuboCopでは上記の2つにパスを書くことができます。
どちらも絶対パス・相対パスどちらでもかけますが、通常は相対パスになると思います。

ここで書く、相対パスはどこから見た相対パスなのか?という記事です。

inherit_fromに書く相対パス

こちらはとてもシンプルです。

inherit_fromに書く相対パスは、inherit_fromが書いてある設定ファイルからの相対パスになります。

.rubocop.yml
inherit_from: '../.rubocop_todo.yml'

例えば上記の場合、.rubocop.ymlの中で.rubocop_todo.ymlを読み込んでいますが、
これは、.rubocop.ymlからみて../にある.rubocop_todo.ymlを読み込みます。

treeでいうと以下のような関係になります。(baseというディレクトリ名は適当です。)

.
├── .rubocop_todo.yml
└── base
     └── .rubocop.yml

Include/Excludeに書く相対パス

こちらがややこしいです。

Include/Excludeの設定が書かれている設定ファイルの名前によって変わります!

  • ファイル名が.rubocopで始まる設定ファイルの場合
    • その設定ファイルからの相対パス
    • 例: .rubocop.yml.rubocop_todo.ymlなど
  • それ以外の設定ファイルの場合
    • rubocopコマンドを実行したディレクトリからの相対パス
    • 例: .my_rubocop_config.ymlrubocop.yml(ドットなし)など

具体例

.
├── config
│   ├── .my_rubocop_config.yml
│   ├── .rubocop_todo.yml
│   └── .rubocop.yml
└── src
     └── sample.rb

上記のようなディレクトリ構成があるとします。configディレクトリ以下に、3つのrubocopの設定ファイルがあります。

.rubocop.yml
inherit_from:
    - .my_rubocop_config.yml
    - .rubocop_todo.yml

.rubocop.ymlの中でほか2つの設定ファイルを読み込みます。

$ rubocop -c config/.rubocop.yml
rubocopコマンドは、ルートディレクトリから実行するとします。

上記のような前提とします。

この状態で、3つの設定ファイルそれぞれで、src/sample.rbをExcludeする設定を書きたいとします。

以下のようになります。

config/.rubocop.yml
# config/.rubocop_todo.yml
Style/CollectionMethods:
  Exclude:
    - '../src/sample.rb' # .rubocopで始まるので、設定ファイルからの相対
config/.rubocop_todo.yml
Style/CollectionMethods:
  Exclude:
    - '../src/sample.rb' # .rubocopで始まるので、設定ファイルからの相対
config/.my_rubocop_config.yml
Style/CollectionMethods:
  Exclude:
    - 'src/sample.rb' # .rubocopではないので、実行ディレクトリからの相対
    # or 
    # - '**/sample.rb'
  • ファイル名が.rubocopで始まる設定ファイルの場合
    • その設定ファイルからの相対パス
  • それ以外の設定ファイルの場合
    • rubocopコマンドを実行したディレクトリからの相対パス

なので、
config/.rubocop.ymlconfig/.rubocop_todo.ymlでは、../src/sample.rbというように../でたどる必要があります。
config/.my_rubocop_config.ymlは、rubocopコマンドを実行したディレクトリからの相対パスなので、'src/sample.rb'**/sample.rbとかけます。

なので、RuboCopの設定ファイルをtoolsやconfigディレクトリなどに置いて、rubocopコマンドはリポイ取りルートなどから実行するようなケースでは、
.rubocopから始まらない設定ファイルにするのをおすすめします。

どういう時に問題になりやすいか

RuboCopがデフォルトで読み込むのは.rubocop.ymlなので、この名前で設定ファイルを書かれている方が多いかと思うのですが、
.rubocop.ymlがリポジトリルート(またはrubocopコマンドを実行するディレクトリ)においてあるのであれば、問題になりません。

ですが、設定ファイルをconfigディレクトリやtools/rubocop等においてる場合に問題になりやすいです。

.
├── src
│   ├── admin_server
│   │   └── test
│   │       └── bar.rb
│   └── api_server
│       └── test
│           └── foo.rb
└── tool
    └── rubocop
        └── config
            └── rubocopの設定ファイル

上記のような構成にし、rubocopコマンドをリポジトリルートなどで実行する場合は、
設定ファイルは.rubocopで始めないほうが楽です。

rubocopの設定ファイル
Foo/BarCop:
    Exclude:
        - '**/test/**/*.rb'

testディレクトリではまるっと無視したいcopが時折あると思うのですが、その場合上記のように書けると楽です。
ですが、.rubocopで始まる設定ファイルの場合、
../../../src/**/test/**/*.rbというように適切に../で階層を戻る必要が出てしまいます。
rubocopで始まらない設定ファイルの場合は、
実行ディレクトリからの相対パスなので'**/test/**/*.rb'と簡単にかけます。

.rubocop.ymlをルートに置かない理由

Inculude/Excludeの相対パスがややこしい問題は、.rubocop.ymlをリポジトリルートに置けば解決なので通常はそれで全く問題ないと思っています。

ただ、さまざまな都合でtoolsやconfigディレクトリなどの下にRuboCopの設定ファイルを置きたいケースがあると思っています。

  • 一つのリポジトリの中に複数のアプリがあり、RuboCopの設定は共通にしたい
    • 上の方で例に上げたように、api_server, admin_serverのようケース
    • メインのアプリは一つだが、デプロイ・コード生成・バッチなどのような、メインアプリとは別の小さなアプリ・スクリプトでディレクトリを切っているケース
  • rubocop gemをアプリのGemfileに入れたくない・入れにくい
    • アプリの実行に必要なgemではない
    • 上の方で例に上げたように、api_server, admin_serverのようにアプリが複数あってそれぞれのGemfileに入れるのは微妙感ある
      • かといって、RuboCopのためだけにリポジトリルートに別途Gemfileを用意するのも微妙
  • rubocopをdockerで実行できるようにしておきたい
    • Gemfileにrubocopを入れない場合、各ユーザーがgem install rubocopするのも微妙なのでdockerで起動できるようにしておきたい
    • => dockerfileや便利スクリプトの置き場が必要
  • RuboCopに関するREADMEや設定のガイドラインを用意したい
    • RuboCopの実行の仕方・エディタで動かす方法などを書きたい
    • RuboCopの設定は揉めやすいのでガイドラインとしてドキュメント化しておきたい
    • => 設定ファイル・ドキュメントを一つのディレクトリにまとめたい

さまざまな都合が存在するかなと思います。
チームや状況によってもちろん異なると思いますが、.rubocop.ymlをリポジトリルートに置かないケースはそれなりにあるのかなと思います。

まとめ

inherit_fromに書く相対パス

  • inherit_fromが書かれた設定ファイルからの相対パス

Include/Excludeに書く相対パス

  • それが書かれた設定ファイルが.rubocopで始まるファイル名の場合
    • その設定ファイルからの相対パス
  • 設定ファイル名が.rubocopで始まらない場合
    • rubocopコマンドを実行したディレクトリからの相対パス
  • 設定ファイルをリポジトリルートに置かない場合は、.rubocopで始まらない設定ファイル名のほうが楽

おまけ

https://speakerdeck.com/vividmuimui/anatafalsezhi-ranairubocopfalseshe-ding
今回の話は、社内LTでの内容をリライトしたものです。メイン部分は同じ話ですが、違う設定に関する話もちょっと書いてあるので、よかったら見てください