🪐

.rubocop_todo.yml の反映漏れを CI で防止する

2024/09/05に公開

.rubocop_todo.yml の運用を少し楽にするテクニックの紹介です。

.rubocop_todo.yml とは

既存のプロジェクトに RuboCop を導入すると、違反が大量出てしまい、解消に苦慮するという問題があります。その問題を解決する方法の1つが .rubocop_todo.yml という設定ファイルです。

.rubocop_todo.yml は、Ruby の静的コード解析ツールである RuboCop によって自動生成されます。このファイルの設定を読み込むことで、プロジェクト内の現在のコードベースに存在する違反を一時的に無視することができます。

https://docs.rubocop.org/rubocop/configuration.html#automatically-generated-configuration

この設定を駆使することで、段階的に RuboCop の違反を解消することができるようになっています。

.rubocop_todo.yml とプロダクトコードの乖離

便利な .rubocop_todo.yml ですが、運用するにあたって不便な点があります。それは、プロダクトコードと .rubocop_todo.yml を追従していく必要があることです。

理想的には、プロダクトコードの変更に合わせて .rubocop_todo.yml も適切に更新するのが望ましいですが、しばしば更新が忘れられることも多いです。その結果、後から気づいた人が更新するという運用になりがちです。

生成した .rubocop_todo.yml の差分をCIで検出する

.rubocop_todo.ymlの反映漏れは、CIを使うことで割と簡単に検知できます。
具体的には、以下のような step を組み込みます。

  • rubocop --regenerate-todo を実行する
  • その後のワークツリーで git diff --exit-code を実行する

rubocop --regenerate-todoは前回.rubocop_todo.yml のTODOリストを生成したオプションで再生成するものです。同じオプションで実行されることが担保できるため、今回の要件に合います。

Regenerate the TODO list using the same options as the last time it was generated with --auto-gen-config (generation options can be overridden).
--regrenerate-todo - Basic Usage :: RuboCop Docs

git diff --exit-code は差分があれば場合に終了コード1、無ければ終了コード0を返します。

Make the program exit with codes similar to diff(1). That is, it exits with 1 if there were differences and 0 means no differences.
Git - git-diff Documentation

これにより rubocop --regenerate-todo で差分が生じることで、.rubocop_todo.yml とプロダクトコードが乖離していることを検知します。

上記の step で差分を検知した後は、その差分を自動でコミットしたりする仕組みと紐づけることで、人が管理せずに .rubocop_todo.yml の反映できます。

.rubocop_todo.yml の差分から Pull Request を作成する GitHub Actions の設定例

最後に .rubocop_todo.yml を自動でプロダクトコードに追従する GitHub Actions のワークフローの例を紹介します。

作成しているPRに対して、.rubocop_todo.ymlに差分があれば、その差分を解消するPRを自動で作成するものです。

.rubocop_todo.ymlを再生成するPRを作成するジョブ
on: pull_request
jobs:
 check-code-generation:
  runs-on: ubuntu-latest
  permissions:
    contents: write
    packages: read
    pull-requests: write
  steps:
    - uses: actions/checkout@v4
    - uses: ruby/setup-ruby@v1
      with:
        bundler-cache: true
    - run: bundle exec rubocop --regenerate-todo
    - name: Check rubocop todo
      id: rubocop-todo-config
      continue-on-error: true
      run: git diff --exit-code
    - uses: peter-evans/create-pull-request@v7
      if: steps.rubocop-todo-config.outcome != 'success'
      with:
        commit-message: 'chore: Update code using `rubocop --regenerate-todo`'
        branch: ${{ github.ref_name }}/propose-generated-diff
        base: ${{ github.event.pull_request.head.ref }}
        delete-branch: true
        title: 'chore: Update code using `rubocop --regenerate-todo`'
        body: 'refs. #${{ github.event.number }}'

Check rubocop todoで差分を確認しますが、この時点でCIを終了したくないので continue-on-error: true を設定しています。後続のステップで差分がある場合(git diff --exit-codeが終了コード1の時)をif: steps.rubocop-todo-config.outcome != 'success'で確認して、PRを作成しています。

この例ではPRの作成に peter-evans/create-pull-request を使っていますが、他のカスタムアクションやGitHub APIで自作しても良いです。

作成中のPRに差分があれば、以下のようなPRが作成されます。

sample

実際に使う場合は rubocop による違反をもみ消さないよう、Lintを行った後に上記の仕組みを入れるようにしましょう。

まとめ

簡単に作れるのでぜひ試してみてください。

参考

今回紹介したCIは、弊社の開発者ブログから着想を得て作成しました。
https://tech.classi.jp/entry/2022/02/10/181500

Discussion