Asakusa.rb 第478回

Asakusa.rb に行った。ちょうど一昨日リリースされた RuboCop 0.59.0 でいくつかイシューのフィードバックがあったので、そのバグフィックスをしたりしていた。

再現テストから副次的なバグも見つかって、一方のテストをパスしたら他方のテストが落ちるという状態で苦戦しつつ、ほぼパッチを書き終えるところまでやった。

あとはdup できるオブジェクトかどうか判定するのに AS を使えるのであれば、duplicatable? を使えるだとかとその実装やら、別アプローチやら、dupclone の違いなど雑談したりしていた。

`Style/EmptyCaseCondition` copのautocorrectでコメントがなくなるケースを直した

少し前にフィードバックをもらっていたイシューとなる。

github.com

以下のように、case と最初の when の間にあるソースコードコメントが autocorrect で消えるというフィードバックだった。

def foo
  case
  # when bar ...
  when bar
    2
  end
end

プロダクトコードのパッチは以下で、case から最初の when までの範囲を if に autocorrect 置換していたのを、case を削除して最初の whenif に置換することでコメントを維持するようにした。

        private

        def correct_case_when(corrector, case_node, when_nodes)
-         case_range = case_node.loc.keyword.join(when_nodes.first.loc.keyword)
-
-         corrector.replace(case_range, 'if')
+         remove_case_node(corrector, case_node)
+         corrector.replace(when_nodes.first.loc.keyword, 'if')

          when_nodes[1..-1].each do |when_node|
            corrector.replace(when_node.loc.keyword, 'elsif')
@@ -88,6 +87,14 @@ def correct_when_conditions(corrector, when_nodes)
            corrector.replace(range, conditions.map(&:source).join(' || '))
          end
        end
+
+       def remove_case_node(corrector, case_node)
+         range = range_by_whole_lines(
+           case_node.loc.keyword, include_final_newline: true
+         )
+         corrector.remove(range)
+       end
      end
    end
  end

range_by_whole_lines(case_node.loc.keyword, include_final_newline: true)range_by_whole_lines メソッドと include_final_newline: true オプションが伝家の宝刀で、case について行末改行までを対象とした範囲を得ることができる。

(余談だが、private メソッドに切り出しているのは諸々の Cop に引っかかる都合上 Extract Method した結果となる。)

以下のようにコメントが維持されるようになっている。

Before

def foo
  if bar
    2
  end
end

After

def foo
  # when bar ...
  if bar
    2
  end
end

github.com

RuboCop 0.59.0 がリリースされた

今回のリリースは 0.58.2 以前からのバグフィックスが多めだと思う。自分の変更の中では Changes セクションにあるものが影響の大きめなものだと捉えている。

github.com

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

New features

#5659: Make Layout/EmptyLinesAroundClassBody aware of specifying a superclass that breaks the line. (@koic)

Layout/EmptyLinesAroundClassBody cop のオルタナティブ設定である EnforcedStyle: empty_lines の際に、スーパークラスの指定で改行をするケースに対して false positive だったものを気づくようにした。コードにすると以下のようなケース。

class Foo <
      Bar

  def do_something
  end

end

このパッチのやりとりの中で、スーパークラスの指定で改行するケースをとりしまる cop (一行で同じ行に書く) を用意しようという話になり Drenmi さんが後に対応してくれる予定。

Bug fixes

#6132: Fix a false negative for Naming/FileName when Include of AllCops is the default setting. (@koic)

Naming/FileName cop で lib/layoutManager.rb といったキャメルケースのファイル名について取り締まりが出来ていなかったものを取り締まるように修正した。もちろんデフォルトの設定で除外される Gemfile, Rakefile といったものは対象外としている。

#6164: Fix incorrect autocorrect for Style/UnneededCondition when using operator method higher precedence than ||. (@koic)

Style/UnneededCondition cop の autocorrect を実行すると意味合いが変わるケースに対する修正。

|| は他の `<<, ==, +, といった演算子メソッドに比べて結合順位が低い。以下のようなコードを3項演算子に autocorrect する際にはカッコが必要。

ary << if foo
  foo
else
  bar
end

以前はこう autocorrect されていた。

ary << foo || bar

これは以下のような意味。

(ary << foo) || bar

このパッチでこのように意味を変えないように autocorrect するようになった。

ary << (foo || bar)

#6196: Fix incorrect autocorrect for Style/EmptyCaseCondition when using return in when clause and assigning the return value of case. (@koic)

Style/EmptyCaseCondition cop の autocorrect で意味合いが変わるケースがあって、それだと autocorrect 後のコードが壊れているというものを修正したもの。

以下のように when の中で return を使った場合は return の値が返る。

def foo
  v = case
      when true
        return 2
      end
end

p foo # => 2

if の場合は "void value expression" エラーになる。

def bar
  v = if true
        return 2
      end
end

bar # => hoge.rb:4: void value expression

ロジック全体をみて振る舞いを維持した autocorrect は難しいため、このケースはそのままに置いておくようにした。

#6240: Fix an auto-correct error for Style/WordArray when setting EnforcedStyle: brackets and using string interpolation in %W literal. (@koic)

Style/WordArray cop のオルタナティブ設定 EnforcedStyle: brackets での autocorrect 時にエラーになる問題を解決したもの。 以下のように式展開が入っているケースで autocorrect するとエラーになっていたものを、期待する autocorrect したコードになるようにした。

foo = 'foo'

%W(#{foo}bar baz)

Changes

#4301: Turn off autocorrect for Rails/RelativeDateConstant by default. (@koic)

Rails/RelativeDateConstant cop の autocorrect をデフォルトで無効にした。

以下のようなコードを autocorrect 前 (上) と autocorrect 後 (下) にするのに対して、ファイルをまたがって autocorrect するのが困難なため。

class A
  B = 2.weeks.ago

  def c
    B
  end
end
class A
  def self.b
    2.weeks.ago
  end

  def c
    B
  end
end

#4832: Change the path pattern (*) to match the hidden file. (@koic)

.rubocop.yml の IncludeExclude なんかで、ワイルドカード * 指定したパスに対してドットで始まる隠しファイルを含むようにした。詳しくは以前の日記に記している。

koic.hatenablog.com

#6235: Enable Layout/EmptyLineAfterGuardClause cop by default. (@koic)

ガード条件の後に空白を入れる Layout/EmptyLineAfterGuardClause cop をデフォルトで有効にした。詳しくは以前の日記に記している。

koic.hatenablog.com

累の最終巻を読んだ

最終巻がリリースされていた。読み始めたら続きや結末が気になって最後まで読んでいた。

表参道.rb #38

「表参道.rb #38 〜Railsアンチパターン〜」で発表してきた。会場は外苑前のビジネスバンクグループさん。

omotesandorb.connpass.com

一般参加者枠が満席だったので、当日直前まで発表者枠が空いていたら参加しようと思っていたところ、そうなったのでそうした。

そのような経緯なので発表スライドは以下の1枚である。これですべてが伝わるだろう、、、ということで行なったプレゼンスタイルではない

f:id:koic:20180907195925j:plain

最近は RailsConf などの海外カンファレンスの動画を見るのが英語トレーニングというかマイブームで、その中のひとつである RailsConf 2018 の DHH のプレゼンがキーワードだけで何分か話すというスタイルで試してみたかったというのがある。

色使いを DHH のスライドの 1 枚目 FIXME に似せているのはそのため。

confreaks.tv

あとはテーマに沿って話すことはあるが現実的に何枚ものスライドを作る時間はなかったので、こういったスタイルはそうそうないだろうという副作用でのインパクトを狙ったというものだった。わずか一枚ながら default_scope というキーワードの強さが持つアンチパターンいろいろがにじみ出ていると思う。スライドの作りがないので、他の人の発表をじっくり聞くことができるという効用もあって本人的に中々よかった。

話の内容としては「default_scope と論理削除はズッ友」というアンチパターンアンチパターンを呼ぶというものだった。

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 バージョンを利用者が分かるすべについては少し検討しているところ。