25
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

#前置き

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

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

インスペクションってなによ?って方はAndroidStudioのInspectionでコードチェックを楽にしてみた。を見て下さい。

#参考にしたコード

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

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

コードの説明

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

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のクラスを探してきて!というメソッドです。

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

#終わりに

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

25
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?