SWC's AST Structure and Node Types: A Complete Guide for Tooling
SWC's AST is a strongly-typed, enum-based tree defined in the swc_ecma_ast crate where every syntax element—from statements to expressions—exists as a distinct enum variant, enabling compile-time safe traversal and transformation via visitor patterns.
SWC's Abstract Syntax Tree (AST) serves as the foundation for the compiler's JavaScript and TypeScript transformations. Defined primarily in the swc_ecma_ast crate within the swc-project/swc repository, this tree uses Rust enums to represent every syntactic construct, providing tooling authors with zero-cost abstractions and exhaustive pattern matching against the SWC's AST structure.
Core Node Types in SWC's AST Structure
Program and ModuleItem: The Root Hierarchy
The AST structure begins with the Program enum defined in crates/swc_ecma_ast/src/module.rs at line 11. This top-level container holds either a Script or a Module, representing the two execution contexts in JavaScript. Immediately below, the ModuleItem enum (line 108) represents items that can appear at the top level of a module, with variants for Stmt (statements) or ModuleDecl (module declarations like imports and exports).
Statements and Declarations
The Stmt enum in crates/swc_ecma_ast/src/stmt.rs (line 39) defines over 40 variants covering all possible statements, including Block, Empty, Debugger, and Expr. For declarations, the Decl enum in crates/swc_ecma_ast/src/decl.rs (line 18) provides variants such as FnDecl, ClassDecl, and VarDecl, alongside TypeScript-specific declarations.
Expressions and Patterns
Expression nodes are defined in the Expr enum located in crates/swc_ecma_ast/src/expr.rs (line 33), containing more than 80 variants for literals, binary operations, function calls, and JSX elements. For destructuring and binding patterns, the Pat enum in crates/swc_ecma_ast/src/pat.rs (line 16) includes ObjectPat, ArrayPat, and AssignPat.
TypeScript and JSX Extensions
SWC's AST structure provides first-class support for TypeScript through the TsType enum and auxiliary types in crates/swc_ecma_ast/src/typescript.rs (line 312), covering type literals, interfaces, and unions. JSX support is implemented in crates/swc_ecma_ast/src/jsx.rs (line 18) with JSXElement, JSXAttr, and JSXExpr enums.
Classes, Properties, and Operators
Class definitions use the Class struct and ClassMember enum in crates/swc_ecma_ast/src/class.rs (line 73). Object properties are represented by the Prop enum in crates/swc_ecma_ast/src/prop.rs (line 18), which includes KeyValueProp, GetterProp, SetterProp, and MethodProp. Operator tokens are typed as enums in crates/swc_ecma_ast/src/operators.rs (line 18), including BinaryOp, AssignOp, UnaryOp, and UpdateOp.
How SWC Builds the AST Structure
The AstNode Macro and Span Generation
Every struct in the AST structure is annotated with #[ast_node(...)], a procedural macro that automatically generates the Span field and implements Clone, PartialEq, and the Visit traits required by the visitor infrastructure. This macro ensures that every node carries source location information while reducing boilerplate code.
Tagged Enums for Serialization
All enums in the AST structure carry #[tag("NodeType")] attributes, which SWC uses for internal AST serialization when emitting JSON for debugging. For example, the Stmt enum definition in crates/swc_ecma_ast/src/stmt.rs uses these tags to map Rust variants to standard JavaScript node types:
#[ast_node(no_clone)]
#[derive(Eq, Hash, Is, EqIgnoreSpan)]
pub enum Stmt {
#[tag("BlockStatement")]
Block(BlockStmt),
#[tag("EmptyStatement")]
Empty(EmptyStmt),
#[tag("DebuggerStatement")]
Debugger(DebuggerStmt),
#[tag("ExpressionStatement")]
Expr(ExprStmt),
}
This design provides zero-cost abstraction—matching on enums compiles to simple integer switches avoiding dynamic dispatch—while offering static guarantees that all variants are handled at compile time.
Working with SWC's AST in Rust Tooling
Parsing JavaScript into AST Nodes
The swc_ecma_parser crate constructs the AST structure from source code. The Parser::new function accepts a Syntax configuration and StringInput to produce a Module or Script root node:
use swc_common::{SourceMap, FileName, sync::Lrc};
use swc_ecma_parser::{Parser, StringInput, Syntax, EsConfig};
fn parse_module(src: &str) -> swc_ecma_ast::Module {
let cm: Lrc<SourceMap> = Default::default();
let fm = cm.new_source_file(FileName::Custom("input.js".into()), src.into());
let mut parser = Parser::new(
Syntax::Es(EsConfig {
jsx: true,
..Default::default()
}),
StringInput::from(&*fm),
None,
);
parser
.parse_module()
.expect("failed to parse module")
}
Traversing with the Visit Trait
Tooling can traverse the AST structure using the Visit trait from swc_ecma_visit. The visit_with method dispatches to type-specific methods such as visit_ident or visit_fn_decl:
use swc_ecma_ast::*;
use swc_ecma_visit::{Visit, VisitWith};
struct IdentifierCollector {
pub ids: Vec<String>,
}
impl Visit for IdentifierCollector {
fn visit_ident(&mut self, ident: &Ident, _: &dyn Node) {
self.ids.push(ident.sym.to_string());
}
}
fn collect_idents(module: &Module) -> Vec<String> {
let mut visitor = IdentifierCollector { ids: vec![] };
module.visit_with(&mut visitor);
visitor.ids
}
Transforming with the Fold Trait
Mutable transformations use the Fold trait, which creates a new AST structure with modified nodes. The fold_with method applies a folder across the entire tree:
use swc_ecma_ast::*;
use swc_ecma_visit::{Fold, FoldWith};
struct VarToLet;
impl Fold for VarToLet {
fn fold_var_decl(&mut self, mut decl: VarDecl) -> VarDecl {
decl.kind = VarDeclKind::Let;
decl
}
}
fn transform(module: Module) -> Module {
module.fold_with(&mut VarToLet)
}
Serializing to JSON for Debugging
When the serde feature is enabled, the entire AST structure supports serialization. This is useful for inspecting the tree during development:
use swc_ecma_ast::*;
use serde_json::to_string_pretty;
fn ast_to_json<T: serde::Serialize>(node: &T) -> String {
to_string_pretty(node).expect("serialization failed")
}
// Example usage:
// let module = parse_module("function foo(){ return 42; }");
// println!("{}", ast_to_json(&module));
Essential Source Files for AST Manipulation
The following files in the swc-project/swc repository constitute the public surface for SWC's AST structure:
crates/swc_ecma_ast/src/lib.rs— Public re-exports and macro glue for the entire ASTcrates/swc_ecma_ast/src/module.rs—Program,Module,Script, andModuleItemdefinitionscrates/swc_ecma_ast/src/stmt.rs—Stmtenum and related statement structscrates/swc_ecma_ast/src/expr.rs—Exprenum and expression helperscrates/swc_ecma_ast/src/decl.rs—Declenum for functions, classes, and variablescrates/swc_ecma_ast/src/pat.rs— Pattern types for destructuringcrates/swc_ecma_ast/src/prop.rs— Object property variantscrates/swc_ecma_ast/src/jsx.rs— JSX-specific node definitionscrates/swc_ecma_ast/src/typescript.rs— Full TypeScript type system nodescrates/swc_ecma_ast/src/lit.rs—Litenum for primitives (strings, numbers, booleans, regex, bigint)crates/swc_ecma_ast/src/class.rs— Class declarations and member definitionscrates/swc_ecma_ast/src/operators.rs— Binary, unary, and assignment operatorscrates/swc_ecma_parser/src/lib.rs— Parser entry point that produces the ASTcrates/swc_visit/src/lib.rs— Visitor (Visit) and folder (Fold) trait definitions
Summary
- SWC's AST structure uses Rust enums to represent all JavaScript and TypeScript syntax, providing compile-time safety and zero-cost pattern matching.
- The
ProgramandModuleItemenums serve as the root containers, whileStmt,Expr,Decl, andPatenums categorize specific syntax elements. - The
#[ast_node]macro generates common traits and span information, while#[tag(...)]attributes enable JSON serialization. - Tooling interacts with the AST through the
Visittrait (for reading) andFoldtrait (for transforming), defined in theswc_visitcrate. - All AST types support
serdeserialization when the feature is enabled, facilitating debugging and interoperability.
Frequently Asked Questions
What makes SWC's AST structure different from Babel's?
SWC's AST uses Rust's enum system to enforce type safety at compile time, whereas Babel uses a loose object-based tree. This means SWC visitors must handle all enum variants, preventing runtime errors, and pattern matching optimizes to integer switches rather than property lookups.
How does pattern matching work on SWC AST nodes?
Pattern matching uses Rust's match expressions on the enum variants. For example, matching on a Stmt can destructure it into specific variants like Stmt::Block(block) or Stmt::Expr(expr), allowing exhaustive handling of all 40+ statement types defined in stmt.rs.
Can I use SWC's AST for TypeScript type-checking?
While the AST structure includes comprehensive TypeScript nodes in typescript.rs (line 312), SWC itself is a transpiler, not a type checker. The AST provides the syntax tree for type annotations, but semantic type checking requires additional symbol resolution logic beyond the scope of the core AST.
What is the performance cost of SWC's visitor pattern?
The visitor pattern incurs zero runtime overhead compared to manual traversal. The Visit and Fold traits use static dispatch, and enum matching compiles to direct jumps. According to the source implementation in swc_visit, this design avoids the virtual table lookups common in object-oriented AST implementations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →