Integrantのコードナビゲーションを可能にするIntelliJプラグイン

この記事はJetBrains Advent Calendar 2021の1日目の記事です。

今の会社ではClojureでバックエンドのアプリケーションを開発しており、Integrant というライブラリを使っていわゆるDI(Dependency Injection)をしています。

Integrantの詳細はこちらの記事が詳しいです。

scrapbox.io

コンポーネントの設定と初期化のコードは同じファイルに書くこともできますが、規模が大きい場合は大抵設定のマップを別途ednファイルに書くと思います。以下が例です。

コンポーネントの設定(edn)

{:adapter/jetty {:port 8080, :handler #ig/ref :handler/greet}
 :handler/greet {:name "Alice"}}

コンポーネントの初期化(clj)

(require '[ring.adapter.jetty :as jetty]
         '[ring.util.response :as resp])

(defmethod ig/init-key :adapter/jetty [_ {:keys [handler] :as opts}]
  (jetty/run-jetty handler (-> opts (dissoc :handler) (assoc :join? false))))

(defmethod ig/init-key :handler/greet [_ {:keys [name]}]
  (fn [_] (resp/response (str "Hello " name))))

普段コードを読んでいる時にあるコンポーネントがどういう風に初期化されているか調べたい場合が結構あるんですが、これを行うにはコンポーネントのキーでgrepしてinit-keyが前にある行にジャンプするという手間がかかり、これをもっと簡単に行えるIntelliJプラグインを開発しました。

デモ


www.youtube.com

以下の機能をサポートしています。

プラグインIntelliJのMarketplaceに公開されています。 Integrant - IntelliJ IDEs Plugin | Marketplace

実装の詳細

ソースコードはこちらです。 https://github.com/shinichy/integrant-intellij-plugin

ベースにしたテンプレートのコードがKotlinだったので、Kotlinで実装しました。

Direct Navigationという仕組みを使って実装しています。Go to Declaration or Usages コマンド(MacだとCmd+BもしくはCmdキー押しながらマウスクリック)を関数や変数を使用している箇所で実行するとそれらの定義の箇所にジャンプできますが、Clojureのキーワードを対象に実行しても通常何も起きません。Direct Navigationはそのコマンドの実行をフックする仕組みで、getNavigationElement(element: PsiElement) で 対象の elementに対応する PsiElementを返すと返したPsiElementにジャンプしてくれます。例えば element がMapのキーの時に対応するコンポーネントinit-keyPsiElementを返すことで、Mapのキーからinit-keyの初期化コードにジャンプさせることができます。PsiElementIntelliJがコードをパースした結果のASTの要素です。PSI Elements | IntelliJ Platform Plugin SDK

Mapのキーに対応したinit-key の定義を探す際はintegrant.core/init-keyの使用箇所を検索し、init-keyの後にMapのキーと一致するキーワードを探しています。コマンドの実行の度に毎回検索しているのでちょっと遅いですが、今の会社のプロジェクトの規模くらいだとそこまで気にはなりませんでした。もっと早くするために専用のインデックスを用意してもよさそうです。

IntelliJが生成するASTは当然Clojureのコードに対応していることが前提なので、このプラグインCursiveに依存しています。結構Cursiveの内部の実装クラス(cursive.psi.impl.*)に依存してしまっているので、将来のCursiveのアップデートで動かなくなってしまうかもしれません。。

感想

プラグインを開発するのは今回初めてだったのですが、公式のテンプレートがあり、これをベースにするとGithub Actions等も含めてプロジェクトの雛形を生成してくれるので立ち上がりはかなりいい開発体験でした。

Kotlinは今回初めて書いたのですが、Scalaに似ているのでそれほど違和感なくすんなり書けました。慣れのせいもありますがやっぱりScalaの方が好みです(returnをいちいち書かなくてはいけなかったり、nullable型よりOptionの方が扱いやすい等)。

プラグイン開発のドキュメントは公式ドキュメントが結構充実していて参考になりますが、それ以外ほとんど情報がなく、公式ドキュメントにない情報は他のプラグインIntelliJソースコードを参照するしかなく結構大変でした。テストも実装しようとしたのですが、何故かうまく動かず諦めました。。