というものを見つけて、個人的に大変学びだったので。
名前こそ実験的って言ってるけど、追加されたのはもう2年も前だった。
Add experimental format-preserving mode to @babel/generator https://github.com/babel/babel/pull/16708
v7.26で追加されてたので、latest majorであるv8になっても、prefixが取れなかったということにはなる。
どういうオプションか
端的にいうと、
- まずコード文字列をASTにする
- そのASTをtransformしたりして
- ASTをまた文字列に戻す
この最後の段階で、できるだけ“元コード文字列に近い状態に復元するのをがんばる”よっていうオプション。
がんばらない場合
たとえば、
const x = [ 1, 2 ];
というコードをASTにすると、基本的には、
constで宣言された変数で、名前はx- 中身は配列で、数字の
1と2
という情報だけが残る。
なのでASTを文字列に戻すと、たいていこうなる。
const x = [1, 2];
スペースとか改行とか、意味のないものたちは消える。
コメントが絡むと自体はより複雑になる。
たとえば、
// c0
for /*c1*/ (;/*c2*/;) { /*c3*/ }
この手の実装では、コメントの所有をそれぞれASTのノードにattachする。
しかしこの場合は、
- c0:
ForStatementの前、leadingなコメントとわかる - c1:
ForStatementの内側ではあるな?.initよりは前かな? - c2:
.initも.testも.updateもないので、これもなし崩し的にForStatementの内側としか言えない? - c3:
.bodyのBlockStatementの内側だ
という分類になる。
で、問題は、c1もc2も、ForStatementの内側ってことはわかってるが、具体的な位置まではよくわからんってこと。
なのでこれを文字列に復元しようとしても、
// c0
for /*c1*/ /*c2*/ (;;) {/*c3*/}
みたいになりがち。 まぁ完全に元通りにはなってないけど、コメントは喪失してないのがポイント。
ASTをいじってコードに戻すユースケースでは、まあほぼ困ることはないと思う。 というか、コメント自体いらないってケースも多いと思う。
ただコメントが必要なケースもあり、regionコメントとかPUREコメントとか、JSDocとか、大事なものは残したい。ただこういうのは、だいたいわかりやすいleadingな位置にあることが多いので、そこまでがんばらなくてもなんとかなる。
ベストエフォートでがんばる
そこでexperimental_preserveFormatオプション。
ざっとAIで調べた限りやってることは、
- ASTノードに、トークンの所有まで紐づける
- トークンからline/colが引ける
- その都度で現在位置と比較して、過不足を補う
ということをやってるらしい。
なのでさっきのforでも、コメントを出力するときに(や;のトークン位置を見て、タイミングをはかってる。
ベストエフォートというのは、フォーマッターみたく綺麗に出力するものではなく、あくまで元ソースに近づけることを目的としてるのがポイントとのこと。
なのでたとえば、
const foo = 3;
const bar = 3;
この変数たちをリネームすると、
const x = 3;
const longer=3;
みたいになるってこと。元のトークン位置を基準にしてるだけなので。
そういうわけで、
parse()するときには、tokens: trueと元コード文字列が必須createParenthesizedExpressionsも推奨
generate()するときも、retainLines: trueが必須- これがexperimentalな所以とのこと
という条件が揃ってないと使えない。
すごいニッチな機能やなとは思うが、codemodみたいなシーンでは嬉しいのかも。
ちなみに
フォーマッターもがんばってる側、PrettierやOxfmtなど。
forを出力したあと、(までの間にコメントがないかソースを確認;を出力するたび、前後にコメントがないかソースを確認
みたいなことをやってる。 JS/TSXはあらゆる場所にコメントが書けてしまう言語なので、あらゆる場所でこれをやる必要がある。もちろんパフォーマンス影響は避けられない。
そのうえ、オプションによって;がなくなったり()が増えたり減ったり、挙げ句の果てには要素をソートしたいとか色々あってまじで(ry
だからフォーマッター実装としては、基本的にASTノードにくっついてる情報はアテにならない、というかアテにできない。 紐づくノードが存在しないコメントもあるし、どのみち元ソースを見ないと判断できないケースもままあるから。
コメントを書ける位置が構文として定まってる言語なら、ぜひattachしてほしいけど・・・。
そういうわけで、フォーマッターでもないのにcodegenでがんばってるのはすごい!と感銘を受けたのが、この記事を書いたきっかけ。