コマンドラインでヘッドレス IntelliJ IDEA を使って静的コード解析

FindBugs の不穏な話を聞いて、まだ余裕あるけれど他の方法があっても良いかなと考えていたところで、こんな記事を見た:

* [FindBugsコミュニティにおける例の件の顛末、そしてSpotBugsとは何か - Kengo's blog](http://blog.kengo-toda.jp/entry/2016/11/14/235642)

> 他にはIntelliJ IDEAも推奨されています。IDEAはIDEですが、[バッチ（CI）に組み込んでXMLを出力させることも可能][1]とのことです。

どうやら、IDEA でコードを書いている最中に表示される諸々の警告をヘッドレス環境でも取得できるらしい。

ところが[該当リンク][1]に飛んでみてもあまり詳しいことが書いてなかったので調べてみた。

[1]: https://mailman.cs.umd.edu/pipermail/findbugs-discuss/2016-November/004316.html


まずはじめに
----------------

ローカル環境でやるなら IntelliJ IDEA を終了させること。

すでに起動している IDEA インスタンスがあると、後述のコマンドは期待通りに動かない。


コマンドどこ
---------------

Mac だと app の中にコマンドが入ってる:

~~~~~~~~~~~~~~~~~~~~~~~~~~~bash
$ /Applications/IntelliJ\ IDEA\ 15.app/Contents/bin/inspect.sh
~~~~~~~~~~~~~~~~~~~~~~~~~~~

このコマンドは以下の引数を必要としている:

* 第1引数: プロジェクトルート
* 第2引数: 解析の設定ファイル
* 第3引数: 出力先ディレクトリ

オプションなどもあるが、それは引数を満たさないで実行すれば表示される。


実行してみる
--------------

* 第1引数: 偶々あった手元のプロジェクト
* 第2引数: `~/Library/Preferences/IntelliJIdea15/inspection/Default.xml`
    * 標準のやつ
* 第3引数: `/tmp/idea_inspect_result`

~~~~~~~~~~~~~~~~~~~~~~~~~bash
$ /Applications/IntelliJ\ IDEA\ 15.app/Contents/bin/inspect.sh ＜適当なプロジェクトルート＞ ~/Library/Preferences/IntelliJIdea15/inspection/Default.xml /tmp/idea_inspect_result
2016-11-16 16:48:54.230 idea[61909:4744355] Value of IDEA_JDK: (null)
2016-11-16 16:48:54.234 idea[61909:4744363] fullFileName is: /Applications/IntelliJ IDEA 15.app/Contents/bin/idea.vmoptions
2016-11-16 16:48:54.234 idea[61909:4744363] fullFileName exists: /Applications/IntelliJ IDEA 15.app/Contents/bin/idea.vmoptions
2016-11-16 16:48:54.234 idea[61909:4744363] Value of IDEA_VM_OPTIONS is (null)
2016-11-16 16:48:54.234 idea[61909:4744363] Processing VMOptions file at /Applications/IntelliJ IDEA 15.app/Contents/bin/idea.vmoptions
2016-11-16 16:48:54.235 idea[61909:4744363] Done
/Applications/IntelliJ IDEA 15.app/Contents/bin/idea.properties: 'java.endorsed.dirs' already defined
log4j:WARN No appenders could be found for logger (io.netty.util.internal.logging.InternalLoggerFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
Nov 16 16:48:57  idea[61909] <Warning>: CGSConnectionByID: 0 is not a valid connection ID.
Nov 16 16:48:57  idea[61909] <Warning>: CGSConnectionByID: 0 is not a valid connection ID.
# ... 略
[ 107798]  ERROR - spection.InspectionApplication - JDK: 1.8.0_76-release
[ 107798]  ERROR - spection.InspectionApplication - VM: OpenJDK 64-Bit Server VM
[ 107798]  ERROR - spection.InspectionApplication - Vendor: JetBrains s.r.o
[ 107798]  ERROR - spection.InspectionApplication - OS: Mac OS X
[ 107799]  ERROR - spection.InspectionApplication - Last Action:
~~~~~~~~~~~~~~~~~~~~~~~~~

エラー出てるけど exit status は 0 で終わっている。

`wc -l **/*.java` が 36000 行くらいで 90 秒くらいだった。行儀よく書いて警告がもっと少なかったら速くなるのかな？


結果
------------

~~~~~~~~~~~~~~~~~~~~bash
$ ls -1 /tmp/idea_inspect_result/
CanBeFinal.xml
ClassMayBeInterface.xml
ConfusingElse.xml
# ... 略
XmlUnboundNsPrefix.xml
unused.xml
~~~~~~~~~~~~~~~~~~~~

大量に警告が出てきたので掃除しよう・・・

試しに中身を見てみる:

~~~~~~~~~~~~~~~~~~~~~~~bash
$ cat /tmp/idea_inspect_result/unused.xml
<?xml version="1.0" encoding="UTF-8"?>
<problems is_local_tool="false">
  <problem>
    <file>file://$PROJECT_DIR$/JavaファイルAまでのパス</file>
    <line>211</line>
    <module>プロジェクト名が入ってる</module>
    <package>xxx.yyy.zzz</package>
    <entry_point TYPE="method" FQNAME="該当メソッドのFQNAME" />
    <problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">unused declaration</problem_class>
    <hints>
      <hint value="comment" />
      <hint value="delete" />
    </hints>
    <description>Method is never used.</description>
  </problem>
  <!-- ... 略 -->
  <problem>
    <file>file://$PROJECT_DIR$/JavaファイルXまでのパス</file>
    <line>53</line>
    <module>プロジェクト名が入ってる</module>
    <package>xxx.yyy.zzz</package>
    <entry_point TYPE="class" FQNAME="該当クラスのFQNAME" />
    <problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">unused declaration</problem_class>
    <hints>
      <hint value="comment" />
      <hint value="delete" />
    </hints>
    <description>Class is not instantiated.</description>
  </problem>
</problems>
~~~~~~~~~~~~~~~~~~~~~~~

簡単な構造なようなので、上述のリンク先のMLに書いてある通りにパーサを用意するのは難しくは無さそうかな？


CI に組み込むには
-----------------

* CheckStyle や FindBugs と違って gradle 一発でインストールされるわけじゃないので、CI サーバに IntelliJ IDEA を設置する必要がある。
* 上述したとおり、結果をパースして適切な処理をする必要がある。


フォーマッタもあるよ
--------------------

最新の IntelliJ IDEA から、フォーマッタも呼べるようになっているみたい:

* https://github.com/JetBrains/intellij-community/blob/master/bin/mac/format.sh
* https://confluence.jetbrains.com/display/IDEADEV/Command-Line+Source+Code+Formatter