Perl Hackers Hub
第63回 PPIとPerl::Tidyを組み合わせて作るコード整形ツール(2)
前回の
静的解析ライブラリを組み合わせて部分的にコード整形を行う
(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.129
2022年6月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-12890-6
- 特集1
Reactの深層
最新バージョンから読み解く! 変わる常識と変わらない思想 - 特集2
できるところから無理なく導入!
小さく始めるデザインシステム - 特集3
最新レコメンドエンジン総実装
協調フィルタリングから深層学習まで
Perl, プログラミング言語, PPI, Perl::Tidy, 静的解析
バックナンバー
Perl Hackers Hub
- 第72回 初学者に伝えたい Perl学習の勘所 ―勉強会を10年運営して培ったノウハウ(2)
- 第72回 初学者に伝えたい Perl学習の勘所 ―勉強会を10年運営して培ったノウハウ(1)
- 第71回 ISUCONの実装から最近のPerlを学ぶ ―わかりやすく変更しやすいコードを実現する考え方と方法(2)
- 第71回 ISUCONの実装から最近のPerlを学ぶ ―わかりやすく変更しやすいコードを実現する考え方と方法(1)
- 第70回 Raisin入門 ―Rest APIマイクロフレームワークを使ってみよう!(2)
- 第70回 Raisin入門 ―Rest APIマイクロフレームワークを使ってみよう!(1)
- 第69回 表形式データを操るUNIXシェル型Perl製コマンド群 ―ビッグデータ時代の汎用的なデータ整備と分析のために(2)
- 第69回 表形式データを操るUNIXシェル型Perl製コマンド群 ―ビッグデータ時代の汎用的なデータ整備と分析のために(1)
- 第68回 他言語のライブラリをPerlに移植する(2)
- 第68回 他言語のライブラリをPerlに移植する(1)