開発本部の飯塚です。
この記事は Clojure Advent Calendar 2022 5日目の穴埋めに向けた記事です。
今回は clj-kondo を Babashka pods として利用する方法を簡単に解説したいと思います。
用語の説明
clj-kondo
clj-kondo とは何か、どう使うのかについては以下の記事でまとめているので もし知らなければ先にこちらを参照することをおすすめします。
Babashka
Babashka は Clojure のインタプリタです。 GraalVM を使ってネイティブイメージとして動くため起動がとても速く Clojure の機能のほとんどがそのまま使えるためスクリプトとしての用途などで広く利用されています。
Babashka pods
Babashka pods とは Babashka にて Clojure ライブラリとして使える「プログラム」です。
あえて強調した通り「プログラム」なので Clojure で書かれている必要は勿論ありません。 Go で作られたものもあれば C# で作られたものもあります。
Babashka pods として動くプログラムは nREPL をベースとしたプロトコルで Babashka とやりとりし動作します。 このプロトコルでは nREPL 同様に Bencode を使っていて、メッセージのやりとりは標準入出力で行われます。 なので基本的には Bencode さえ扱えればどの言語でも Babashka pods として動くプログラムは作れます。
参考までに拙作の Dad でも Babashka pods に対応しているので、その部分へのリンクだけ貼っておきます。 https://github.com/liquidz/dad/blob/main/src/dad/pod.clj
Babashka pods としての clj-kondo
前置きが長くなりましたが、clj-kondo は Babashka と同じ Borkdude 氏によるものなので当然 Babashka pods としても動きます。
一応 clj-kondo 自体が clojars にデプロイされているのでライブラリとしても勿論利用できはするのですが、 Babashka pods として利用することで手軽に便利スクリプトが作れるのでその利点と方法が紹介できればと思います。
今回はあるプロジェクト配下で private にできそうな public var を検出するスクリプト を例に説明します。
準備
まずは準備です。例えば foo.clj
のようなファイルを用意してみましょう。
ファイルの保存先は任意のプロジェクトルート直下を想定しています。
(ns foo (:require [babashka.pods :as pods])) ;; Babashka pods として clj-kondo を読み込む ;; clj-kondo コマンドへのパスが通っている必要あり ;; もし clj-kondo コマンドを持っていない場合は Pod registry も利用可能 ;; https://github.com/babashka/pod-registry (pods/load-pod "clj-kondo") ;; 読み込んだ pod で提供されている ns を require (require '[pod.borkdude.clj-kondo :as clj-kondo])
これだけで Babashka pods として clj-kondo を使う準備は完了です。 clojars のライブラリから使う場合は Leiningen や Clojure CLI を使って project.clj なり deps.edn なりから用意する必要がありますが、 Babashka pods から使う場合はファイル1つだけなのでかなり手軽であることがわかると思います。
エラーが無いかは bb
コマンドを使って bb foo.clj
のように実行しても確認はできますが、
いくら Babashka の起動が速いとは言っても非効率なので Clojure 開発環境から Babashka の REPL に接続することをおすすめします。
そうすることでフォーム単位での評価ができ、 REPL 駆動でスクリプトを書くことが可能になります。
なお最近の Clojure 開発環境であれば大抵は Babashka に対応しているはずなので、 どうやって Babashka の REPL に接続するのかは各開発環境のドキュメントを参照してください。 例えば拙作の vim-iced では IcedInstantConnect コマンドでREPLの起動と接続が可能です。
なおもし Clojure の開発環境がない場合は以下のまとめ記事を参考にすると良いでしょう。
プロジェクトの解析
次に実際に clj-kondo を使って解析データを取得してみましょう。
:lint
で指定しているディレクトリは検出したいプロジェクトに応じて変更してください。
今回説明するスクリプトの中で一番時間がかかるのがここの clj-kondo による解析処理なので、例えば analysis-data
として束縛しておけばその後の解析データを使った処理でデータ構造の確認や情報の抽出が楽になります。
(def analysis-data (-> {:lint ["src"] :config {:output {:analysis true}}} (clj-kondo/run!) (:analysis)))
public な var の抽出
解析データが取得できたら次は public な var の抽出です。
今回は :var-definitions
という var の定義情報を利用します。
解析データに他にどのようなものが含まれるのかの詳細は clj-kondo のドキュメントを参照してください。
(def public-vars (->> (:var-definitions analysis-data) ;; public なものだけにしたいので private は除外 (remove :private) ;; 必要な情報(ns名, var名)だけにする (map #(select-keys % [:ns :name])) (distinct)))
結果の出力
これで最後です。
「privateにできそう」というのは言いかえると「varが定義されているns以外で使われていない」ということです。 それをそのまま条件として書き出して、該当する var を出力します。
var の利用状況は解析データの :var-usages
にあります。
(doseq [v public-vars] (let [;; public な var を使っている箇所を抜き出す usages (filter #(= (:name v) (:name %)) (:var-usages analysis-data))] ;; var の利用元の ns 名がすべて定義されている ns と一致するならば、それは「privateにできそうな var」 (when (every? #(= (:ns v) (:from %)) usages) (println (format "%s/%s" (:ns v) (:name v))))))
これで public だけど private にできそうな var が表示できました。
勿論ライブラリ等のコードで意図的に public にしているものも表示されてしまうとは思いますが、
例えば public-vars
の抽出時に特定の var は除外するみたいなことは自由にできるので、自身のプロジェクトに応じてカスタマイズすれば使えるスクリプトになるかと思います。
これ以外にも例えば以下のようなことも可能です。
- テストコードの無い public var を表示する
- ns のエイリアスとして他ファイルと異なるエイリアスを使っている箇所を表示する
今回紹介したスクリプトの全体は以下の Gist に保存してありますので、とりあえず試してみたい方はこちらからコピーしてください。
最後に
clj-kondo の解析データはコマンドからも取得可能で、それをパイプしてワンライナーであれこれする方法もありますが、 試行錯誤のしづらさが個人的にネックでした。
それと比べて以上のようなことが1ファイルで、かつ REPL 駆動で書けるのは嬉しい人も多いのではないでしょうか?
clj-kondo は解析データからはいろいろな情報が取得可能なので、この記事内で紹介したこと以外でも便利な使い道があるはずです。
リンターであるという認識が強いのか解析データを利用する方面での記事はあまり多くない印象なので、これを機にこんなこともできて便利だよ!という記事が増えたら良いなと思います。
トヨクモでは一緒に働いてくれる技術が好きなエンジニアを募集しております。