kotlinでlintをかける3つの方法

釘宮慎之介氏:はい、よろしくお願いします。「kotlinでもJavaでも検出できるCustom Lintの作り方」というタイトルで発表します。自己紹介なんですが、釘宮と申します。Androidエンジニアで、DMM.comというところで働いています。

さっそくアジェンダですが「kotlinを使ったAndroidプロジェクトでlintをかける時にどうしたらいいんだっけ?」という話と、「Custom Lintを作るにはどうしたらいいんだっけ?」という話をします。その後に、「じゃあ実際にandroidのlintでCustom Lintを作ってみましょう。」「(作ったandroid-lintのCustom Lintを)テストしてみましょう。使ってみましょう」という流れで話していきます。

kotlinを使ったAndroidプロジェクトでlintをかけるには、以前までは主に2つの方法しかありませんでした。それが「Ktlint」と「detekt」という方法です。

android-gralde-plugin 3.1.0ぐらいから、androidのlintでもkotlinのlintをかけることができるようになりました。android-lintのいいところは、公式が作っているという点、標準のlintのタスクでちゃんとlintしてくれるという点です。あと、後述するんですけど、Custom Lintを作る際に、型情報を使ったlintとかも作れるという点と、加えてkotlinだけじゃなくて、Javaもそのままlintのチェックをかけることができるという点があります。

(Ktlint、detekt、android-lintには)それぞれいいところがあります。所感でいうと、Ktlintとdetektはそんなに大差はなく、どっちかというとStyle寄りで、インデントのサイズとかそこらへんを見てくれるのかなというところがあります。

ただ、Ktlintはルールの一部無視ができないというか、やればできるんですけど、けっこう難しかったりします。なので、僕自身はKtlintを使ってるんですけど、もしかしたらdetektのほうが使いやすいかもしれないです。

androidのlintに関しては、Styleもロジックも確認はできるんですけど、標準のlintのタスクでインデントのサイズとかを見てくれないようなので、そこがちょっと弱いのかなと思っています。

どれを使えばいいのかっていう主観なんですけど、androidのlintを外すことはできないので、Styleとかを見たいのであれば、androidのlintに加えてKtlintやdetektを併用するのがいいのかなと考えています。

Custom Lintを作るにはASTを知る必要がある

Custom Lintの作り方なんですけど、これはKtlint、detekt、androidのlintの場合も一緒で、まずはASTを知らなければ作れません。(スライドを指して)このように、コードはツリーの構造で、コンパイラやIDEとかを解釈します。

例えば、「引数が複数あったら絶対改行してね」みたいなCustom Lintを作りたいとします。上がNGで、下がOKの場合、こういうlintを作りたいっていう時です。

NGの時もOKの時も、PSI、ASTのツリーはこのようになります。

(一部省略しているのでスライド上では)一番上に、ARGUMENTのLISTというノードがあり、その下にARGUMENTである1、カンマ、スペース、2があるというツリーになります。

さっきのOKの時、つまりちゃんと改行されてる時は、この3つ目のスペース、WHITE SPACEに改行コードが含まれていて。

NGの時には改行コードが含まれてません。

なので、Custom Lintを作りたい時は、こういうことを確認します。このようにツリーの構造になっていて、加えて3つ目のWHITE SPACEのところには改行コードがちゃんと含まれてるかという確認をして、逆に入ってない時はNGを出すというようにやります。

それで、「なんとなくわかったけど、まぁツリーがどうなってるかわかんないっすよね」と思うと思いますが、大丈夫です。僕もわかんないんです(笑)。これに関しては、「PsiViewer」というプラグインが用意されています。どういうものかというと、左側にコードを表示して、右側にツリー構造がどうなってるのかを表示してくれるプラグインなので、これを使ってにらめっこしながら作るといいんじゃないかなと思います。

android-lintでのCustom Lintの作り方

次に、androidのlintでCustom Lintを作ってみます。例えば、RxJavaを使っているとして、Disposableを適切に処理してないでそのままにしている場合は警告を出すというCustom Lintを作ります。

手順は5つです。まずプロジェクトを作る。次にDetectorを作って、Issueを作って、Registryを作る。最後にこのRegistryを登録するという手順です。

プロジェクトを作るところについては、いつも通り作ればいいんですが、「googlesamples」というgithubがあって、そこにテンプレートみたいなものがあるので、それを持ってくるとか、ならうのがいいと思います。

Detectorを作るところについては、(スライドを指して)完成形がこんなコードになります。

まず、Detectorというものをextendして、UastScannerというものをimplementします。Uastというのは、Unified ASTの略です。

その次に、createUastHandlerというものをオーバーライドします。つまり実際は何をやるかというと、UIElementHandlerというものを実装することになります。

このUIElementHandlerは、ツリーの上から順にスキャンしていってくれて、その各種ノードが見つかった時に、各種のvisitメソッドを呼びます。このvisitメソッドをオーバーライドして使うようになります。

今回はPsiViewerを見ると、DOT_QUALIFIED_EXPRESSIONというものが来た時に確認するだけで済みそうなので、それに対応するvisitメソッドをオーバーライドするようにします。

visit対象のノードクラスで、さっきのDOT_QUALIFIED_EXPRESSIONというもののクラス情報を、上のgetApplicableUastTypesというところに返してあげるようにします。これはお約束になっていて、これをしないとvisitメソッドをオーバーライドしても呼ばれないということがあるのでやります。

実際のvisitメソッドの中身は、こうなっています。

これ、ソースって見れますかね? 大丈夫かな。この中身は何かというと、親がBLOCKになっている時にメソッドチェーンの最後がsubscribeというものになっています。そのsubscribeのメソッドの返り値がDisposableになっている時にレポートする、となっています。

1つ(話を)戻すと、このDisposableの型が返ってくる情報がKtlintやdetektだと取れなくて、androidのlintでできる点がメリットだと思っています。

加えて、kotlinの場合以外に、Javaの場合も書くようにします。UastScannerはkotlinとJavaと区別なくvisitしてくれるんですけど、kotlinとJavaの時でPSIの型が違うので、別途こちらもPsiViewerとにらめっこしながら作ることになります。

Detectorができたら、Issueというものを作っていきます。このIssueの中には、ID情報や説明とかが入っています。

その後に、作ったIssueをRegistryというところに登録して。

このRegistryをbuild.gradleに記述すればCustom Lintは完成です。

作ったCustom Lintのテストの書き方

作ったCustom Lintをテストしていきます。テストメソッドの書き方はこのようになっています。

対象のコードを文字列で用意します。これは長くなることが多いので、ファイル読み込みにするのがいいと思います。用意したら、それをkotlinファイルとして渡す、つまりkotlinメソッドを呼んで文字列を渡します。そして、lintにかけたらエラー文言が出てくるので、適切なエラー文言になっているかをexpectメソッドで確認します。Javaの時はこの2箇所をJavaに変えればOKです。

作ったCustom Lintの使い方です。これは簡単で、同一プロジェクトにCustom Lintのモジュールがある場合は、dependenciesにlintChecks projectのモジュール名を入れればOKです。他のプロジェクトとかでも同じCustom Lintを使いたい時には、JARにしてあげて、そのJARのパスをlintChecksで渡してあげればOKです。ここまでできれば無事動くことが確認できました。

まとめです。kotlinでlintをかける方法は3つあるっていうことを説明しました。kotlinでもdetektでもandroid-lintの場合でも、Custom Lintを作る際はASTを知らなければいけませんということを説明しました。最後にCustom Lintの作り方を説明しました。ご静聴ありがとうございました。

(会場拍手)