В SWC синтаксическое дерево (AST) представляет исходный JavaScript/TypeScript код в виде иерархической структуры узлов, где каждый узел соответствует синтаксической конструкции языка: выражению, оператору, объявлению, модулю или служебной конструкции. Все трансформации и анализ выполняются через обход этого дерева.
AST в SWC построен вокруг набора строго типизированных структур,
описанных в @swc/core и внутренних модулях
трансформации. Ключевая особенность — отсутствие «магии»: обход дерева
вручную предполагает прямую работу с узлами и их полями без
автоматических высокоуровневых абстракций.
Каждый узел AST в SWC содержит:
type — строковый идентификатор вида узла
body, expression,
left, right)
Пример типичных узлов:
Program — корень модуля
ModuleItem — элемент верхнего уровня (импорт, экспорт,
statement)
Stmt — оператор
Expr — выражение
Ident — идентификатор
CallExpr — вызов функции
Ручной обход AST в SWC сводится к рекурсивному спуску по всем возможным полям узла, содержащим дочерние узлы. В отличие от visitor-абстракций, здесь логика обхода реализуется явно.
Базовый шаблон обхода:
function traverse(node) {
if (!node) return;
switch (node.type) {
case "Program":
node.body.forEach(traverse);
break;
case "ExpressionStatement":
traverse(node.expression);
break;
case "CallExpression":
traverse(node.callee);
node.arguments.forEach(arg => traverse(arg.expression));
break;
case "Identifier":
break;
default:
for (const key in node) {
const value = node[key];
if (Array.isArray(value)) {
value.forEach(traverse);
} else if (value && typeof value === "object") {
traverse(value);
}
}
}
}
Такой подход обеспечивает полный контроль над структурой обхода, но требует точного знания формы AST.
Корневой узел содержит список инструкций:
Program {
body: ModuleItem[]
}
Обход начинается именно отсюда, и каждый элемент body может
быть импортом, экспортом или выражением.
Выражения требуют особого внимания, поскольку они рекурсивны и часто вложены:
BinaryExpression — содержит left и
right
CallExpression — содержит callee и
arguments
MemberExpression — доступ к свойству объекта
Пример обработки:
function visitExpr(expr) {
switch (expr.type) {
case "BinaryExpression":
traverse(expr.left);
traverse(expr.right);
break;
case "MemberExpression":
traverse(expr.object);
traverse(expr.property);
break;
case "CallExpression":
traverse(expr.callee);
expr.arguments.forEach(a => traverse(a.expression));
break;
}
}
Узлы Identifier являются листьями дерева. Они не содержат
дочерних элементов, но часто используются как точки модификации
(например, переименование переменных).
case "Identifier":
// анализ имени
break;
Для больших AST удобнее использовать универсальный обходчик с динамическим анализом полей:
function traverse(node) {
if (!node || typeof node !== "object") return;
for (const key of Object.keys(node)) {
const value = node[key];
if (Array.isArray(value)) {
for (const item of value) {
traverse(item);
}
} else if (value && typeof value === "object") {
traverse(value);
}
}
}
Этот метод не требует знания структуры узлов, но может заходить в служебные поля, поэтому иногда требует фильтрации.
В SWC каждый узел имеет поле type, которое позволяет точно
определить его структуру. При ручном обходе это ключевой механизм
управления логикой.
Пример выборочного анализа:
function traverse(node) {
if (!node) return;
if (node.type === "VariableDeclarator") {
traverse(node.id);
traverse(node.init);
return;
}
if (node.type === "FunctionDeclaration") {
traverse(node.params);
traverse(node.body);
return;
}
for (const key in node) {
const value = node[key];
if (value && typeof value === "object") traverse(value);
}
}
Ручной обход часто используется не только для анализа, но и для сбора информации.
const identifiers = [];
function traverse(node) {
if (!node) return;
if (node.type === "Identifier") {
identifiers.push(node.value);
}
for (const key in node) {
const value = node[key];
if (Array.isArray(value)) {
value.forEach(traverse);
} else if (value && typeof value === "object") {
traverse(value);
}
}
}
Ручной обход позволяет не только читать дерево, но и модифицировать его.
function traverse(node) {
if (!node) return;
if (node.type === "Identifier" && node.value === "oldName") {
node.value = "newName";
}
for (const key in node) {
const value = node[key];
if (Array.isArray(value)) {
value.forEach(traverse);
} else if (value && typeof value === "object") {
traverse(value);
}
}
}
AST SWC содержит множество уровней вложенности, особенно в:
Пример сложной структуры:
CallExpression
├── MemberExpression
│ ├── Identifier
│ └── Identifier
└── Arguments[]
При ручном обходе важно не пропускать поля arguments,
body, consequent, alternate.
Некоторые расширенные AST-структуры могут содержать ссылки на родительские узлы или служебные поля, которые создают риск бесконечной рекурсии. При универсальном обходе требуется фильтрация:
const SKIP_KEYS = new Set(["parent", "comments", "loc", "range"]);
function traverse(node) {
if (!node || typeof node !== "object") return;
for (const key in node) {
if (SKIP_KEYS.has(key)) continue;
const value = node[key];
if (Array.isArray(value)) {
value.forEach(traverse);
} else if (value && typeof value === "object") {
traverse(value);
}
}
}
Ручной DFS-обход AST имеет линейную сложность относительно числа узлов, однако реальные затраты зависят от:
Оптимизация достигается за счёт:
function traverse(node) {
if (!node) return;
if (node.type === "ImportDeclaration") {
console.log(node.source.value);
}
for (const key in node) {
const value = node[key];
if (value && typeof value === "object") traverse(value);
}
}
function traverse(node) {
if (!node) return;
if (node.type === "CallExpression") {
if (node.callee.type === "Identifier") {
console.log(node.callee.value);
}
}
for (const key in node) {
const value = node[key];
if (value && typeof value === "object") traverse(value);
}
}
Ручная реализация обхода требует:
Отсутствие встроенной типовой проверки делает ошибки структуры особенно критичными, так как пропущенные поля приводят к неполному анализу дерева.
Хотя SWC предоставляет visitor API через @swc/core, ручной обход:
Visitor-структуры абстрагируют обход, но ограничивают гибкость, тогда как ручной подход полностью раскрывает структуру AST и поведение узлов.