AndroidStudioで独自Inspectionを作って、Activity.createIntent/Fragment.newInstanceを強制してみた

#前置き

ちょっと前にQiitaやTwitterで盛り上がっていたネタなんですが、Activity.createIntent/Fragment.newInstanceが便利そうだと知り、ここ数ヶ月ActivityやFragmentに入れて作るようになりました。

ただ、Javaの制約上、staticメソッドの実装は強制が出来ません。
これでは運用でカバーと同じ。自分は規約厨なので、我慢なりません。
そういえば、カスタムインスペクションって作れるとか書いておきながら、やってないじゃん！ってことで、作ってみました。

インスペクションってなによ？って方は[AndroidStudioのInspectionでコードチェックを楽にしてみた。](http://qiita.com/shiraji/items/03be03a9b1cbe5ecc433)を見て下さい。

#参考にしたコード

butterknife_inspections
https://github.com/erikzielke/butterknife_inspections

HungarianInspectionPlugin (@takahiromさんいつもありがとうございます！)
https://github.com/takahirom/HungarianInspectionPlugin

IntelliJ community (特にLogger initialized with foreign classインスペクション)
https://github.com/JetBrains/intellij-community

# Activity.createIntentを強制するInspection

Activity createIntent Inspection
Github: https://github.com/shiraji/create-intent-inspection
Plugin: https://plugins.jetbrains.com/plugin/7915?pr=

![screenshot.gif](https://qiita-image-store.s3.amazonaws.com/0/20058/66dae209-1300-5a24-72d8-f80d40bab328.gif)

# Fragment.newInstanceを強制するInspection

Fragment newInstance Inspection
Github: https://github.com/shiraji/new-instance-inspection
Plugin: https://plugins.jetbrains.com/plugin/7916?pr=

![fragment_ss.gif](https://qiita-image-store.s3.amazonaws.com/0/20058/6576fe83-35f3-d857-cd0b-86a3007a9e55.gif)

# コードの説明

基本的にどちらも同じ構成になっています。

plugin.xmlにInspectionの登録

```plugin.xml
    <extensions defaultExtensionNs="com.intellij">
        <!-- Add your extensions here -->
        <localInspection enabledByDefault="true"
                         groupBundle="messages.InspectionsBundle"
                         groupPath="Android"
                         implementationClass="com.github.shiraji.createintentinspection.inspection.CreateIntentInspection"
                         language="JAVA" level="WARNING"/>
    </extensions>
```

CreateIntentInspection.javaはJava用InspectionクラスのBaseJavaLocalInspectionToolを継承し、必要メソッドを実装しました。

```CreateIntentInspection.java
public class CreateIntentInspection extends BaseJavaLocalInspectionTool {
    @Nls
    @NotNull
    @Override
    public String getGroupDisplayName() {
        return "Android";
    }

    @Nls
    @NotNull
    @Override
    public String getDisplayName() {
        return "Activity should implement createIntent()";
    }

    @NotNull
    @Override
    public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) {
        return new CreateIntentInspectionVisitor(holder, isOnTheFly);
    }
}

```

PsiElementVisitor.javaを継承したVisitor内で実際にワーニングを出すのかどうか、どんなワーニングを出すのかなどを実装します。難しいのがIntelliJ独自のPsiClass関連。このクラスが表示しているクラスやメソッド、メンバーなどにアクセスできるようになるのですが、大規模＋複雑でなかなかわかりづらい・・・。

必要だった処理をUtilとして作成しました。（IntelliJ内にあるのかもしれないけど、見つけられなかった・・・。）

```InspectionPsiUtil.java
public class InspectionPsiUtil {
    public static PsiClass createPsiClass(String qualifiedName, Project project) {
        final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
        final GlobalSearchScope searchScope = GlobalSearchScope.allScope(project);
        return psiFacade.findClass(qualifiedName, searchScope);
    }

    public static boolean isAbstactClass(PsiClass aClass) {
        if (aClass == null || aClass.getModifierList() == null) {
            return false;
        }

        return aClass.getModifierList().hasModifierProperty("abstract");
    }

    public static boolean isStaticMethod(PsiMethod method) {
        return hasMethodModifier(method, "static");
    }

    public static boolean isPublicMethod(PsiMethod method) {
        return hasMethodModifier(method, "public");
    }

    private static boolean hasMethodModifier(PsiMethod method, String aStatic) {
        return method != null &&
                method.getModifierList().hasModifierProperty(aStatic);
    }

}
```

特に大変だったのが、今見ているクラスがAcitivityやFragmentを継承しているかどうか？という判別処理。
PsiClass#isInheritor(PsiClass, boolean)を使えばわかるのですが、引数のPsiClassをActivityやFragmentのPsiClassにする必要があって、それがなかなか大変。モックでなんとかごまかそうとしたけどわかりにくい上に気持ち悪い。
そこでbutterknife_inspectionsのコードを参考にして、`createPsiClass(String, Project)`を作りました。
これは今開いているproject内からqualifiedNameのクラスを探してきて！というメソッドです。

ここを突破しちゃえばあとはすんなりいけました。

#終わりに

作ってみて改めて実感。ライブラリは小さくするべき。風呂敷ひろげすぎても何もいいことない。
（この二つリリースする前に大失敗を犯した。どこかの勉強会で話すネタとしておきます。）