RuboCop 0.58.0 がリリースされた

通算 100 リリース目にあたるっぽい。期せずしてだと思うけれど、JST では七夕リリースになっている。

今回のリリースは 0.57 からのバグフィックスが多めだと思うので、0.57 を使っていたユーザーはとりわけアップグレードしておくと良いと思う。あと Ruby 2.1 のサポートがドロップされている。

github.com

自分の変更に関するコメント。

New features

なし

Bug fixes

#5966: Fix a false positive for Layout/ClosingHeredocIndentation when heredoc content is outdented compared to the closing. (@koic)

以下のコードでクロージングの STR に offense が出ているというフィードバックだった。

def f
  puts <<-STR
 There's one space before this line
  STR
end

squiggly heredoc (<<~) やその類のサポート Gem を使っていないときに、ヒアドキュメントのクロージングより前にコンテンツが出るのに offense を出すのは明らかに偽陽性だったので、それを直した。

#6025: Fix an incorrect auto-correct for Lint/UnneededCondition when using if_branch in else branch. (@koic)

フィードバックのあったコード。

if a
  a
else
  b ? c : d
end

仮に auto-correct で以下のようなコードにしても読みやすくなるとは思えないので、このケースはスルーするようにした。

a || (b ? c : d)

これに関連して #6013 で折をみつつ解決を試みていたイシューがあるけれど、そちらは今回のリリースには間に合わなかった。

#6029: Fix a false positive for Lint/ShadowedArgument when reassigning to splat variable. (@koic)

フィードバックのあったコード。

def method(*items)
  *items, last = items
  return last if items.empty?
  items
end

splat variable (*var) に対応していなかったので、対応した。

#5467: Fix a false negative for Style/MultipleComparison when multiple comparison is not part of a conditional. (@koic)

6月に新宿で開催された西日暮里.rb に参加して直していたイシュー。

def foo(x)
  x == 1 || x == 2 || x == 3
end

自動修正でこうなって欲しいという Cop の期待に合わせておいた。

def foo(x)
  [1, 2, 3].include? x
end

副次的に offense の出る範囲もより正確になった。

  a = "a"
  if a == "a" || a == "b"
- ^^^^^^^^^^^^^^^^^^^^^^^ Avoid comparing a variable with multiple items in a conditional, use `Array#include?` instead.
+    ^^^^^^^^^^^^^^^^^^^^ Avoid comparing a variable with multiple items in a conditional, use `Array#include?` instead.
    print a
  end

パッチの説明は西日暮里.rbで話したとおり。

#6052: Fix a false positive for Style/SymbolProc when using block with adding a comma after the sole argument. (@koic)

フィードバックのあった以下のブロック引数のケースに対応した。これどう振る舞うんだったっけ?と調べてから直した覚えがある。

sorted = h.sort_by { |i,| i.ref }

#6067: Prevent auto-correct error for Performance/InefficientHashSearch when a method by itself and include? method are method chaining. (@koic)

自分で定義した keys に関して auto-correct でエラーになっていたのに対するパッチ。

def keys
  # When delegation instead of an array,
  # it does not know the type until run-time.
  []
end

def my_include?(key)
  keys.include?(key)
end

レシーバーを指定していない場合は、独自定義としてスルーするようにしている。現状の機構だとこれ以上のケアをしようとすると結構テコ入れが必要な気がしている。

Changes

#6006: Remove rake repl task. (@koic)

開発者向け。RuboCop のモジュールを REPL で使うためのコマンドを bin/console に統一して、非推奨警告を出していた rake repl タスクを消した。

東京→仙台

YARD 0.9.13 で RuboCop のビルドが通らなくなっていたのについて、仙台行きの新幹線の中で YARD のコードを見ていたけれど、デバッグの動きを見ているうちに時間切れ。YARD の気持ちは難しいのと、東京から仙台は結構近かった。

夜は『蔵の庄』で美味しいお酒と肴をつついたりしていた。

久しぶりに RuboCop の master ブランチが落ちていた

久しぶりに RuboCop の master ブランチが落ちていたので見たところ、YARD のアップグレードが原因だった。

github.com

YARD の変更自体が期待した振る舞いかリグレッションか判断がつかなかったので、まずは割れ窓を放置しないよう RuboCop の方にワークアラウンドの PR を開いたところまで。

あとは RubyKaigi 2018 のスポンサーブースに展示するスライドについて Keynote 職人芸を発揮していたりしていた。

`Layout/SpaceInsideReferenceBrackets` cop の false negative を直した

レポートが分かりやすかったので着手していた。問題解決には Token まわりのコードを見るのに骨があった。

github.com

問題としては以下のようにネストした reference brackets の外側の括弧に対して期待した offense が出ないというもの。

record[ options[:attribute] ]

AST へのイベント処理としては先に record[ が処理されてから options[ が処理されるので、開き括弧 [ (left_ref_bracket) の前のトークンが閉じ括弧 ] (right_bracket) でないときは、トークンを reverse させないのがパッチの肝。このケースでも常に reverse していたので、内側の options[ の括弧を常に参照していたのが問題の原因だった。

逆に [ の前の token] はどういったものかというと o[:foo][:bar] のケースで、そういった場合とは処理を振り分けておく必要があったというものだった。

@@ -111,10 +111,14 @@ def bracket_method?(node)
         end
 
         def left_ref_bracket(node, tokens)
-          if node.method?(:[]=)
+          current_token = tokens.reverse.find(&:left_ref_bracket?)
+          previous_token = previous_token(current_token)
+
+          if node.method?(:[]=) ||
+             previous_token && !previous_token.right_bracket?
             tokens.find(&:left_ref_bracket?)
           else
-            tokens.reverse.find(&:left_ref_bracket?)
+            current_token
           end
         end
 
@@ -131,6 +135,11 @@ def closing_bracket(tokens, opening_bracket)
           end
         end
 
+        def previous_token(current_token)
+          index = processed_source.tokens.index(current_token)
+          index.nil? || index.zero? ? nil : processed_source.tokens[index - 1]
+        end
+
         def empty_config
           cop_config['EnforcedStyleForEmptyBrackets']
         end