🌲

SWCで取得したASTから特定のリテラルを抜き出す

2023/07/02に公開

VSCodeのデコレーションAPIを使ってTailwindCSSのクラス名を日本語に置き換えるというネタをやりました。
https://zenn.dev/achamaro/articles/f5ac30e0231ba4

特定のテキストを置き換えるためには、置き換える対象となるテキストを見つけ出さなければなりません。
今回はその部分について書きたいと思います。

ASTを使う理由

ASTとは

ChatGPTに簡単な説明をもとめると以下の回答が得られました。

AST(抽象構文木)は、プログラムの構造を表現するデータ構造です。ソースコードを解析して、プログラムの意味的な要素や関係を抽象化して表現します。ASTは、コンパイラやインタプリタなどのツールで使用され、プログラムの解析や変換に利用されます。

少しだけ具体的にすると、変数宣言のノードがあり、その中に変数名やその値、リテラルなのかテンプレートなのかといった情報がツリー構造で表現されています。

AST Explorer というブラウザ上で動作するツールで実際のASTが見れるのでこちらを見ていただければ理解が早いと思います。
https://astexplorer.net/

より具体的に検索できる

特定のテキストの位置は正規表現を使えば取得できます。実際作ったものもクラス名のマッチングには正規表現を使用しています。

ただ、今回はクラス名を取得したいので単純なマッチングでは変数名やimportしたモジュール名、テキストノードの文言にもマッチしてしまう可能性が出てきてしまいます。

ASTを使い検索するノードを制限することで確度の高いクラス名候補の文字列からマッチングすることができます。

実装

今回は .tsx のASTを得るために SWC というツールを使用しました。
https://swc.rs/

ASTは前述の通りツリー構造になっているので、ひたすら下層まで辿っていき目当てのノードが見つかったら処理するということを繰り返します。

今回は変数定義の文字列リテラル、テンプレート構文に現れる文字リテラル、関数の引数に現れる文字列リテラル等を対象にマッチングしています。

https://github.com/achamaro/vscode-tailwindcss-i18n/blob/main/src/parser.ts

(ネタで作ったものなので一部のクラスのみマッチングします。)

SWCでの注意点として、繰り返しparseしているとノードの位置を表すspanの値がズレていきます。
これは直前に空文字列をパースして得られるspanの値をオフセット値として実際のテキストをparseして得られるspanの値から引いてあげれば正しい値が求められます。
https://github.com/swc-project/swc/issues/1366

さいごに

ASTって聞くとなんだか難しそうな気がしてしまいますが、正体はただのツリー構造のデータです。
なんとなくでも案外さわれるので一段上のプログラマーになったような気分が手軽に味わえます。

ちなみに、話題のNextJSではSWCのプラグインをRustで書くこともできます。
https://github.com/achamaro/iconify-icon/blob/main/packages/swc-plugin-icon/src/lib.rs
(Viteプラグインで実装していたアイコン用プラグインをNextJS用に作っていたけどRSCとの相性が悪すぎて断念したリポジトリ)

Discussion