This post is Private. Only a writer or those who know its URL can access this post.

まだ .rubocop_todo.yml で消耗してるの?

自己紹介

サトウリョウスケ

プログラム歴

  • 16 ~ 25 歳: C / C++
  • 25 ~ 29 歳: C#, Oracle, Java, COBOL
  • 29 ~ 34 歳: Ruby on Rails, MySQL, TypeScript

今日のお題


RubocopChallenger について紹介させて下さい :bow:


RubocopChallenger とは


動作 :robot:

CircleCI と組み合わせて、以下のフローを週 3 回(火・水・木)夜間バッチで実行する。

  1. :page_facing_up: プロダクトの .rubocop_todo.yml を読み込む
  2. :outbox_tray: 自動修復可能なルールを 1 つ取り出す
  3. :wrench: 自動修復を実行
  4. :envelope: Pull Request を作成 ← クリック

FireShot Capture 001 - Style_ParallelAssignment-20190321083055 by social-plus-devel · Pull R_ - github.com.png


FireShot Capture 002 - Style_ParallelAssignment-20190321083055 by social-plus-devel · Pull R_ - github.com.png


Circle CI で使う場合

.circleci/config.yml の書き方例

jobs:
  rubocop_challenge:
    docker:
      - image: circleci/ruby:2.6-node-browsers
    working_directory: ~/repo
    steps:
      - checkout
      - run:
          name: Rubocop Challenge
          command: |
            bundle install
            bundle exec rubocop_challenger go \
              --email=challenger@example.com \
              --name='rubocop challenger' \
              --template='.circleci/rubocop_challenge_template.md.erb' \
              --labels='rubocop challenge' 'in progress'

workflows:
  version: 2
  challenge:
    triggers:
      - schedule:
          cron: "30 23 * * 1,2,3" # 8:30am every Tuesday, Wednsday and Thursday (JST)
          filters:
            branches:
              only:
                - master
    jobs:
      - rubocop_challenge

何が嬉しいの :information_desk_person:

  • 古いプロダクトには違反した書き方がたくさんある :confounded:
    • 弊社では 2012 年ローンチのプロダクトに 2017/02 にようやく RuboCop を導入した :cop_tone3:
    • 最初から RuboCop 使ってるプロダクトだと大丈夫だけど :upside_down:
  • 一気に自動修復するとぶっ壊れる可能性がある :skull_crossbones: :skull_crossbones: :skull_crossbones:
    • 毎日 1 つずつ修復してくれるから確認しやすい :pill: :sparkles:

RubocopChallenger の功績 :moyai:


.rubocop_todo.yml の変化 :chart_with_downwards_trend:

chart (1).png


今後の予想 :chart_with_downwards_trend:

順調に行けば 6 月までに片付く :sparkles: :hugging: :sparkles:

chart (2).png


ちなみに導入前の様子 :chart_with_upwards_trend:

chart.png


RubocopChallenger の中身 :police_car:


動作 (再掲) :robot:

以下のフローを週 3 回(火・水・木)夜間バッチで実行する。

  1. :page_facing_up: プロジェクトの .rubocop_todo.yml を読み込む
  2. :outbox_tray: 自動修復可能なルールを 1 つ取り出す
  3. :wrench: 自動修復を実行
  4. :envelope: Pull Request を作成

最初のスクリプトバージョン :nerd:

# Commits on Sep 11, 2018

AUTO_CORRECTABLE = /# Offense count: (\d+)\n# Cop supports --auto-correct\./

def empty_line?(prev_char, char)
  prev_char == "\n" && char == "\n"
end

def extract_rule_title(string)
  string =~ /^([^# ].+):$/
  Regexp.last_match(1)
end

def extract_auto_correcable_rule
  buff = ''
  char = ''
  result = []
  file = open '.rubocop_todo.yml'

  loop do
    prev_char = char
    char = file.getc
    break if char.nil? # EOF

    buff << char
    next unless empty_line?(prev_char, char)

    if buff =~ AUTO_CORRECTABLE
      title = extract_rule_title(buff)
      result << [Regexp.last_match(1).to_i, buff.dup, title]
    end
    buff = ''
  end

  result.sort_by! { |r| r[0] }
end

result = extract_auto_correcable_rule

target_rule =
  if ARGV[0] == 'min'
    result.first
  else
    result.last
  end

puts target_rule[2]

current_data = File.read('.rubocop_todo.yml')
File.write('.rubocop_todo.yml', current_data.sub(target_rule[1], ''))

:weary:


ちなみに今のバージョンはもっとスマートです :cop_tone1: :sparkles:


現在のバージョン :surfer:

def extract_rubocop_rules
  File
    .read(rubocop_todo_file_path)         # .rubocop_todo.yml を読み込む
    .split(/\n{2,}/)                      # 2つ以上連続する改行で区切って配列にする
    .map! { |content| Rule.new(content) } # Rule クラスで初期化
    .reject! { |rule| invalid?(rule) }    # 扱えないルールを除外する
    .sort!                                # Offense Count でソートする
end

:point_down: .rubocop_todo.yml の一部

# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment.
Layout/SpaceBeforeFirstArg:
  Exclude:
    - 'spec/models/short_uri_spec.rb'

# Offense count: 6
# Cop supports --auto-correct.
Layout/SpaceInsideArrayPercentLiteral:
  Exclude:
    - 'lib/extra/crawler/html_document.rb'
    - 'spec/models/csv_manager/user_list_spec.rb'

今後やりたいこと :map:

  • Auto-correct 出来なかった場合に ignore リストにルールを追加する
    • v1.2.0 で対応済み :white_check_mark:
  • :four_leaf_clover: --safe-auto-correct のサポート
  • :comet: PrCreater を別 gem として切り出す
  • :apple: GitHub App に対応する

:levitate: Auto-correct 出来なかった場合に ignore リストにルールを追加する

RuboCop の中には、完全に auto-correct に対応していないルールがある。

# Style/Semicolon の例
# 行末に ; が存在する場合以外は auto-correct できない

puts 'hoge'; # => auto-correct される
puts 'fuga'; puts 'piyo' # => auto-correct されない

こういう場合は ignore リストに Style/Semicolon を追加して、次回以降は対象にしないように したい。
▶︎ v1.2.0 で対応しました :white_check_mark:


スクリーンショット 2019-03-26 2.50.42.png


:four_leaf_clover: --safe-auto-correct のサポート

コマンドオプションに追加したい。

つっつきボイス:「たまたまRubocopの話題が続きました」「Rubocopにこういうオプションがあるのね↓」「安全でないcopがあるという話を追ってるようです」

# 同記事より
# 安全なcopだけを実行
$ rubocop --safe

# 安全なオートコレクトだけを実行
$ rubocop --safe-auto-correct

# 安全なcopだけを実行してから安全なオートコレクトだけを実行
$ rubocop --safe --safe-auto-correct

週刊Railsウォッチ(20181112)より


:comet: PrCreater を別 gem として切り出す

RubocopChallenger 内部で使っている GitHub に Pull Request を作成する class が割と便利なので gem にしたい。

ENV['GITHUB_ACCESS_TOKEN'] = "xxx"

pr_creater = PrCreater.new(branch: "update/v#{version}")

pr_creater.commit 'Update version' do
  File.write('lib/rubocop_challenger/version.rb', <<~VERSION)
    # frozen_string_literal: true

    module RubocopChallenger
      VERSION = '#{version}'
    end
  VERSION
end

pr_creater.commit 'Run $ bundle update' do
  `bundle update`
end

pr_creater.create_pr(title: "Update v#{version}", body: '', base: 'master')

:apple: GitHub App に対応する

  • 一言で言うと Renovate みたいな GitHub App にしたい
  • Gemfile に追加したり .circleci/config.yml を編集する必要がなくなる

会社のブログ

まだ .rubocop_todo.yml で消耗してるの? - Feedforce Developer Blog


おしまい :wave:

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.