RuboCop 0.51.0 のCHANGELOGを読む
RuboCop のバージョン 0.51.0 がリリースされました。
https://github.com/bbatsov/rubocop/releases/tag/v0.51.0
CHANGELOG から変更を見ていこうと思います。
破壊的変更
古い Ruby のサポート終了
RuboCop 0.51.0 から、古いバージョンのRubyのサポートが終了しました。
RuboCopに関係するRubyのバージョンには、2つの概念があります。
一つは、RuboCopを実行するのに使うRubyのバージョンです。これは、rubocop
コマンドを実行した時に走るRubyインタプリタのバージョンになります。
もう一つは、RuboCopが解析を行う際に想定するRubyのバージョンです。
これは、例えばWebアプリケーションであればそのアプリケーションを実行するRubyのバージョンになるでしょうし、ライブラリであればそのライブラリがサポートする中で一番低いRubyのバージョンになります。
このバージョンは、.rubocop.yml
内でTargetRubyVersion
を使用して設定することが可能です。
例
AllCops: TargetRubyVersion: 2.4
今回はこの両方のバージョンが、2.1まで引き上げられました。
つまり、RuboCop 0.51.0からはバージョン2.1未満のRubyを使用してRuboCopを実行することが出来ません。
また、アプリケーションがRuby 2.1未満を使用している or ライブラリがRuby 2.1未満をサポートしている場合、古いバージョンに合わせた解析が行えなくなってしまいます。
後者の例で具体的にどのような問題が起きるのか解説します。
RuboCopにはStyle/SymbolArray
というCopが存在します。このCopは[:foo, :bar, :baz]
のようなコードに対して%i[foo bar baz]
と書きましょう、という指摘を出します。
この%i
構文はRuby 2.0から追加されました。そのためこれまでRuboCopではこのCopはTargetRubyVersion
に2.0以上が指定されている時のみ有効になっていました。
しかし、今回のアップデートからはTargetRubyVersion
には2.1以上しか指定できなくなってしまったため、このCopは常に有効になります。
つまり、Ruby 1.9を対象にしているプロジェクトでもRuboCop 0.51.0を使用するとこのCopが有効になってしまいます。
このケースはStyle/SymbolArray
を無効にすれば良いだけですが、このようなCopは多く存在するため、全てをいい感じに動かすのは難しいでしょう。
Ruby 2.1未満でRuboCopを使いたい場合、Rubyをアップデートするか、RuboCop 0.50.0を使用する必要があります。 これらRuboCopがサポートしていないバージョンのRubyは、もう既に公式にサポートが終了しているので、アップデートをしたほうが良いでしょう。
新規Cop追加
Copとは、RuboCopにおいてひとつのルールを指す言葉です。例えば、「インデントが正しいかチェックする」「非推奨メソッドを使っていないかチェックする」などが1つのCopの単位になります。
この章では、0.51.0で新たに追加されたCopをひとつずつ紹介します。
Rails/UnknownEnv
- PR: https://github.com/bbatsov/rubocop/pull/4791
- Document: http://rubocop.readthedocs.io/en/latest/cops_rails/#railsunknownenv
このCopは、RailsアプリケーションでのRails.env
のtypoを検出します。
Railsアプリケーションでは、以下のようにしてproduction
環境でのみ有効になるコードを書くことが出来ます。
if Rails.env.production? do_something_for_production end
ですが、このproduction?
メソッドをtypoしていたとしても、例外や警告は一切発生せず、常にfalse
を返します。
# `production?`をtypoしているため、常にfalseに評価されてしまっている! if Rails.env.prodcution? do_something_for_production end
このようなコードは非常に発見しづらいバグを生んでしまいます。
このCopを使用すると、上記のような未知のenvironmentを検出することが出来ます。
また、staging
などの独自のenvironmentを定義している場合、以下のようにして.rubocop.yml
で定義を追加することが出来ます。
Rails/UnknownEnv: Environments: - development - test - production - staging
この際、development
, test
, production
も書く必要があることに注意してください。
Style/StderrPuts
- PR: https://github.com/bbatsov/rubocop/pull/4813
- Document: http://rubocop.readthedocs.io/en/latest/cops_style/#stylestderrputs
Rubyではメッセージを標準エラー出力に出すwarn
というメソッドが定義されています。
# 標準エラー出力にメッセージが出る warn '`foobar` is deprecated. Use `foobaz` instead of `foobar`'
上記のコードは、$stderr.puts
とほぼ同等の動きをします。
このCopは$stderr.puts
を使用している場合にwarn
メソッドを使用するよう警告を出します。
# warn で書き換えることが出来る $stderr.puts '`foobar` is deprecated. Use `foobaz` instead of `foobar`'
ただし、warn
メソッドは、$VERBOSE
フラグがnil
の場合(例えば、-W0
オプション付きでRubyが実行された場合)にはメッセージを出力しません。
そのため、必ずメッセージを出力したい場合にはwarn
ではなく$stderr.puts
を使うほうが良いでしょう。
Lint/UnneededRequireStatement
- PR: https://github.com/bbatsov/rubocop/pull/4764
- Document: http://rubocop.readthedocs.io/en/latest/cops_lint/#lintunneededrequirestatement
thread
やrational
などいくつかのライブラリは、require
をしなくても使用することが出来ます。
$ ruby -e "puts Thread" Thread $ ruby -e "puts Rational" Rational $ ruby -e 'p require "thread"' false $ ruby -e 'p require "rational"' false
そのため、これらのライブラリへのrequire
は意味のないコードです。
このCopはこのようなrequire
する必要のないライブラリをrequire
しているコードに警告を出します。
Lint/RedundantWithObject
- PR: https://github.com/bbatsov/rubocop/pull/4796
- Document: http://rubocop.readthedocs.io/en/latest/cops_lint/#lintredundantwithobject
Enumerable
, Enumerator
にはそれぞれ#each_with_object
, #with_object
が定義されています。
- https://docs.ruby-lang.org/ja/latest/method/Enumerator/i/with_object.html
- https://docs.ruby-lang.org/ja/latest/method/Enumerable/i/each_with_index.html
これらのメソッドを使うことで、任意のオブジェクトをeach
に渡して実行することが出来ます。
例:
hash = [1, 2, 3].each.with_object({}) do |item, hash| hash[item] = item.to_s end p hash # => {1=>"1", 2=>"2", 3=>"3"}
このように#with_object
は便利なのですが、開発やリファクタリングの過程でコードが変更され、#with_object
が必要なくなることがあります。
# 引数でhashを受け取るようになったので、`with_object`は必要ない def f(hash) [1, 2, 3].each.with_object({}) do |item| hash[item] = item.to_s end end hash = {} f(hash) p hash # => {1=>"1", 2=>"2", 3=>"3"}
ですがこのような時にwith_object
を消し忘れてしまうことがあります。
このCopは、そのようなwith_object
の消し忘れに対して警告します。
Style/CommentedKeyword
- PR: https://github.com/bbatsov/rubocop/pull/4673
- Document: http://rubocop.readthedocs.io/en/latest/cops_style/#stylecommentedkeyword
Lint/RegexpAsCondition
- PR: https://github.com/bbatsov/rubocop/pull/4854
- Document: http://rubocop.readthedocs.io/en/latest/cops_lint/#lintregexpascondition
Rubyではif
文などの条件文全体が正規表現リテラルである場合、その正規表現を暗黙的に$_
と比較する、という仕様があります。
https://docs.ruby-lang.org/ja/latest/doc/spec=2fcontrol.html#if
$_ = 'b' if /a/ puts 'これは表示されない' end $_ = 'a' if /a/ puts 'これは表示される' end
この仕様はワンライナーを書く際に便利です。
https://docs.ruby-lang.org/ja/latest/doc/spec=2frubycmd.html
$ cat animal.txt cat dog cow $ cat animal.txt | ruby -nle 'puts $_ if /^c/' cat cow
ですが、通常のアプリケーションを使う上ではあまり使うことのない機能でしょう。 意図しない挙動になってしまうかも知れません。
このCopは、このような条件文に正規表現リテラルが入っているコードに対して警告を出します。
Style/MixinUsage
- PR: https://github.com/bbatsov/rubocop/pull/4840
- Document: http://rubocop.readthedocs.io/en/latest/cops_style/#stylemixinusage
このCopは、トップレベルに書かれたinclude
に対して警告を出します。
例
# 警告が出る include FooModule class Object include FooModule end
トップレベルに書かれたinclude
は暗黙的なObject
に対するinclude
です。
このCopでは明示的にObject
に対するinclude
を書くように指摘を出します。
また、このCopは将来的に暗黙的なinclude
を書くようなスタイルもサポートする予定です。
Style/DateTime
- PR: https://github.com/bbatsov/rubocop/pull/4857
- Document: http://rubocop.readthedocs.io/en/latest/cops_style/#styledatetime
DateTime
ではなくTime
やDate
を使うことを推奨するCopです。理由はちゃんと調べてないので、自分で考えてください。
Gemspec/OrderedDependencies
- PR: https://github.com/bbatsov/rubocop/pull/4874
- Document: http://rubocop.readthedocs.io/en/latest/cops_gemspec/#gemspecordereddependencies
gemspec
内で定義されている依存Gemの順番を揃えるCopです。
# gemがアルファベット順でソートされている。 s.add_runtime_dependency('parallel', '~> 1.10') s.add_runtime_dependency('parser', '>= 2.3.3.1', '< 3.0') s.add_runtime_dependency('powerpack', '~> 0.1') s.add_runtime_dependency('rainbow', '>= 2.2.2', '< 3.0') s.add_runtime_dependency('ruby-progressbar', '~> 1.7') s.add_runtime_dependency('unicode-display_width', '~> 1.0', '>= 1.0.1')
なお、このCopはAuto-Correctをサポートしているため、-a
オプションをつけてRuboCopを実行することで自動的にソートを行うことが可能です。
RuboCop プラグイン開発者向け
また、このリリースではRuboCop内部のAPIにも変更があります。 そのため、rubocop-rspecのようなRuboCopプラグインを作っている人は、以下の対応をする必要があります。
なお、RuboCopの設定ファイルGem(onkcopなど)に対しては特に対応は必要ありません。
NodePattern
まず、NodePatternというRuboCop内部で使用しているASTに対するパターンマッチャに破壊的な変更がありました。
RuboCop 0.50.0までは、(send _ :== nil)
というパターンは以下のようなコードを生成していました。
node.send_type? && children.size == 3 && children[1] == :== && children[2] == nil
ここで、最後の行のchildren[2] == nil
に注目してください。これは、ASTの3番目(1-originで)の要素が"存在しない"ことを意味します。
この仕様が原因で、今までは"nil
リテラルが渡された"ことを検査することが出来ませんでした(Hackな方法を使えば出来たのかも知れないけど)。
RuboCop 0.51.0 からは、先程のパターンは以下のようなコードに変換されます。
node.send_type? && children.size == 3 && children[1] == :== && children[2].nil_type?
これは"nil
リテラルが渡された"ことを意味します。
また、今までのようなASTの要素が存在しないことを示したい場合、(send _ :== nil?)
のようにnil?
メソッドの呼び出しとして実装する必要があります。
そのため、多くのケースでは既存のNodePatternを使っているコード内のnil
をnil?
に書き換える必要があるでしょう。
例として、rubocop-rspecではこの変更が既に行われているため、参考にしてください。 https://github.com/backus/rubocop-rspec/pull/477
add_offense
また、RuboCop 0.51.0からはadd_offense
メソッドの書き方が変更されました。
0.50.0まではadd_offense
メソッドは次のように使用していました。
add_offense(node, :selector, message(node.children.first))
ですが、RuboCop 0.51.0からは通常の引数ではなくキーワード引数を使用する様に変更されました。
add_offense(node, location: :selector, message: message(node.children.first))
なお、従来のスタイルのadd_offense
も現状は使用することが出来ますが、次のリリースで削除される予定です。
そのため、RuboCopプラグインを作っている場合はなるべくはやく対応をした方が良いでしょう。
名前の変更
今回のアップデートでは、Lint/LiteralInCondition
がLint/LiteralAsCondition
に変更されました。
もし.rubocop.yml
にLint/LiteralInCondition
の記載がある場合には名前の変更が必要です。
この変更は、mryを使うことで自動的に修正することが可能です。 https://github.com/pocke/mry
$ gem install mry $ mry .rubocop.yml --target 0.51.0
また、最新のmryからは--from
オプションを使用することで、指定したリリースから追加されたCopの一覧を.rubocop.yml
に追記することが可能です。
# RuboCop 0.51.0でのリネームが反映され、0.51.0で新規追加されたCopが.rubocop.ymlに追記される $ mry .rubocop.yml --target 0.51.0 --from 0.50.0
まとめ
この記事は以上になりますが、RuboCop 0.51.0ではこの他にも多くの機能追加、バグ修正が行われています。 より詳しい変更を知りたい方は、リリースノートをご覧ください。
Release RuboCop 0.51 · bbatsov/rubocop
過去のリリース
- RuboCop 0.50.0 のCHANGELOGを読む - pockestrap
- RuboCop 0.49.0 のCHANGELOGを読む - pockestrap
- RuboCop 0.48.0 のCHANGELOGを読む - Qiita
- RuboCop 0.47.0 のCHANGELOGを読む - Qiita
- RuboCop 0.46.0 のCHANGELOGを読む - SideCI TechBlog
- RuboCop 0.45.0 のCHANGELOGを読む - SideCI TechBlog
- RuboCop 0.44.0 / 0.44.1 のCHANGELOGを読む - SideCI TechBlog
- RuboCop 0.43.0 の CHANGELOG を読む - SideCI TechBlog
- RuboCop 0.41 / 0.41.1 がリリースされました。 - SideCI TechBlog