В системе компиляции и трансформации JavaScript/TypeScript SWC работа с
AST (Abstract Syntax Tree) построена вокруг двух фундаментальных
механизмов обхода и обработки узлов: Visit и
Fold. Эти два трейта формируют основу анализа и
преобразования синтаксического дерева, определяя различие между чтением
структуры и её изменением.
AST в SWC представляет собой иерархию строго типизированных узлов: выражения, инструкции, объявления, модули и т.д. Каждый узел содержит вложенные структуры, формируя дерево, которое необходимо либо исследовать, либо трансформировать.
Для работы с этим деревом используются два подхода:
Оба подхода реализованы через трейты Rust и активно используются в
экосистеме swc_ecma_visit.
Visit предназначен для анализа AST без его модификации. Он
реализует паттерн обходчика (visitor pattern), при котором каждый узел
посещается с возможностью выполнения побочных действий.
Ключевая характеристика:
AST остаётся неизменным, все операции — наблюдающие
Обход строится вокруг методов вида:
visit_* — для каждого типа узла
visit_children — рекурсивный обход потомков
Типичная структура посетителя:
struct Analyzer;
impl Visit for Analyzer {
fn visit_ident(&mut self, n: &Ident) {
// анализ идентификатора
println!("{}", n.sym);
}
}
Visit обычно включает две логические фазы:
В SWC часто используется комбинация Visit и
VisitMut, но классический Visit ориентирован
на чтение.
&T)
Visit позволяет контролировать глубину обхода через явные вызовы методов посещения дочерних узлов.
Пример:
impl Visit for Analyzer {
fn visit_function(&mut self, f: &Function) {
// обработка функции
self.visit_block(&f.body);
}
}
Контроль обхода важен для:
В отличие от Visit, Fold предназначен для преобразования
дерева. Он реализует функциональную модель: входной узел →
преобразованный узел.
Ключевая характеристика:
Каждый узел может быть заменён другим узлом
Fold использует владение (T вместо &T),
что позволяет создавать новые структуры AST.
struct Transformer;
impl Fold for Transformer {
fn fold_ident(&mut self, n: Ident) -> Ident {
Ident {
sym: n.sym.into(),
..n
}
}
}
Fold работает снизу вверх:
Это обеспечивает композиционность трансформаций.
Fold строго связан с моделью владения Rust:
Это исключает необходимость в мутабельных ссылках и снижает риск гонок данных.
&T (ссылки)
T (владение)
В реальных сценариях компиляции оба механизма используются последовательно:
Visit для анализа и подготовки информации
Fold для трансформации кода
Visit для проверки результата
Такая архитектура позволяет разделять ответственность между этапами компиляции.
Fold хорошо компонуется через цепочки:
Пример концептуальной композиции:
Fold1 -> Fold2 -> Fold3
Каждый слой работает независимо, изменяя AST последовательно.
Fold позволяет:
Пример трансформации:
a + b на add(a, b)
Fold автоматически рекурсивно применяет преобразования ко всем дочерним узлам, если не переопределено поведение.
Типичный шаблон:
impl Fold for Transformer {
fn fold_block_stmt(&mut self, b: BlockStmt) -> BlockStmt {
BlockStmt {
stmts: b.stmts.into_iter().map(|s| self.fold_stmt(s)).collect(),
..b
}
}
}
Порядок выполнения Fold критичен:
В сложных компиляционных пайплайнах порядок Fold фиксируется архитектурно.
Модуль swc_ecma_visit реализует:
visit_ и
fold_
Особое значение имеет автоматическая генерация обходчиков для всех типов узлов, что исключает необходимость ручного написания traversal logic.
Распространённые архитектурные схемы:
Такие схемы позволяют строить полноценные компиляторы и трансформеры поверх SWC без изменения ядра системы.