電通総研 テックブログ

電通総研が運営する技術ブログ

IntelliJプラグイン開発の始め方~リファクタリング編~

こんにちは。電通総研ITの寺尾です。
今回はIntelliJリファクタリング機能の実装方法についてご紹介します。

前回はこちら:IntelliJプラグイン開発の始め方~コード補完編~

リファクタリングとは

クラス名や変数名を変更した際、それを参照している別コードでも一緒にリネームしてくれる、パッケージ移動した際に変更後の状態に合わせてパッケージ名を変更してくれる機能を自然と使用している開発者は多いと思います。

プラグイン開発ではIntelliJに拡張ポイントを実装し、任意の処理をリファクタリングに合わせて実行させることができるようになります。

実装前に知っておくこと

前回と同様に、本記事でもKotlinでの実装例を提示します。

Kotlinプロジェクトに関する内容は、アクション機能編の実装前に知っておくことをご参照ください。

実装

ゴール

今回は、DAO名を変更した際に対応するSQLディレクトリ名をリネームする拡張ポイントの実装をします。

RenameDemo

事前準備

IntelliJプラットフォームプラグインSDKでは、特定の要素に専用のプロセッサークラスが用意されています。
今回はクラス名のリネーム用クラスRenameJavaClassProcessorを使用して拡張ポイントを実装します。

まずは以下の準備から始めていきましょう。

リファクタリング機能は拡張ポイントとして登録します。
plugin.xmlには、以下のように<renamePsiElementProcessor>タグを使ってプロセッサークラスを登録します。
IntelliJの拡張ポイントとして登録するため、<extensions defaultExtensionNs="com.intellij">タグの中に記述します。

    <renamePsiElementProcessor implementation="org.domaframework.doma.intellij.refactoring.dao.DaoRenameProcessor" order="first"/>

プロセッサークラスの実装

プロセッサークラスでは、以下のオーバーライドメソッドを実装します。

  • canProcessElement:リネーム時、プロセッサーの実行条件を満たしているかを判定する
  • renameElement:リネーム前の要素情報とリネーム後の名称を受け取り任意の処理を実行

処理対象の判定

DAO名のリネーム以外でSQLディレクトリ名が変更されないように、しっかり事前チェックをしておきます。

canProcessElementの中で、以下のように処理対象であるかを判定します。
getDaoClass()は、引数の要素がDAOが存在することを判定する独自のメソッドです。任意の処理で同様にチェックしてみてください。

    override fun canProcessElement(element: PsiElement): Boolean =
        super.canProcessElement(element) && getDaoClass(element.containingFile) != null

リネーム処理本体

DAOのリネームによって変更前と後の情報を受け取り、変更前のDAO名情報を基にSQLディレクトリ側もリネームします。
このメソッドは、リネーム処理が呼び出された際にDAO側のリネームが実行される前に処理されます。

そのため、最後に親のrenameElement()を呼び出さないと、DAO側はリネームされないため注意してください。

以下サンプルで呼び出しているgetContentRoot()getPackagePathFromDaoPath()は独自に実装している拡張関数になるため、「Doma Tools」のコードも参考に実装してみてください。

  override fun renameElement(
        element: PsiElement, -- リネーム前の要素情報
        newName: String, -- リネーム後の要素名
        usages: Array<out UsageInfo>, -- 対象要素の参照リスト
        listener: RefactoringElementListener?,
    ) {
        // 変更前要素情報からDAOクラス情報を取得
        val daoClass = getDaoClass(element.containingFile) ?: return

        val project = element.project
        val virtualFile = element.containingFile.virtualFile ?: return

        // DAOパッケージ名を基にSQLディレクトリ要素を取得
        project.getContentRoot(virtualFile)?.let {
            element.module?.getPackagePathFromDaoPath(virtualFile)?.let {
                // ディレクトリ名がDAOクラス名と一致するディレクトリ名をリネーム
                if (it.name == daoClass.name) {
                    it.rename(it, newName)
                }
            }
        }
        // 親のリネーム処理を呼び出す
        super.renameElement(element, newName, usages, listener)
    }

動かしてみる

デバッグ環境でDAOクラス名やファイルのリネームによって対応するSQLディレクトリ名も一緒に変更されることを確認して見ましょう。

デバッグ起動方法は前回と同じく、デバッグ起動するで実行してください。

参考: 「Doma Tools」実装コード

Doma Tools」のリファクタリング機能は、以下コードで実装しています。
DaoRenameProcessor

さいごに

今回はリファクタリング機能の実装についてご紹介しました。

Doma Tools」ではDomaのDAOとSQLの紐づけに対応し、ファイルジャンプだけでなくファイル名の連動も強化しています。
特定のファイル間での連携が必要なプロジェクトにおいて、リファクタリング機能を用意して開発効率を向上させましょう!

Doma Tools」へのレビューも投稿していただけますと大変励みになります🙇‍♀️

Doma Tools マーケットプレースページ

プラグインOSSとしてDomaコミュニティへ寄贈されています。
不具合修正や機能要望、ディスカッションにもぜひご参加ください。

採用ページ

執筆:@terao.haruka
レビュー:@nakamura.toshihiro
Shodoで執筆されました