カスタムなCheckstyleを使おうとして、いくつかのページを見たが、
私のケースと少し違って、少々調べる時間が必要になったので、メモを残す。
とはいえ、既によくまとまってるページがあったので、
基本的にはそのページとJava docや公式情報を見れば十分だった。
ちなみに私の場合はTokenTypesの情報はIDE経由で見た。
(内容は同じ)
Checkstyleでカスタムチェックをやろうとした動機
元々SpringBootTestを使ってCIでアノテーションチェックを行っていたが、
- 処理が重い。
- Gradleでexcludeを使ってる所為で例外処理を書く必要がある。
などの課題があった。
静的解析でなんとかならないかと思って調べたところ、
既に導入済みのcheckstyleでなんとかなりそうだったので、今回調べたという経緯である。
Gradleのマルチプロジェクト
私の場合、既にあるマルチプロジェクトの中のサブフォルダの中に
カスタムcheckstyleのプロジェクトを作成した。
このあたり↓の記事を参考にした。
allprojects {
// 省略
dependencies {
checkstyle 'com.puppycrawl.tools:checkstyle:8.1'
checkstyle project(':helper:custom-checkstyle')
}
checkstle {
// checkstyleの設定
}
// 省略
}
このやり方だと自身のプロジェクトもcheckstyleの対象になるので
ちゃんと動くか懸念があったが、特に問題なかった。
恐らくビルドの実行と、checkstyleの実行が別のためだと思われる。
ちなみにカスタムチェックを実装するモジュールの方のbuild.gradleは
こんな感じで継承するclassを含むパッケージを導入する。
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
compile group: 'com.puppycrawl.tools', name: 'checkstyle', version: '8.1'
}
カスタムチェックの実装
実装するには以下を継承したクラスを作成し、
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 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は以下のように該当箇所とエラーメッセージを指定する。
log(ast.getLineNo(), "NG message")
あるいはxmlでメッセージやフォーマットを用意しておく方法もある。
(詳しくはAbstractCheckのjava docを参照)
また、私が試した際は標準出力などへの結果は表示されなかったので、
デバッグでcheckMainのタスクなどを実行するか、
logメソッドでprintデバッグを行った。
DetailAST
ASTの各ノードの扱い方は DetailAST や TokenType などのjava docに詳しく書いてある。
- DetailAST#findFirstToken を使って子から特定のTokenTypeを検索。
- modifiersのようにpublic/private/static/アノテーションなどがあるものは、
以下のようにして検索できる。
for (DetailAST child = ast.getFirstChild();
child != null;
child = child.getNextSibling()) {
// ループ内の処理
}
ASTの構造は以下を参考に。
例えばメソッド定義の構造であれば以下を見れば良い。