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