ちょっと調べる機会があったので、まとめておく。
JavaScriptのAST
まず前提のおさらい。
JSにおけるASTは、言語自体のSpecにそういうのがあるわけではなく、コミュニティによって形成されたデファクトがあるというだけ。
それがESTreeって呼ばれてるやつ。
estree/estree: The ESTree Spec https://github.com/estree/estree
ただお察しの通り、これは有志の同意がなんとなく寄り集まって形になってるだけで、たとえばJSXみたいなものは含まれてなかったりする。
あくまでESTreeのスコープとしては、ES5とかES2022とかそういう本質的なものだけ。
Define scope of this project · Issue #219 · estree/estree https://github.com/estree/estree/issues/219
(まぁスコープも何も、そんな責任境界を求められてもな・・・感はあると思うけど)
そういうわけで、たとえばJSXなんかは独自に拡張された結果であり、言い出しっぺがその仕様を公開して、
それをまた有志のパーサー各位が、実装したりしてなかったりする。
ESTree 互換っぽい AST を出力する JavaScript のパーサーまとめ https://zenn.dev/sosukesuzuki/scraps/fa4d48f9098d66
という結果がそこにあるだけ。
ESTreeのリポジトリのIssueを見てると、そういう数多の葛藤が浮き沈みしてるのがわかって趣深い。
ESTreeにおけるコメント
で、本題。
そもそもコメントは、ESTreeのASTには含まれてない。
Standardize Comment Types · Issue #201 · estree/estree https://github.com/estree/estree/issues/201
その理由をざっくりまとめると・・・、
- 既にいろんなコンテキストで使われててエコシステムもあり、今さら感
- JSDoc, Flow, Prettier, etc…
- コメントはどこにでも書けるので、どう解釈したいかは、実装側がよしなにするほうがいい
って感じか。
実際のところ、
- 行コメント
- 複数行コメント
みたいなタイプを追加するだけなら簡単で、実際にそれをやってくれてるパーサーもある。
けど、結局はJSDocのそれみたく「何に対するコメント?」ってのが本当に知りたいものであるはず。
なのに肝心のその表現は、実装・コンテキストごとに異なるという。
// JSDocは後に
/** @type {number} */ const tsTyped = something();
// Flowは前に
const flowTyped /*: number*/ = something();
これをまとめるのはまあ・・難しいよな・・。
そのほか
- https://github.com/estree/estree/issues/41
- ESTreeがASTではなくCSTを提供できれば?というIssue
- もちろん結論は出てない
- https://github.com/eslint/doctrine
- ESLintがJSDocのサポートを内包していた頃のパーサー実装
- 今は非サポートで、
eslint-plugin-jsdoc
にその役割を譲った - コメント文字列を渡すと、それをASTにパースしてくれる
- https://eslint.org/docs/latest/extend/custom-rules#accessing-comments
- ESLintの
sourceCode
には、コメントを取るだけのAPIが残ってる getAllComments()
,getCommentsBefore()
,getCommentsAfter()
,getCommentsInside()
- ESLintの
- https://github.com/es-joy/jsdoccomment
eslint-plugin-jsdoc
が内部で依存してる実装- (
espree
extendsacorn
な)ASTノードを渡すと、対応するJSDocが取れる - パースもするけど、型情報は別のライブラリ?
- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser
ASTツーリング界隈は奥深い。