detektを2.0に移行する 〜カスタムルールを添えて〜

に公開

はじめに

detektを2.0(alpha)に移行したので、私の環境でやったことをまとめます。
個人的な調査であり、設定が誤っている可能性もあるので、参考程度にご覧ください。

背景

Kotlinを2.3.0に上げようとしたところ、detektでエラーが出たので調べたのだが「Releasing further 1.x releases is heavily unpractical」という言及を見つけた。

https://github.com/detekt/detekt/issues/8865

そのため、alphaではあるものの、動かしてみようと思い、移行してみることになった。

環境

AGP:8.13.2
detekt:1.23.8 -> 2.0.0-alpha.1

移行手順

公式にマイグレーションガイドがありそうに見えるが、差分が載っているだけでガイドは見つからなかった。
https://detekt.dev/changelog-2.0.0/
差分やエラー、issue、Githubリポジトリを見ながら修正していった。

パッケージ名の修正

ライブラリのパッケージ名が変更になっている。
io.gitlab.arturbosch.detekt -> dev.detekt

tomlファイルやbuild.gradleファイルで定義しているものを修正する

build.gradleの記載を修正

レポート設定

xmlをcheckstyleに変更

before
reports {
    xml.required.set(true)
    xml.outputLocation = file(...)
}
after
reports {
    checkstyle.required.set(true)
    checkstyle.outputLocation = file(...)
}

Excludeがうまく効かないことの修正

同じソースでdetektを実行した際、1.x系では除外できていたAnnotationProcessingによる自動生成物までが検査対象になってしまっていた。(RoomのDaoやshowkaseなど)
通常のExcludeでは効かなかったので以下のような対応を行った。
(何か設定が誤っているせいで起きている可能性もあるため、正しい解決策ではないかもしれない。)

tasks.withType(Detekt).configureEach {
    exclude { f -> f.file.path.contains("/build/generated/")
}

指摘ファイルのパス変更に伴う対応

「detekt結果のxml(checkstyle)をDangerに流してプルリクでコメントする」という使い方をしていたが、そこでエラーが起きた。
原因は指摘対象ファイル名のパス変換エラーで、detekt1.x系は絶対パスだったのに対し、detekt2.x系では相対パスになっていた。
絶対パスを相対パスに変換して処理していたので、その処理を消したらうまく動くようになった。

beforeのxml出力
<file name="/Users/.../module/src/main/.../Hoge.kt">
	<error ... />
</file>
afterのxml出力
<file name="module/src/main/.../Hoge.kt">
	<error ... />
</file>

SARIFに関して相対パスの方が良いという議論があり、この関連でcheckStyleもそうなったと考えられる。
一方でドキュメントでは、「basePathを設定すると相対パスに出来る」といったことが書いあり、絶対パスにしたい場合どうすればよいかは分かっていない。
実行した感じでは、指定してもしなくても相対パスだが、指定することでルートを変化させることが出来た。
個人的には相対パスで問題ないので、調査はここまでにした。

Configファイルの修正

使われなくなった項目

以下の項目が使われなくなったので削除(これは私のConfigファイルの場合)

build:
  maxIssues:
  excludeCorrectable:
  weights:
  ...

config:
  validation: true
  warningsAsErrors: false
  ...

processors:
  ...

console-reports:
  ...

output-reports:
  ...

maxIssuesがなくなったことにより、一つでもエラーがある場合にdetekt実行結果がエラーになるようになった。
もしエラーにしたくない場合は、build.gradleのdetektブロックでignoreFailures = trueを指定すれば良い。

detekt{
    ignoreFailures = true
}

(根本的には、baselineを使うなどして、ignoreしない方が良い)

変更が必要なもの

ルールに設定できるしきい値thresholdが、それぞれに具体化された名前に変更になっている。
例えば、CognitiveComplexMethodallowedComplexityComplexConditionallowedConditionsという具合に。
慣れていると全部thresholdでいいのではと思うが、より分かりやすくしたのだと推測。
2.xのドキュメントにそれぞれの設定名が載っているので、調べて正しいものに修正する。
名前が変わっているルールもありそうだったが、詳細はdetekt実行時のログに書いてあるので見てほしい。

カスタムルールの修正

自前でルールクラスを作っている場合に必要な修正。

コンストラクタ

カスタムルールが継承するRuleクラスからissueプロパティが無くなり、Ruleのコンストラクタにdescriptionを渡すようになった。

before
class CustomRule(config: Config) : Rule(config) {

    override val issue: Issue = Issue(
        id = "CustomRule",
        severity = Severity.Style,
        description = "This is Custom rule",
        debt = Debt.FIVE_MINS
    )
after
class CustomRule(config: Config) : Rule(
    config,
    description = "This is Custom rule",
) {

idはクラス名が使われ、severityはconfig.xmlで設定するようになった。
idも aliasesとしてconfig.xmlで設定可能。
2.0で利用者側の自由度が上がった形だ。
これらは、configファイルのドキュメントに記載がある。

debtは廃止された様子。

CodeSmellがなくなった

Findingを使えば良さそう。
issueは渡さなくなる(Ruleにissueがなくなったので修正は容易)

before
report(
    CodeSmell(issue, entity, message)
)
after
report(
    Finding(entity, message)
)

RuleSetProvider

ruleSetIdがクラス定義になった。
instanceから引数が無くなり、configを受け取ってRuleを返す関数を定義するようになった。

before
class CustomRuleSetProvider : RuleSetProvider {
    override val ruleSetId: String = "custom"
    override fun instance(config: Config): RuleSet = RuleSet(ruleSetId, listOf(
        CustomRule(config),
    ))
after
class CustomRuleSetProvider : RuleSetProvider {
    override val ruleSetId: RuleSet.Id = RuleSet.Id("custom")
    override fun instance() = RuleSet(ruleSetId, listOf(
        ::CustomRule,
    ))
}

カスタムルールのテストの修正

compileAndLintがなくなったので、lintに修正。
compileAndLintと同様にList<Finding>が返るので、使い方は今までと変わらない。

before
val code = "target code here"
val result = CustomRule(Config.empty).compileAndLint(code)
// ... resultをテストする
after
val code = "target code here"
val result = CustomRule(Config.empty).lint(code)
// ... resultをテストする

余談だが、テストは「想定コードの文字列をルールに渡して検出されるか」をテスト出来るので重宝している。

最後に

これでdetektを2.0に上げて、kotlin2.3.0で動かすことが出来た。
Excludeがうまく行かない問題は、力技で解決している感があり、なにか私の設定が誤っている可能性があるかもしれない。
指摘やマサカリがあれば投げてほしい。

Discussion