Checkstyleでカスタムチェック

カスタムなCheckstyleを使おうとして、いくつかのページを見たが、
私のケースと少し違って、少々調べる時間が必要になったので、メモを残す。

とはいえ、既によくまとまってるページがあったので、
基本的にはそのページとJava docや公式情報を見れば十分だった。

https://takemikami.com/2018/07/28/checkstyle.html

https://checkstyle.sourceforge.io/writingchecks.html

https://checkstyle.sourceforge.io/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html

ちなみに私の場合はTokenTypesの情報はIDE経由で見た。
(内容は同じ)

## Checkstyleでカスタムチェックをやろうとした動機

元々SpringBootTestを使ってCIでアノテーションチェックを行っていたが、

- 処理が重い。
- Gradleでexcludeを使ってる所為で例外処理を書く必要がある。

などの課題があった。
静的解析でなんとかならないかと思って調べたところ、
既に導入済みのcheckstyleでなんとかなりそうだったので、今回調べたという経緯である。

## Gradleのマルチプロジェクト

私の場合、既にあるマルチプロジェクトの中のサブフォルダの中に
カスタムcheckstyleのプロジェクトを作成した。
このあたり↓の記事を参考にした。

https://stackoverflow.com/questions/45366305/android-checkstyle-8-1-error-unable-to-create-root-module

```gradle:build.gradle
allprojects {
  // 省略
  dependencies {
    checkstyle 'com.puppycrawl.tools:checkstyle:8.1'
    checkstyle project(':helper:custom-checkstyle')  
  }
  checkstle {
    // checkstyleの設定
  }
  // 省略
}
```

このやり方だと自身のプロジェクトもcheckstyleの対象になるので
ちゃんと動くか懸念があったが、特に問題なかった。
恐らくビルドの実行と、checkstyleの実行が別のためだと思われる。

ちなみにカスタムチェックを実装するモジュールの方のbuild.gradleは
こんな感じで継承するclassを含むパッケージを導入する。

```java:./helper/build.gradle
plugins {
    id 'java'
}
repositories {
    mavenCentral()
}
dependencies {
    compile group: 'com.puppycrawl.tools', name: 'checkstyle', version: '8.1'
}
```

## カスタムチェックの実装

実装するには以下を継承したクラスを作成し、

https://checkstyle.sourceforge.io/apidocs/com/puppycrawl/tools/checkstyle/api/AbstractCheck.html

```java
package com.sample;

import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;

public class CustomCheck extends AbstractCheck {
    String annotation;
    public void setAnnotation(String annotation) { this.annotation = annotation; }

    @Override
    public int[] getDefaultTokens() { return this.getRequiredTokens(); }
    @Override
    public int[] getAcceptableTokens() { return this.getRequiredTokens(); }
    @Override
    public int[] getRequiredTokens() {
        return new int[]{TokenTypes.METHOD_DEF};
    }
    // チェック処理の実装
    @Override
    public void visitToken(DetailAST ast) {
        // 前部でMETHODD_DEFを指定するとここでMETHOD_DEFのASTが渡される
    }
}
```

checkstyle.xmlで作成したクラスを指定すれば良い。

```xml:checkstyle.xml
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
        "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
        "https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
  <module name="TreeWalker">
    <module name="com.sample.CustomCheck">
        <property name="annotation" value="Transactional"/>
    </module>
  </module>
</module>
```

- 字句種類は DetailAst#getType() で、字句内容は DetailAst#getText() で取得する。
- NGのものがあればlog()を使う。

logは以下のように該当箇所とエラーメッセージを指定する。

```java
log(ast.getLineNo(), "NG message")
```

あるいはxmlでメッセージやフォーマットを用意しておく方法もある。
(詳しくはAbstractCheckのjava docを参照)

また、私が試した際は標準出力などへの結果は表示されなかったので、
デバッグでcheckMainのタスクなどを実行するか、
logメソッドでprintデバッグを行った。

### DetailAST

ASTの各ノードの扱い方は DetailAST や TokenType などのjava docに詳しく書いてある。

https://checkstyle.sourceforge.io/apidocs/com/puppycrawl/tools/checkstyle/api/DetailAST.html

- DetailAST#findFirstToken を使って子から特定のTokenTypeを検索。
- modifiersのようにpublic/private/static/アノテーションなどがあるものは、
以下のようにして検索できる。

```java
for (DetailAST child = ast.getFirstChild();
     child != null;
     child = child.getNextSibling()) {
  // ループ内の処理
}
```

ASTの構造は以下を参考に。
例えばメソッド定義の構造であれば以下を見れば良い。

https://checkstyle.sourceforge.io/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html