RubyKaigi 2023 参加記 #12 - Find and Replace Code based on AST (Day3)
Photo by Daniel Lerman on Unsplash
Wantedly でエンジニアをしている江草です。
この記事では、Richard Huang(@flyerhzm) さんによる「Find and Replace Code based on AST」をご紹介させていただきます。AST ベースでコード検索や置換を行うツールである Synvert についての発表でした。
なぜ AST を使うのか
正確である
Ruby のコードをパースしているので、ただ正規表現等を使ってコードを検索するよりも false positive を減らしやすいです。例えば、文字列リテラルの中身は検索から除外する、というようなことができます。
# 検索に含めたい
puts 'hello'
puts('hello')
TEXT<<
# 検索に含めたくない
puts 'hello'
TEXT
複雑な置換ができる
例えば Hash の古い rocket syntax を置き換えるなど、AST を考慮できることで複雑な置き換えが行いやすくなるというメリットがあります。
Synvert
Synvert はユーザーフレンドリーに、AST を使ってコードの検索と置換を行うツールです。CLI ツールとしても使えるほか、VS Code 拡張やデスクトップアプリ、 Web 上で使える Playground があります。
発表者の方は、元々は Rails のアップグレード作業を楽にするためにこのツールを作成されたそうです。
アーキテクチャ
Synvert はいくつかの gem に分かれている構成になっています。
NodeQuery
https://github.com/xinminlabs/node-query-ruby
専用の NQL(Node Query Language) または、Node Rules と呼ばれる Hash object を使って、AST node を検索する gem です。
例えば Hoge
クラスを継承しているクラスを検索する NQL は次のようになります:
.class[parent_class.name=Hoge]
NodeMutation
https://github.com/xinminlabs/node-mutation-ruby
NodeMutation は NodeQuery で検索した AST node を置き換える API を提供している gem です。AST node に対して、コードを削除したりコードを挿入したりなどの操作ができます。
Synvert Core
NodeQuery や NodeMutation を使ってコード検索や置換を行う DSL を提供している gem です。
デモ
この記事の筆者が Synvert を実際に使ってみました。
Synvert は Web 上の Playground で気軽に試すことができます。
snippet の生成
https://playground.synvert.net/ruby/generate-snippet
画像のように playground にコード置換の置換後のコード例と置換後のコード例のペアをいくつか与えると、Synvert を使ってその置換を行う Ruby の snippet を生成することができます。
生成された snippet:
Synvert::Rewriter.new 'group', 'name' do
if_ruby '3.1.2'
within_files '**/*.rb' do
find_node '.send[receiver=nil][message=raise][arguments.size=1][arguments.0=.send[message=new][arguments.size=1]]' do
replace :arguments, with: '{{arguments.0.receiver}}, {{arguments.0.arguments.0}}'
end
end
end
コードの置換
https://playground.synvert.net/ruby/parse-snippet
次に、先ほど生成した snippet を用いてコードを置換してみます。今回の例では、2ステップで簡単にコードを置換することができました。
RuboCop との比較
RuboCop でも AST を見てコードの検索や置換ができるので、自分で Cop を実装すれば Synvert と同じようなことはできそうです。しかし、Synvert はエディタ等から通常のコード検索を行うような感覚で使えるので、単発のコード検索や置換を行う場面でとても有用そうです。
感想
Rails 等アップグレード作業やその他リファクタリングで、一定のルールに従ってコードを置換したり検索したりしたくなることは度々あります。Cop を実装するまでもないときは、正規表現である程度検索して、手動で false positive を除外したりすることがありました。Synvert を使えば気軽に AST を使った単発のコード置換や検索ができるので、このような作業がより速く正確にできそうです。来週から仕事でも Synvert を使ってみようと思いました。