KtLint + Spotless + GitHub ActionsでPRにsuggested changeさせる


ちょっと30分ぐらいで書いた小ネタで申し訳ないんですが、すごく簡単で、便利なので、アドベントカレンダーで紹介します。

Android Studioの自動フォーマットだとKtLintで指摘されるものを修正できず、Formatterをコミット前やビルド時に走らせるのもコード量に比例して遅くなりそうで、また変更したところだけフォーマットさせたいですがうまくできません。コミットのたびに時間かかりそうで微妙で、なにか解決策を探していました。
これを調べ始めて30分程度でできちゃったので、すごく簡単に機械的にレビューさせられるので、ちょっと試してみてください。

インデントを直すsuggested changeの例

使うツール

Spotless
変更したファイルを検出してFormatterを呼び出してくれたり、いろいろな機能があります。(JetNewsなどGoogleのOSSなどでも使われています。)

KtLint
Kotlinでよく使われるFormatterです。Spotlessと連携して動作させることができます。

reviewdog
PRにレビューコメントをしてくれるツールです。最近、git diffで変更が出ている部分をSuggested Changeでレビューできる機能が追加されました。

この3つを連携させて変更したファイルをフォーマットさせ、変更をsugggested changeさせることができます。

手順

まずSpotless + KtLintを導入します。

plugins {
    id 'com.diffplug.spotless' version '5.7.0'
}

allprojects {
    repositories {
...
    }
}

subprojects {
    repositories {
        google()
        jcenter()
    }

    apply plugin: 'com.diffplug.spotless'
    spotless {
        kotlin {
            target '**/*.kt'
            targetExclude("$buildDir/**/*.kt")
            targetExclude('bin/**/*.kt')

            ktlint("0.39.0")
        }
    }
}

あとは以下を .github/workflows/review-suggest.yml などに保存します。
GITHUB_TOKENは勝手にGitHub Actionsが提供しているので、他にすることはないです。
ここではreviewdog/action-suggesterを使っています。

name: reviewdog-suggester
on: [pull_request]
jobs:
  kotlin:
    name: runner / suggester / spotless
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-java@v1 # JDK 11が必要なければ消してください
        with:
          java-version: '11'
      - run: ./gradlew spotlessKotlinApply
      - uses: reviewdog/action-suggester@v1
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          tool_name: spotless

以下でフォーマットをかけて

./gradlew spotlessKotlinApply

以下でreviewdogを使って差分をコメントさせるだけです。つまり差分さえあればレビューさせられるので、いろんなフォーマッターに使えて便利です。

uses: reviewdog/action-suggester@v1

これでコメントさせていくことができました。 GitHub Actionsでreviewdogを使うことに対してセキュリティなど少し気になるところはあるのですが、reviewdogの作者の方は以前にGoogleにはいるブログなどを書いていたりして、割と身元が分かっている方なので安心して使うことができます。

ローカルでコミット前にフォーマットするには?

何も考えずに ./gradlew spotlessApplyだけだと全部のソースコードをフォーマットかけてしまいます。

spotlessにはratchetという便利機能があり、以下のようにratchetFromでブランチを指定しておいて、 ./gradlew spotlessApply すると、masterブランチからの差分だけフォーマットをかけることができます。便利です。

spotless {
    ratchetFrom 'origin/master'

ただしこの機能をGitHub Actionsでも使う形になるので、yamlファイルにcheckoutを追加して一度masterをチェックアウトしてから必要なブランチにチェックアウトして利用してく必要があります。これをしないとorigin/masterが存在しないという形でビルドが失敗します。

      - uses: actions/checkout@v2
        with:
          ref: master # 一度masterでcheckoutする
      - uses: actions/checkout@v2 # 目的のブランチでcheckoutする