oxc / oxc_ast
This crate deserves a chapter of itself.
It is split in 3 parts:
- The structs that describe each node of the AST.
- A few implementations for those structs.
- Generated implementations under
oxc_ast/src/generatedgenerated by scripts intasks/ast_tools/src, based on decorators like#[ast(...)]
A few concepts/keywords to be known:
The Oxc AST differs slightly from the estree AST by removing ambiguous nodes and introducing distinct types. For example, instead of using a generic estree
Identifier, the Oxc AST provides specific types such asBindingIdentifier,IdentifierReference, andIdentifierName. This clear distinction greatly enhances the development experience by aligning more closely with the ECMAScript specification.
- AST: Abstract Syntax Tree
- estree: one of the standard for representing an AST for JavaScript programs
- There are other kinds of ASTs
Structs
The oxc_ast module exposes multiple structs such as Program, IdentifierName, ObjectProperty ...
They represent each node of the AST of a modern JavaScript program. Since oxc supports jsx and TypeScript by default, those nodes specific to their syntax are also present, like: JSXElement, JSXFragment, JSXExpression, TSEnumDeclaration, TSUnionType ...
https://github.com/oxc-project/oxc/tree/main/crates/oxc_ast/src/ast
Implementations
A few implementations specific to each of these structs are handcoded in crates/oxc_ast/src/ast_impl.
Generated implementations
You can read this comment on top of the structs
#![allow(unused)] fn main() { // NB: `#[span]`, `#[scope(...)]`,`#[visit(...)]` and `#[generate_derive(...)]` do NOT do anything to the code. // They are purely markers for codegen used in `tasks/ast_tools` and `crates/oxc_traverse/scripts`. See docs in those crates. // Read [`macro@oxc_ast_macros::ast`] for more information. }
Here is an example of structs where you can see some macros applied:
#![allow(unused)] fn main() { #[ast(visit)] #[derive(Debug, Clone)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] #[estree(type = "Identifier")] pub struct BindingIdentifier<'a> { pub span: Span, pub name: Atom<'a>, #[estree(skip)] #[clone_in(default)] pub symbol_id: Cell<Option<SymbolId>>, } }
Like said in the comment above, those macros don't really do anything by themselves (like regular macros would), they are markers for the internal tool tasks/ast_tools which:
- goes through the structs using the macros above, in different crates like
oxc_ast,oxc_regular_expression,oxc_span,oxc_syntax... - generate the implementations for those structs in a
./generatedfolder for those structs
The generators are located in tasks/ast_tools/src.
Why was it done like that, instead of regular use of macros ?
Because of performance and maintenance reasons. See the following links for more informations: