長らく触ってはいつつも、なんとなくしか把握してなかったので、もう少しちゃんと見ておこうと思い。
その前にESTree
JavaScriptのASTといえば、やっぱデファクトのESTreeですよねということで先に。
estree/estree: The ESTree Spec https://github.com/estree/estree
ただここにある.md
は、ES世代ごとにファイルが分かれており、俯瞰するには不便・・・。
というわけで、ざっと眺めるにはTSの型で。
DefinitelyTyped/types/estree/index.d.ts at master · DefinitelyTyped/DefinitelyTyped https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/estree/index.d.ts
実際には利用するパーサー実装による(パーサー側で型定義持ってたりとか)けど、それはさておきほんと最小限って感じね。
その前にBabel
BabelのASTは、ESTreeのASTとちょっと違う。って書いてある。
汎用的なノードがより具体的なものになってるとのこと。
・ Literal token is replaced with StringLiteral, NumericLiteral, BigIntLiteral, BooleanLiteral, NullLiteral, RegExpLiteral ・ Property token is replaced with ObjectProperty and ObjectMethod ・ MethodDefinition is replaced with ClassMethod and ClassPrivateMethod ・ PropertyDefinition is replaced with ClassProperty and ClassPrivateProperty ・ PrivateIdentifier is replaced with PrivateName ・ Program and BlockStatement contain additional directives field with Directive and DirectiveLiteral ・ ClassMethod, ClassPrivateMethod, ObjectProperty, and ObjectMethod value property’s properties in FunctionExpression is coerced/brought into the main method node. ・ ChainExpression is replaced with OptionalMemberExpression and OptionalCallExpression ・ ImportExpression is replaced with a CallExpression whose callee is an Import node. This change will be reversed in Babel 8. ・ ExportAllDeclaration with exported field is replaced with an ExportNamedDeclaration containing an ExportNamespaceSpecifier node. https://babeljs.io/docs/babel-parser#output
そのほか、()
のためのParenthesizedExpression
みたいな、ESTreeには存在しないノードもある。(当然、プラグインやらいろいろ有効にすると、もっとたくさんBabel独自のASTノードが生える)
babel/packages/babel-parser/ast/spec.md at main · babel/babel https://github.com/babel/babel/blob/main/packages/babel-parser/ast/spec.md
いい感じにまとまってるSpecがあるのは見やすくて良い。
OXCのASTは2種類ある
さて本題。
OXCのASTを利用すると言った場合、JSから使うのか、Rustから使うのかで、そのASTは少し違う。
元々はRustで書かれたパーサーなので、そのASTについてもRustで定義されてるけど、JSから使う場合は、ESTree互換(を目指してる)形式になる。
たとえば真偽値は、Rust版ではBabel同様にBooleanLiteral
というstruct
になってるけど、JS版ではESTree同様にLiteral
になるって感じ。(#[estree()]
がその指定)
/// Boolean literal
///
/// <https://tc39.es/ecma262/#prod-BooleanLiteral>
#[ast(visit)]
#[derive(Debug, Clone)]
#[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ESTree)]
#[estree(type = "Literal", via = crate::serialize::ESTreeLiteral, add_ts = "raw: string | null")]
pub struct BooleanLiteral {
/// Node location in source code
pub span: Span,
/// The boolean value itself
pub value: bool,
}
OXCのAST(Rust版)
今度こそ本題。
RustのコードにおけるASTの定義はここにある。
https://github.com/oxc-project/oxc/blob/main/crates/oxc_ast/src/ast/js.rs https://github.com/oxc-project/oxc/blob/main/crates/oxc_ast/src/ast/literal.rs
で、Rust版のASTを触ってて最初に引っかかるのは、struct
とenum
の区別かなと。
たとえばBabelでは、Program
ノードのbody
の型はArray<Statement>
になってて、このStatement
はただのUnion型である。
interface Program extends BaseNode {
type: "Program";
body: Array<Statement>;
directives: Array<Directive>;
sourceType: "script" | "module";
interpreter?: InterpreterDirective | null;
}
// ...
type Statement = BlockStatement | BreakStatement | ContinueStatement | DebuggerStatement // ...
つまり、必ずなんらかの具体的なノードの実体(という表現が正しいかはわからんけど)がそこにあるってこと。
一方、OXCのRust版では、Statement
はenum
で定義されてて、さらには実装もちょっと持ってたりする。
https://github.com/oxc-project/oxc/blob/405b73d8e72b92fb329d777dd157c78decf4f5c2/crates/oxc_ast/src/ast/js.rs#L983 https://github.com/oxc-project/oxc/blob/405b73d8e72b92fb329d777dd157c78decf4f5c2/crates/oxc_ast/src/ast_impl/js.rs#L762
なので、この1クッションがあるってことを最初に理解しておかないと、延々とRustコンパイラに怒られることになる・・・。(自戒)
ASTノード一覧
そんなenum
を除いたASTノードの顔ぶれはこのような感じ。並びは定義されてる順のまま、なんとなく行間で整理してみた状態。
BooleanLiteral
NullLiteral
NumericLiteral
StringLiteral
BigIntLiteral
RegExpLiteral
Program
IdentifierName
IdentifierReference
BindingIdentifier
LabelIdentifier
ThisExpression
ArrayExpression
Elision
ObjectExpression
ObjectProperty
TemplateLiteral
TaggedTemplateExpression
TemplateElement
TemplateElementValue
ComputedMemberExpression
StaticMemberExpression
PrivateFieldExpression
CallExpression
NewExpression
MetaProperty
SpreadElement
UpdateExpression
UnaryExpression
BinaryExpression
PrivateInExpression
LogicalExpression
ConditionalExpression
AssignmentExpression
ArrayAssignmentTarget
ObjectAssignmentTarget
AssignmentTargetRest
AssignmentTargetWithDefault
AssignmentTargetPropertyIdentifier
AssignmentTargetPropertyProperty
SequenceExpression
Super
AwaitExpression
ChainExpression
ParenthesizedExpression
Directive
Hashbang
BlockStatement
VariableDeclaration
VariableDeclarator
EmptyStatement
ExpressionStatement
IfStatement
DoWhileStatement
WhileStatement
ForStatement
ForInStatement
ForOfStatement
ContinueStatement
BreakStatement
ReturnStatement
WithStatement
SwitchStatement
SwitchCase
LabeledStatement
ThrowStatement
TryStatement
CatchClause
CatchParameter
DebuggerStatement
BindingPattern
AssignmentPattern
ObjectPattern
BindingProperty
ArrayPattern
BindingRestElement
Function
FormalParameters
FormalParameter
FunctionBody
ArrowFunctionExpression
YieldExpression
Class
ClassBody
MethodDefinition
PropertyDefinition
PrivateIdentifier
StaticBlock
AccessorProperty
ImportExpression
ImportDeclaration
ImportSpecifier
ImportDefaultSpecifier
ImportNamespaceSpecifier
WithClause
ImportAttribute
ExportNamedDeclaration
ExportDefaultDeclaration
ExportAllDeclaration
ExportSpecifier
BabelとESTreeを足して2で割ったって印象かな?Babelよりも具体的か。
Babelではオプションが必要だったParenthesizedExpression
も、デフォルトで出力されるようになってる。
あとはこういう記録も見つけた。
Grammar | The JavaScript Oxidation Compiler https://oxc.rs/docs/learn/ecmascript/grammar.html#assignmentpattern-vs-bindingpattern
大変そうだ・・・。