mike-neckのブログ

Java or Groovy or Swift or Golang

eclipseがメインのIDEに指定されているプロジェクトでIntelliJを使って開発する

職場のプロジェクトがメインで指定するIDEeclipseですが、IntelliJ IDEAが大好きなのでIntelliJ IDEAで仕事しています。

するとどうしても問題が出てきます。

  • importの順番が異なるために無駄なdiffが出てくる
  • コードスタイルが異なるのでレビューしづらい
  • インスペクションのレベルが違うのでレビューしづらい/チェックしづらい

importの順番

importの順番は気合で直します(嘘)

eclipseからコードスタイルフォーマットを出力してIntelliJで取り込んでもImportの順番は出力できないようです。

したがって下のQiitaの記事を参考に Code Style > Java > Impors > Import Layout にてImportの順番をeclipseのそれに合わせます。

qiita.com

しかし、時折ですが、eclipseで書いたコードのimport順がおかしな場合もあります。そのようなイレギュラーケースは気合で直していたりします。

コードスタイル

上記の通りeclipseからコードフォーマット設定をxmlで出力して、取り込むことで対応します。しかしeclipseのデフォルト設定で開発している場合などはxmlの情報が少なくなり、かなり変なスタイルが取り込まれることもあったり、そもそもコードスタイルをxmlで出力していないということもあります(.settings/org.eclipse.jdt.core.prefs で管理している場合など)。

この場合はeclipseのフォーマッタをターミナルから起動して修正します。

$ ~/path/to/Eclipse.app/Contents/MacOS/eclipse -application org.eclipse.jdt.core.JavaCodeFormatter -verbose -config ~/path/to/setting/org.eclipse.jdt.core.prefs ~/path/to/project/src/main/java/com/example/Foo.java

なお、以前はこれをgit statusと組み合わせてgit commitのフックで実行していましたが、IntelliJのchangelistが壊れるのでやめて、次のスクリプトを利用してpushの前に実行するようにしています。

#!/usr/bin/env bash

project=~/path/to/project
eclipse=~/path/to/Eclipse.app/Contents/MacOS/eclipse

cd ${project}

head=`git log --oneline --decorate | grep HEAD | awk '{printf $1}'`
last=`git log --oneline -decorate` | grep origin | awk '{printf $1}'`
java_files=`git diff --name-only ${head} ${last} | awk -v dir=${project} '{print dir"/"$1}' awk -F\| 'system("test -f " $1)==0 { print $1 }'`

${eclipse} -application org.eclipse.jdt.core.JavaCodeFormatter -verbose -config ~/path/to/setting/org.eclipse.jdt.core.prefs ${java_files}

git add ${java_files}
git commit -m 'apply eclipse formatter'

コードインスペクション

これが一番困っています。eclipseIntelliJで警告になるレベルが異なる(IntelliJでは設定可能)ようです。下手に警告がちらつくと重要な警告を発見できなかったり、レビューに集中できなかったりと弊害が大きいです。

いろいろと方法を考えたのですが、とりあえずecjでコンパイルするのが一番よさそうです。

apply plugin: 'java'
configurations {
  eclipse
}
dependencies {
  eclipse 'org.eclipse.jdt.core.compiler:ecj:4.4'
  // 他省略
}

task ecj(type: JavaExec) {
    main = 'org.eclipse.jdt.internal.compiler.batch.Main'
    classpath configurations.eclipse.asPath
    args '-encoding', 'utf8'
    args '-source', '8'
    args '-d', "$buildDir/ecj"
    args '-target', '8'
    args '-cp', configurations.compileClasspath.asPath
    args sourceSets.main.allJava
}

このようなタスクを記述した ecj.gradle ファイルを作って、 ./gradlew -b ecj.gradle ecj を起動することで警告を探してeclipseに合わせたコードを記述します。

出力例

15. WARNING in /path/to/project/src/main/java/org/mikeneck/util/Last.java (at line 143)
        .<Last<T>>map(Candidate::new)
                      ^^^^^^^^^^^^^^
Type safety: The constructor Candidate(Object) belongs to the raw type Candidate. References to generic type Candidate<T> should be parameterized
----------
16. WARNING in /path/to/project/src/main/java/org/mikeneck/util/Last.java (at line 147)
        @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Unsupported @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
----------

例えば、上の出力例ではIntelliJでは Optional をフィールドまたはパラメーターに利用すると警告が出てくるので @SuppressWarnings("OptionalUsedAsFieldOrParameterType") をつけますが、eclipseでは OptionalUsedAsFieldOrParameterType に対応していないので警告になっています(Optional をフィールドまたはパラメーターに使っても警告が出ないのだろうか…詳しい人教えて…)。またeclipseでは推論可能な型パラメーターを省略すると警告が出てしまうようです(Foo<T> のコンストラクタを Foo::new と書くと警告が出るが、 Foo<T>::new と書くと警告が出ない。一方IntelliJでは推論可能な型パラメーターの省略を推奨している)。おそらく言語仕様で曖昧に解釈できる箇所をecjでは安全側に倒しているというところでしょうか…。

以上