前回の
静的解析ライブラリを組み合わせて部分的にコード整形を行う
(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

