前回の(1)はこちらから。
静的解析ライブラリを組み合わせて部分的にコード整形を行う
(2)では、PPI
とPerl::Tidy
を組み合わせて独自のコード整形ツールを作成します。
例として、開発の初期にはPerl::Tidy
が導入されていなかったプロジェクトについて考えます。あとからPerl::Tidy
を導入する場合、それまでに書かれたコードが一度に整形されます。そうすると、あとになって該当部分のコードが書かれた意図を履歴から調べる際に、コード整形のコミットログが出てきて、本来知りたかったコミットまでたどり着けません。
この問題を解決するために、新しく追加や編集した行に限定してコード整形を行うツールを作成します。
Git::Repositoryで変更した箇所を検知する
Gitを導入しているプロジェクトであれば、編集した差分はgit diff
で知ることができます。この情報を解析して、整形すべき場所を特定します。Perlではgit
コマンドのラッパとしてGit::Repository
モジュールがあるので、これを利用します。
Perlからgitコマンドを扱う
Git::Repositry
を使用して変更された行を特定するには、次のようにします。
このコードでは、ファイル名と行数がわかる最小限の表示しかしないオプションを渡してgit diff
を実行しています。また、Perlのソースコードに限って検出するため、.pm
および.pl
拡張子が付いたファイルのみを指定しています。
git diffの出力を解析する
前項のgit diff
の出力から、変更されたファイル名と行番号を抜き出します。以下はパースする正規表現の例です。
これらの情報を組み合わせると、ファイル内の変更された行番号を列挙できます。
PPIで変更箇所を抜き出す
差分が発生した行番号を特定できましたが、この行だけを整形しようとした場合、意図どおりの結果は得られません。その理由と、PPI
を用いた解決方法を紹介します。
変更行だけを抜き出しても、うまくいかない
差分が発生した行だけを整形すれば、期待する結果が得られるかどうかを考えてみます。
例として、ハッシュの中にキーを追加した差分を考えます。
Perl::Tidy
はハッシュの=>
の位置を、ハッシュ内の一番長いキーの長さに合わせてほかのキーも調整します。ですが、追加した行だけ整形した場合、そうはなりません。期待した結果を得るためには、my $hash = { ... };
全体を整形する必要があります。
自然な整形の単位を考える
Perlのプログラムは文で構成されます。文は式やブロックなどの組み合わせで成り立っています。前項で示したソースコードは、全体が1つの文になっています。このことから、変更された行を含む文を抜き出すことができれば、期待どおりの結果が得られます。
変更された行を含む文を抜き出すためには、次の条件を満たす必要があります。
- 字句が存在するソースコード上の行番号がわかる
- 任意の文が特定の字句を含むかを調べられる
- 任意の文をソースコードに変換できる
PPI
は上記すべての要素を満たします。ソースコードから生成したPDOM
には、行番号などファイル上の位置が保存されています。PDOM
は、プログラム上で意味がある構造を保ったまま木構造として表現されるため、あるPDOM
を含む親のPDOM
であるかを調べる機能があります。また、PDOM
にはもととなったソースコードに戻すメソッドが実装されています。
PPI
を使って特定の行を含む文を抽出したうえで、その部分だけにPerl::Tidy
を実行すると、目的を達成できます。
変更行を含む文を抜き出す
PPI
を使って、指定した行を含む文を抜き出すプログラムを書きます。
PPI::Document
のfind
メソッドを使って、対象の行だけに存在する文や字句をPDOM
として抽出しています。
次に、抜き出したPDOM
が所属する文まで親をたどっていきます。
scope
は、スコープを作る文だと真になるメソッドです。マップの中の文など、狭すぎる範囲を回避するために用いています。また、この段階で、文ではなく字句単体で存在する場合を取り除いています。この中には空白などが含まれますが、コード整形には不要だからです。
ある文が別の文に含まれている場合、1つに集約します。
これで、コード整形を行いたい文が得られました。
Perl::Tidyで抜き出した文を整形する
得られた文をPerlのソースコードに変換したうえで、erl::Tidy
で整形します。
次に、整形したコードをPDOM
に変換します。そして、もともとのPDOM
と置き換えます。
PDOM
の文が継承するPPI::Element
クラスには置換に使うreplace
メソッドが存在しますが、うまく機能しないことがあります。そこで、もともとのPDOM
からinsert_before
したうえで、remove
を行ってPDOM
の置換を実現しています。
整形した結果をファイルに書き戻す
PPI::Document
ではsave
メソッドでファイルに保存できます。
これで、変更した部分を含む文だけをコード整形するツールが完成しました。
まとめ
Perlの静的解析モジュールと、その使用例を示しました。また、静的解析モジュールを組み合わせて、現場のプロジェクトに合ったツールを作成する例を紹介しました。
静的解析は一見難しそうなテーマではありますが、リンタやコード整形ツールなど、実は日常的に使うツールで使われている技術です。また、本質は文字列の解析であり、Perlの得意分野でもあります。本稿を通して静的解析ツールの自作に興味を持っていただければ幸いです。
さて、次回の執筆者は佐藤健太さんで、テーマは「少しマニアックなPerlのテクニック」です。お楽しみに。
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
- 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
- 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT