Perlでモジュールを自動でインポートするためのツールを作った

普段仕事で Perl を書いている。

Perl はモジュールを利用するために use 文を書く必要がある。

モジュールを足すごとに use 文を忘れずに書くのは意外と大変で「use漏れ」というコミットをこれまでたくさん積み重ねてきた。

この作業を自動化するために、過去には VSCode で自動でインポートする拡張機能を作った。

marketplace.visualstudio.com

しかし、以下の課題があった。

  1. 普段使っているのが IntelliJ (作った当時はVSCodeに移行を試みていた)でメンテナンスする気分になれない
  2. TypeScript で頑張ってパースしているが Perl の自由度に敗北している
  3. エクスポートされているシンボルを静的解析するのに限界がある
    このように動的に作られたりするとどうしようもない

そこで、それぞれの課題に対処する新たな自動インポートツールを作ろうと考えた。

  • 課題1 → 特定の IDE に依存しないように CPAN モジュールにする
  • 課題2 → PPI で解析することで Perl の自由度に対処する
  • 課題3 → 動的にモジュールをロードしてシンボルテーブルに登録されているシンボルを取得する

できたのがこちら。

github.com

インストールするには GitHub のレポジトリを指定する。CPAN にアップロードするのをサボっている(アカウント作成時に理由が雑すぎたのがrejectされてしまった…)

cpanm https://github.com/tjmtmmnk/pau.git

使い方はシンプル。 自動インポートしたいファイルを入力して Pau->auto_use に渡すと、使っていないモジュールは削除され、必要なモジュールは追加される。

use Pau;
my $source = "";

while (<STDIN>) {
    $source .= $_;
}
my $formatted = Pau->auto_use(
    # 自動インポートしたい内容
    source    => $source,
    # 利用しているライブラリのパス
    lib_paths => ['lib', 't/lib', 'cpan/lib/perl5'],
);
print(STDOUT $formatted);

パッケージ名を指定しない場合も対象になるのがポイント。 例えば、args my $foo => 'Int' としているときに lib_paths で指定したパス内に Smart::Argsモジュールがあれば、use Smart::Args qw(args) と追加される。

さらに、数千ものライブラリファイルがあってもキャッシュ無しで5秒程度、キャッシュありで1秒程度で実行できるのもポイント。 Parallel::ForkManagerで並列処理したり、ファイルに保存して差分更新することで高速化している。

一方でデメリットもある。

前述した通り、エクスポートされているシンボルを取得するために動的にモジュールをロードしている。 しかし、動的にモジュールをロードするのはセキュリティ上の懸念があると考えている。 ロードするモジュールの BEGINブロックの中で悪意のあるコードが含まれているとロード時に実行されてしまうからである。

そのため、利用しているモジュールが問題ないことを確認し、最小構成になっている仮想環境上で Pau を利用することを推奨する。

※追記

moose か mouse か迷う時とか無いのかな

あります。現在は複数のモジュールから同名のシンボルがエクスポートされている場合、インポートされるモジュールはランダムになっています。

これでは不便なので、インタラクティブにインポートするモジュールを選択するモードの追加や、このシンボルはこのモジュールからインポートするというマッピングを設定できるようにすることを検討しています。

※追記ここまで


以上の開発を元に WEB+DB PRESS Vol.133 の Perl Hackers Hub に「モジュールの自動インポートによる開発効率向上」というタイトルで寄稿させていただきました。

自動インポートってどういう仕組みで動いているの?という疑問にフォーカスして、Perl で一般的にインポート/エクスポートを実現するための Exporter モジュールの仕組みを知るところから始まり、具体例を通して、Perlで簡易的な自動インポートが実現できる構成になっています。

明日 2/24 から発売されます!是非チェックしてみてください!

gihyo.jp