仕事で困ったのでIntelliJのプラグインを作る


はじめに

Javaを書いている時に、こんなクラスのインスタンスを生成する必要があるテストを書くことがあった。


@Value
public class Sample {
    private final Integer a;
    private final String b;
    private final SomeClass c;
}

SomeClassの中にはまたいくつかのオブジェクトが入れ子になっており、このクラスのインスタンスを作るだけでなかなか疲弊してしまう。加えてそれぞれのフィールドに初期値を代入する必要があり、テストに関係ない(オブジェクトさえあればテストができる)どうでもいい初期値を型ごとに考えていた。チームメンバーからも同様の悩みが挙がっていたため、今回はIntention Actionを利用したIntelliJのプラグインでどうにかしてみることにした。
正直なところ詳しいところは公式リファレンスに全部載っているため、それを読めばいいんだと思う。あくまでこれはやってみた記事。

Intention Actionsとは

IntelliJのEditor上で呼び出せる便利なアクション。MacのショートカットはOption(⌥)+Enter。WindowsだとAlt+Enterだと思う。
詳しい情報は公式Helpにある。

プラグイン作成

めざしたもの

クラスのフィールドに型ごとの初期値を入れたインスタンス生成の記述をクリップボードにコピーしてくれるようなプラグイン。
例えば


@Value
public class Sample {
    private final Integer a;
    private final String b;
    private final SomeClass c;
}

のクラスで実行したら

new Sample(0, "", new SomeClass())

とテキトー初期化インスタンスをクリップボードにコピーする。

つくったもの

GitHubに上げた。色々修正するところはある。

アクション

実行したいアクションを「〜Action」という名前でJavaで作成。


public class CopyAction extends PsiElementBaseIntentionAction {

    private static final String NEW_LINE;
    private static final Map<String, String> initialValueLUT = new HashMap<>();

    static {
        initialValueLUT.put("boolean", "false");
        initialValueLUT.put("Boolean", "false");
        initialValueLUT.put("int", "0");
        initialValueLUT.put("byte", "(byte)0");
        initialValueLUT.put("Byte", "(byte)0");
        initialValueLUT.put("Integer", "0");
        initialValueLUT.put("String", "\"\"");
        initialValueLUT.put("BigDecimal", "new BigDecimal(\"0\")");
        initialValueLUT.put("Long", "0L");
        initialValueLUT.put("long", "0L");
        initialValueLUT.put("short", "(short)0");
        initialValueLUT.put("Short", "(short)0");
        initialValueLUT.put("Date", "new Date()");
        initialValueLUT.put("float", "0.0F");
        initialValueLUT.put("Float", "0.0F");
        initialValueLUT.put("double", "0.0D");
        initialValueLUT.put("Double", "0.0D");
        initialValueLUT.put("Character", "\'\'");
        initialValueLUT.put("char", "\'\'");
        initialValueLUT.put("LocalDateTime", "LocalDateTime.now()");
        initialValueLUT.put("LocalDate", "LocalDate.now()");
        initialValueLUT.put("List", "[]");

        NEW_LINE = System.getProperty("line.separator");
    }

    @Override
    public void invoke(@NotNull Project project, Editor editor,
                       @NotNull PsiElement element) throws IncorrectOperationException {
        if (element.getParent() instanceof PsiClass) {
            final PsiClass psiClass = ((PsiClass) element.getParent());
            final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            final String clipBoardText = makeText(psiClass.getName(), psiClass.getAllFields());
            final StringSelection selection = new StringSelection(clipBoardText);
            clipboard.setContents(selection, selection);
        }
    }

    @Override
    public boolean isAvailable(@NotNull Project project, Editor editor,
                               @NotNull PsiElement element) {
        return true;
    }

    @Nls
    @NotNull
    @Override
    public String getFamilyName() {
        return "";
    }

    @NotNull
    @Override
    public String getText() { return "Add Field to ClipBoard";}

    private String makeText(final String className, final PsiField[] psiFields) {
        String clipBoardText = "new " + className + "(" + NEW_LINE;
        clipBoardText += Arrays.stream(psiFields)
                                 .map(p -> "/* " + p.getName() + " */ " + Optional.ofNullable(initialValueLUT
                                                                       .get(p.getTypeElement().getFirstChild().getFirstChild().getText()))
                                                   .orElse("new " + p.getType()
                                                                            .toString()
                                                                            .split(":")[1] + "()"))
                                 .collect(
                                         Collectors.joining("," + NEW_LINE));
        clipBoardText += ")";

        return clipBoardText;
    }
}

plugin.xml

今回行いたいアクションはIntention Actionなので、plugin.xmlに実行クラスをIntentionActionタグで囲って記載する。

  <extensions defaultExtensionNs="com.intellij">
    <intentionAction>
      <className>CopyAction</className>
    </intentionAction>
  </extensions>

拡張ポイントについてはプラグインの拡張ポイントから探す(IntentionActionsはLangExtentionPoints.xmlの43行目あたり)。

つくったもの

とりあえず初期値をいれつつインスタンスを作成する記述をクリップボードにコピーするプラグインをつくれた。