swiftlintを差分のあるファイルにだけpre-commitでかける


背景

最近ジョインしたプロジェクトでは、数年前にswiftlintをプロジェクトに追加したはいいのですがCIなどでチェックしていない、lintツールの運用ルールを明確にしていない、開発者によってはswiftlintを気にしてない、などの理由でlintのwarningが大量に溜まりlintツールがそもそも機能しなくなっていました。

技術的な負債を解消するために既存のコードをメンテする工数が取れないけど、lintくらいは運用してコードの品質を担保したい。

折衷案として、私のチームでは「新規に自分で作ったファイル」「自分が変更したファイル」に対してのみlintのwarningを解消する責任を負うというルールにしました。

ということで導入した差分のあるファイルにのみswiftlintをかけるpre-commitのスクリプトを紹介します。

方針

git commitした際に拡張子が.swiftで差分のあるファイルのみをincludeするswiftlintの設定ファイルを生成してlint→warningが出たらcommitができないようにします。

前準備

commit時に生成されるswiftlintの設定ファイルはgitで管理する必要がないのでgitignoreします。

.swiftlint.precommit.yml

また、もとになる.swftlit.ymlも当然ながら用意しておきましょう。

pre-commitのスクリプト

本編です。これだけです。忙しい人はプロジェクト内の.githooks/pre-commitとかにこれコピペして使ってください。

git_diff=$(git diff --cached --name-only --diff-filter=AM -- '*.swift')

if [ -n "$git_diff" ]; then
  cp .swiftlint.yml .swiftlint.precommit.yml

  echo 'included:' >> .swiftlint.precommit.yml
  echo "$git_diff" | sed -e "s/^/  - /" >> .swiftlint.precommit.yml

  echo 'lint the following files'
  echo "$git_diff" | sed -e "s/^/  - /"
  echo ""
else
  echo 'No changed .swift file'
  exit 0;
fi

lint_result=$(swiftlint --reporter emoji --quiet --config .swiftlint.precommit.yml)

if [ -n "$lint_result" ]; then
  echo 'Some issues found.'
  echo '====================================='
  echo "$lint_result" | sed -e "s/^/>> /"
  echo '====================================='
  echo ''

  exit 1;
else
  echo 'No issues found!'
fi

exit 0

解説

git_diff=$(git diff --cached --name-only --diff-filter=AM -- '*.swift')

git_diffという変数に差分がある拡張子が.swiftのファイル名一覧を入れておきます。

if [ -n "$git_diff" ]; then
  ~~~~
else
  echo 'No changed .swift file'
  exit 0;
fi

git_diffが空=.swiftの差分がないならpre-commitを正常に終了してそのままcommitさせます。

cp .swiftlint.yml .swiftlint.precommit.yml

echo 'included:' >> .swiftlint.precommit.yml
echo "$git_diff" | sed -e "s/^/  - /" >> .swiftlint.precommit.yml

もとになる.swiftlint.yml.swiftlint.precommit.ymlにコピーして、末尾に

included:
  - HasDiff.swift
  - HasDiffViewController.swift

のようにgit_diffの中身のファイルをincludeする設定を書き込みます。

lint_result=$(swiftlint --reporter emoji --quiet --config .swiftlint.precommit.yml)

lint_resultにswiftlintの結果を入れます。

if [ -n "$lint_result" ]; then
  ~~~~~~~
  exit 1;
else
  echo 'No issues found!'
fi

lint_resultがあれば(=なにかしらのwarningがある)exit 1で異常系終了、なければ特に何もせず正常に終了します。

pre-commitがエラーで終了するとcommitができないので、これを使うことでlintが通っていないコードがcommitされることはありません。

導入

各自のローカルでpre-commitの設定を行ってください。

$ chmod +x .githooks/pre-commit
$ git config --local core.hooksPath .githooks/

実際に運用してみてのうまみ

まとめ的な感じで1カ月くらい運用した感想と効果をまとめます。

  • そもそもcommitできなくすることで"apply lint"とか"lint fix"みたいなcommitを後追いですることがなくなった
  • lintルールができたことでレビューもしやすくなった
  • ただローカルでpre-commitの設定をする必要があるので、そこの情報共有が漏れるとかなり脆弱なlint運用ではある

まぁ結構プロジェクトにはメリットが出たかなと思います。