lint-staged + huskyでプロジェクトのpre-commitフックにリンターを設定して…コミット前に指摘を潰しましょうというのはよくあるやり方である
lint-stagedでは差分に対してのみ各種リンターのチェックができる認識でいたが差分があるファイル単位までフィルタリングできるという感じだった
プロジェクトの途中からリンターを導入する場合など、すでに警告対象のコードが紛れてしまっている場合、そういうファイルを変更するとリンターに引っかかるので同時に直さないと行けないというような感じになる
できれば差分のみに対して警告を出すようにしたい
一方、CIではよくreviewdogを使って差分のみに対してリンターを掛けコメントしている例をよく見る
reviewdogのREADMEより
reviewdogはGitHubなどのコードホスティングサービスにレビューコメントを自動的に投稿するためのツールです.レビューするパッチのdiffに所見がある場合、lintツールの出力を使ってコメントとして投稿します。
reviewdogはローカル環境での実行もサポートしており、lintツールの出力をdiffでフィルタリングすることができます。
CIだけじゃなくローカルでも実行ができるということはpre-commitフックでも実行ができる
reviewdogを使えば変更のあったファイルだけじゃなく差分に対してリンターで警告を出すことができるな
ということでいくつか試してみた
以降の例ではhuskyやlint-staged,reviewdogを使っている前提で話を進める、インストール方法や基本的な使い方については他にも紹介している記事がたくさんあるので今回は割愛する
lint-stagedの設定
- lint-staged.config.js(一部)
"*.@(js|ts|tsx)": filenames => `yarn eslint -f compact -c .eslintrc.json ${filenames.join(" ")} | reviewdog -f=eslint-compact -fail-on-error -diff='git diff --cached' -reporter=local`,
eslintを実行してその結果をreviewdogに流している
reviewdogのコマンド部分とオプション
reviewdog -f=eslint-compact -fail-on-error -diff='git diff --cached' -reporter=local
ローカルでの実行
-reporter
オプション
- ローカルに結果を出力する(GitHubやGitLabなどに投稿しない)
-reporter local
と指定するだけ
差分の指定
-diff
オプション
対象とする差分を指定する
CIの場合は、targetブランチと機能ブランチの差分を指定することが多いはず
pre-commitの場合
-diff="git diff --cached"
このようにステージされた差分を指定すれば良い
終了コードのコントロール
-fail-on-error
オプション
リンターから何か出力を受け取った場合、終了コードを1にする
lint-stagedは各チェックでコマンドの終了コードをみて成功、失敗の判断しているためreviewdogの終了コードを見てチェックの成否を判断する
フォーマット
-f
オプション
リンター出力のフォーマット指定
さまざまなリンターツールをサポートしている
$ reviewdog -list rdjson Reviewdog Diagnostic JSON Format (JSON of DiagnosticResult message) - https://github.com/reviewdog/reviewdog rdjsonl Reviewdog Diagnostic JSONL Format (JSONL of Diagnostic message) - https://github.com/reviewdog/reviewdog diff Unified Diff Format - https://en.wikipedia.org/wiki/Diff#Unified_format checkstyle checkstyle XML format - http://checkstyle.sourceforge.net/ ansible-lint (ansible-lint -p playbook.yml) Checks playbooks for practices and behaviour that could potentially be improved - https://github.com/ansible/ansible-lint black A uncompromising Python code formatter - https://github.com/psf/black brakeman (brakeman --quiet --format tabs) A static analysis security vulnerability scanner for Ruby on Rails applications- https://github.com/presidentbeef/brakeman cargo-check (cargo check -q --message-format=short) Check a local package and all of its dependencies for errors - https://github.com/rust-lang/cargo clippy (cargo clippy -q --message-format=short) A bunch of lints to catch common mistakes and improve your Rust code - https://github.com/rust-lang/rust-clippy dotenv-linter Lightning-fast linter for .env files. Written in Rust - https://github.com/dotenv-linter/dotenv-linter dotnet (dotnet build -clp:NoSummary -p:GenerateFullPaths=true --no-incremental --nologo -v q) .NET Core CLI - https://docs.microsoft.com/en-us/dotnet/core/tools/ eslint (eslint [-f stylish]) A fully pluggable tool for identifying and reporting on patterns in JavaScript - https://github.com/eslint/eslint eslint-compact (eslint -f compact) A fully pluggable tool for identifying and reporting on patterns in JavaScript - https://github.com/eslint/eslint ..... ..... .....
余談だが、reviewdog用のフォーマットrdjson
, rdjsonl
というのもあるらしい
これは各リンター用のActionsなどで使っているよう
reviewdog/action-eslint: Run eslint with reviewdog
独自フォーマットの指定
冒頭の設定には出てきていないが紹介しておく
-efm
オプション
-f
で対応していない出力フォーマットの場合、自身で指定が可能
項目についてはREADMEを読めば分かる
たとえば次のフォーマットを指定した場合
-efm="%f:%l:%c %m"
ファイル名:行数:列数 エラーメッセージ
このような形式の出力がでるようにリンター側で調整すればreviewdogが読み込んでくれる
独自リンターとかでも対応可能
めちゃくちゃ親切でerrorformatのplaygroundもあるので実際の出力を調整しながらフォーマット指定を決められる
lint-stagedの--shell
オプション
ここまでreviewdogのオプションの話をしていたが、そもそもreviewdogとリンターを組み合わせる場合はlint-stagedに--shell
オプションを付ける必要がある
が、下記の理由で非推奨らしい…
sindresorhus/execa: Process execution for humans
- クロスplatformではないため、シェルの構文依存になってしまう
- シェルの評価をするので遅くなる
- コマンドインジェクションの可能性がある
使う場合は留意したほうがよい
今回はこのオプションありきでなので設定して進めた
各種リンターでの設定
いくつか設定例を載せておく
secretlint
secretlint、reviewdogとも、checkstyleフォーマットに対応しているので指定するだけ
'*': (filenames) => `${__dirname}/node_modules/.bin/secretlint --format checkstyle --secretlintrc ${__dirname}/.secretlintrc.json ${filenames.join(' ')} | reviewdog -fail-on-error -f checkstyle -diff "git diff --cached" -reporter local`
actionlint
'.github/workflows/*.{yml,yaml}': (filenames) => `actionlint -format '{{range $err := .}}{{$err.Filepath}}:{{$err.Line}}:{{$err.Column}} {{$err.Kind}} {{$err.Message}}\n{{$err.Snippet}}\n\n{{end}}' ${filenames.join(' ')} | reviewdog -fail-on-error -efm="%f:%l:%c %m" -diff='git diff --cached' -reporter local`,
actionlintはcheckstyleに対応してなさそうだったので、reviewdogが解釈できるフォーマットに指定した
actionlint/usage.md at main · rhysd/actionlint · GitHub
grep
単純なgrepも書ける
'*.@(js|ts|tsx)': (filenames) => `grep -nH console.log ${filenames.join(' ')} | reviewdog -fail-on-error -efm="%f:%l: %m" -diff='git diff --cached' -reporter local`,
例でのconsole.log
はeslintでも対応できるが、他にも独自にこの文字列は入れたくないといったパターンで活用できる
まとめ
- lint-staged + huskyでのpre-commitフックにreviewdogを加えて変更差分に対してのみのリンターチェックが可能になった
- eslint,actionlint,secretlint、特定文字列をgrepして検出する設定例を紹介した
プロジェクトに入れる場合、これのために開発環境へ各種コマンド入れておくの?という話になるかなと思うので説明だったり合意が必要だと思う
さっとできる範囲だとglobalなGit hookに対して設定しておくといろいろとはかどるはず
自分は下記のリポジトリを参考にlocalとglobal両方のhookを実行できるようにしている
azu/git-hooks: @azu's global git hooks
いくつか自分用にカスタマイズしてたりするので別途記事にできれば
- 自分用のglobalなgit-hooks