😈

detektをCLIで使用する

2024/06/02に公開

はじめに

detektとはKotlinにおける静的解析ツールです。detektは知っている前提で進めるため、詳しくは公式ドキュメントや既出の記事を参照して下さい。
https://detekt.dev/
detektの実行方法は主に以下2パターンがあります。

  1. gradle/mavenタスクとして実行
  2. CLIで実行

本記事は表題の通り、CLIで実行する場合のポイントをまとめた記事になります。公式にも設定方法があるのですが、いざ導入しようとするとつまりポイントが多かったため、記事として残しておきます。

インストール方法

インストール方法は、基本的に公式ドキュメント通りで問題ないです。
https://detekt.dev/docs/gettingstarted/cli#install-the-cli

linuxなど、公式でいうところのAnyOSの手順でインストールする場合は少し注意が必要です。
手順通りにzipファイルを解凍すると、以下ディレクトリ・ファイルがダウンロードされます。

detekt-cli-${version}
 |-bin
   |-detekt-cli
   |-detekt-cli.bat
 |-lib
   |-detekt-cli-${version}-all.jar

インストールのみだと、PATHが設定されていないので、この状態でdetekt-cli --helpなどを実行しても、以下のようなエラーになります。

bash: detekt-cli: command not found

bashがdetekt-cliを探し出せるように、detekt-cli-${version}/binをPATHに設定しましょう。PATHの設定は既出の記事を参考にして下さい。
https://qiita.com/ryouya3948/items/8edbd5d744c83dd41141

使ってみる

インストール・PATHの設定が完了すれば、基本的に以下コマンドで実行できます。
configファイルの記載方法などは公式に詳しく記載があるので参照して下さい。

detekt-cli --input ${対象ファイルのパス} --config ${configファイルのパス}

https://detekt.dev/docs/introduction/configurations
オプションはこちら
https://detekt.dev/docs/gettingstarted/cli#use-the-cli

詳細設定

ここから本題です。CLIで実行する場合に苦戦した箇所を記載します。内容は主に以下です。

  • detekt-formattingのプラグインを使用する方法
  • Type Resolutionを有効にする方法
  • lint-staged & pre-commitでステージされているファイルのみを対象に実行する方法

detekt-formattingのプラグインを使用する方法

そもそもdetektには、detekt-formattingというプラグインがあります。
ktlintという同じくKotlinのlinterのラッパーです。
https://detekt.dev/docs/rules/formatting/
https://pinterest.github.io/ktlint/latest/

gradle/mavenで使用する場合は、プラグインとして記載するだけで簡単に使用できるのですが、CLIの場合は以下二点に対応する必要があります。

  1. detekt-formatting用のjarファイルをダウンロードし、パスをオプションで設定する
  2. detekt-formatting用のconfigを追記する

では順番に設定していきましょう。

  1. detekt-formatting用のjarファイルをダウンロードし、パスをオプションで設定する

以下のmavenリポジトリからdetekt-formattingのjarファイルをダウンロードしましょう。
https://mvnrepository.com/artifact/io.gitlab.arturbosch.detekt/detekt-formatting/1.23.6

curlでダウンロードする場合はこちら

curl -sSLO https://repo1.maven.org/maven2/io/gitlab/arturbosch/detekt/detekt-formatting/1.23.6/detekt-formatting-1.23.6.jar

jarファイルのダウンロードが完了したら、任意のディレクトリに配置し、実行時の--pluginsオプションにパスを渡すように追記して下さい。

detekt-cli --plugins ${jarファイルのパス} ~~以下省略
  1. detekt-formatting用のconfigを追記する

以下ファイルの内容を、既存のconfigに追記して下さい。CLIの場合はconfigに追記がないと動きませんでした。以下issueを参考にしました。
https://github.com/detekt/detekt/blob/main/detekt-formatting/src/main/resources/config/config.yml
https://github.com/detekt/detekt/issues/4929

以上二つを設定すると、detekt-formattingがCLIで使用できるようになるはずです。

Type Resolutionを有効にする方法

こちらもまずは前提からですが、detektにはType Resolutionというものがあります。こちらも詳細は公式を確認して下さい。
https://detekt.dev/docs/gettingstarted/type-resolution

簡単にいうと、このType Resolutionが有効になっていないと使用できないルールがあります。
gradle/mavenの場合は、公式にもある通り、タスクがいい感じにやってくれます。

detekt - Runs detekt WITHOUT type resolution
detektMain - Runs detekt with type resolution on the main source set
detektTest - Runs detekt with type resolution on the test source set

つまり、detektMainとdetektTestを使用するだけで良いということですね。

CLIの場合はオプションで設定する必要があります。こちらも公式に記載されています。
https://detekt.dev/docs/gettingstarted/type-resolution#enabling-on-detekt-cli

上記に記載がある通り、以下二つのオプションを渡す必要があります。

  • --classpath
    • Kotlinプロジェクトのjarファイルが配置されるパスを渡す
  • --jvm-target
    • 使用しているjvmのバージョン

以下のようなイメージになります。

detekt-cli --classpath "sample/build/lib" --jvm-target "17"

これでCLIでもType Resolutionを有効にできたはずです。試しに、Type Resolutionが必要なルールに反したコードを書き、detekt-cliを実行してみて下さい。

lint-staged & pre-commitでステージされているファイルのみを対象に実行する方法

最後になりますが、そもそもdetektをCLIで実行しようとしたのはここがモチベーションでした。
「ステージされているファイルのみを対象に実行する方法」というのは公式にも記載されています。
https://detekt.dev/docs/gettingstarted/git-pre-commit-hook

筆者の環境は、モノリポになっており、既にlint-stagedを使用してステージされているファイルの拡張子ごとに適切なlinterを当てるという仕組みが備わっていたため、それに乗じて.ktファイルの場合はdetektを実行しよう、というものです。

ということでlint-staged & pre-commit & detekt-cliの設定方法を記載します。

lint-stagedやpre-commitがよくわからない方は、ここでは説明しないので、既出の記事や公式を参考にして下さい。
https://github.com/lint-staged/lint-staged
https://zenn.dev/risu729/articles/latest-husky-lint-staged

必要なのは以下三ファイルで、以下のように設定しています。

  1. pre-commit ~ コミットの際にlint-stagedを起動するため
  2. lint-staged.config.mjs ~ .ktファイルの場合にdetekt-cliを実行するため
  3. detekt.sh ~ 必須ではない。公式に倣ってdetektコマンドを切り出し
pre-commit
npx lint-staged --no-stash
lint-staged.config.mjs
export default {
    "*.kt": (abusolutePaths) => {
        const 相対パス = getRelativePaths(abusolutePaths) // 絶対パスから相対パスを取得する
        return ["${detekt.shのパス}" "${相対パス}"]
    }
}
detekt.sh
#!/bin/bash

echo "Running detekt check..."
fileArray=($@)
detektInput=$(IFS=,;printf  "%s" "${fileArray[*]}")
echo "Input files: $detektInput"

OUTPUT=$(detekt-cli --input "$detektInput" ~その他オプション省略 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
  echo $OUTPUT
  echo "***********************************************"
  echo "                 detekt failed                 "
  echo " Please fix the above issues before committing "
  echo "***********************************************"
  exit $EXIT_CODE
fi

detekt.shは基本公式をそのまま使っています。オプションをこの記事で紹介したように追加しているだけです。

lint-stagedのオプションに--no-stashを渡していますが、これはdetektのauto-correctを実現するためです。lint-stagedは実行結果がエラーの場合は、デフォルトの挙動としてファイルを実行前の状態に戻します。そのため、detektのauto-correctで修正された差分まで戻してしまうためです。

最後は雑でしたが、これでステージされているファイルのみを対象にしてdetektを実行できるようになったのではないでしょうか。

最後に

CLIだと結構設定が複雑で、基礎知識がかなり求められるなと感じました。
detektはgradle/mavenでしか使用したことがなく、設定も簡単だったため、舐めていました、、
それにしても、詰まったところには必ず同じような悩みのissueがある。素晴らしい世界ですね。形はどうであれ、積極的に発信していきましょう。

Discussion