前回の
静的解析ライブラリを組み合わせて部分的にコード整形を行う
(2)PPI
とPerl::Tidy
を組み合わせて独自のコード整形ツールを作成します。
例として、Perl::Tidy
が導入されていなかったプロジェクトについて考えます。あとからPerl::Tidy
を導入する場合、
この問題を解決するために、
Git::Repositoryで変更した箇所を検知する
Gitを導入しているプロジェクトであれば、git diff
で知ることができます。この情報を解析して、git
コマンドのラッパとしてGit::Repository
モジュールがあるので、
Perlからgitコマンドを扱う
Git::Repositry
を使用して変更された行を特定するには、
use Git::Repository;
my $repo = Git::Repository->new(
work_tree => '/path/to/directory'
);
my @diff_outputs = $repo->run(
qw/diff --diff-filter -U0 *.pm *.pl/
);
このコードでは、git diff
を実行しています。また、.pm
および.pl
拡張子が付いたファイルのみを指定しています。
git diffの出力を解析する
前項のgit diff
の出力から、
my $filename_regexp = qr!\+\+\+\sb/(.*)$!x;
my $line_regexp = qr/
^@@\s-(?\d+)
(?:,(?\d+))?
\s\+(?\d+)
(?:,(?\d+))?\s@@
/x;
これらの情報を組み合わせると、
PPIで変更箇所を抜き出す
差分が発生した行番号を特定できましたが、PPI
を用いた解決方法を紹介します。
変更行だけを抜き出しても、うまくいかない
差分が発生した行だけを整形すれば、
例として、
my $hash = {
abc => "ABC",
def => "DEF",
ghij => "GHIJ", # この行を追加
};
Perl::Tidy
はハッシュの=>
の位置を、my $hash = { ... };
全体を整形する必要があります。
自然な整形の単位を考える
Perlのプログラムは文で構成されます。文は式やブロックなどの組み合わせで成り立っています。前項で示したソースコードは、
変更された行を含む文を抜き出すためには、
- 字句が存在するソースコード上の行番号がわかる
- 任意の文が特定の字句を含むかを調べられる
- 任意の文をソースコードに変換できる
PPI
は上記すべての要素を満たします。ソースコードから生成したPDOM
には、PDOM
は、PDOM
を含む親のPDOM
であるかを調べる機能があります。また、PDOM
にはもととなったソースコードに戻すメソッドが実装されています。
PPI
を使って特定の行を含む文を抽出したうえで、Perl::Tidy
を実行すると、
変更行を含む文を抜き出す
PPI
を使って、
my $doc = PPI::Document->new($filename);
my $nodes = $doc->find(
sub {
my (undef, $node) = @_;
return grep {
$node->line_number == $_
} @$line_numbers;
}
);
PPI::Document
のfind
メソッドを使って、PDOM
として抽出しています。
次に、PDOM
が所属する文まで親をたどっていきます。
for my $node (@$nodes) {
my $current_node = $node;
while (!$current_node->parent->scope) {
$current_node = $current_node->parent;
}
next if $current_node->isa("PPI::Token");
push @matches_statement, $current_node;
}
scope
は、
ある文が別の文に含まれている場合、
my @reduced_statement;
for my $stmt (@matches_statement) {
my $prev = scalar(@reduced_statement) > 0 ?
$reduced_statement[-1] : undef;
next if $prev && $prev->contains($stmt);
push @reduced_statement, $stmt;
}
これで、
Perl::Tidyで抜き出した文を整形する
得られた文をPerlのソースコードに変換したうえで、erl::Tidy
で整形します。
for my $statement (@$reduced_statement) {
my $content = $statement->content;
my $dest_content = "";
my $err = Perl::Tidy::perltidy(
argv => "",
source => source => \$content,
destination => source => \$dest_content,
);
$err and die $err;
}
次に、PDOM
に変換します。そして、PDOM
と置き換えます。
my $dest_doc = PPI::Document->new( source => \$dest_content);
my @dest_elements = $dest_doc->elements;
$stmt->insert_before(@dest_elements);
$stmt->remove;
PDOM
の文が継承するPPI::Element
クラスには置換に使うreplace
メソッドが存在しますが、PDOM
からinsert_
したうえで、remove
を行ってPDOM
の置換を実現しています。
整形した結果をファイルに書き戻す
PPI::Document
ではsave
メソッドでファイルに保存できます。
$doc->save($filename);
これで、
まとめ
Perlの静的解析モジュールと、
静的解析は一見難しそうなテーマではありますが、
さて、
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT