RuboCopが静的解析のRubyバージョンを決める流れ

RuboCopが対象のRubyバージョンを決める方法は、探索順位によるため期待どおりランタイムの Ruby のバージョンと一致している場合以外に、条件によってはそうでないこともある。

.rubocop.yml の TargetRubyVersion の値、.ruby-version の値、Gemfile.lock に記された Ruby のバージョンを順番に参照して、それらに設定がない場合は RuboCop でデフォルトの Ruby バージョンとなっている 2.2 が適用される。実装としては以下。

github.com

これらの探索順で最初に見つかった値が RuboCop が静的解析の Ruby バージョンとなる。

リポジトリに .ruby-version を置いていないかつ rbenv などではなく Docker の Ruby イメージを使って、.rubocop.yml にも Gemfile.lock にも Ruby のバージョンの指定がない場合は、ランタイムの Ruby バージョンに関係なくデフォルトの Ruby 2.2 での静的解析が適用される。

どう困ることがあるかというと 、Ruby 2.3 以上のラインタイム環境で frozen_string_literal: true のマジックコメントがあるソースコードの定数に対して .freeze メソッドを付加するよう警告が意図せず出るようになる。Style/MutableConstant cop としては、Ruby 2.3 以上でそのマジックコメントがある場合は、警告を出さないのが本来の動き。

# frozen_string_literal: true

FOO = 'foo'
Offenses:

test.rb:3:7: C: Style/MutableConstant: Freeze mutable objects assigned to constants.
FOO = 'foo'
      ^^^^^

対策として、特定の Ruby バージョンで動けば良い Rails アプリケーションであれば .rubocop.yml の TargetRubyVersionRuby バージョンを指定すると良い。

それっぽい現象のレポートがあった事例としては、以下のイシューがある。

github.com

特殊な例だと (Ruby 2.2 以上の) 複数の Ruby バージョンをサポートする必要のある拡張 .rubocop.yml を複数の Ruby バージョンを Docker の Ruby イメージベースで動作検証したい場合 (例えば CircleCI 2 でのビルド) は、対象とする .ruby-version をビルド工程中に生成する設定を入れるなど考えられる。

これは盲点になりかねないので、ランタイムの Ruby バージョンとは別に、静的解析を適用する Ruby バージョンを利用者が分かるすべについては少し検討しているところ。