Паттерн visitor

Внутреннее устройство SWC основано на работе с абстрактным синтаксическим деревом (AST), где каждый узел представляет собой конструкцию языка: выражение, оператор, объявление, модуль. Для обхода и модификации этого дерева используется паттерн Visitor, позволяющий отделить структуру данных от операций над ней и масштабировать трансформации без изменения базовых типов AST.

Visitor в SWC — это центральный механизм, через который реализуются плагины, транспиляция TypeScript, JSX-преобразования, оптимизации и код-модификации. Его роль заключается в том, чтобы последовательно посещать узлы дерева и применять к ним пользовательские или системные правила трансформации.


Структура AST и необходимость обхода

AST в SWC представляет собой строго типизированную иерархию:

  • Program (корневой узел)
  • ModuleItem
  • Statement
  • Expression
  • Pattern
  • Literal
  • Identifier

Каждый узел содержит вложенные подузлы, формируя рекурсивную структуру. Например, выражение вызова функции включает:

  • callee (вызываемый объект)
  • arguments (список аргументов)
  • optional chaining / type annotations (в зависимости от контекста)

Без универсального механизма обхода пришлось бы вручную рекурсивно разбирать каждый тип узла, что делает систему нерасширяемой. Visitor решает эту проблему за счёт единообразного интерфейса обхода.


Суть паттерна Visitor в SWC

Visitor в SWC реализует принцип:

  • каждый узел вызывает соответствующий метод visitor’а
  • visitor определяет поведение для конкретного типа узла
  • обход дерева управляется централизованно

Базовая модель:

  • enter (вход в узел)
  • exit (выход из узла)
  • replace (замена узла)

Таким образом можно:

  • модифицировать узлы
  • удалять узлы
  • заменять целые поддеревья
  • собирать информацию без изменения AST

Visitor API в SWC (JavaScript/TypeScript слой)

В пользовательском API SWC visitor реализуется через объект с методами, соответствующими типам узлов.

Пример базовой структуры:

import { Visitor } from "@swc/core/Visitor";

const visitor: Visitor = {
  VariableDeclarator(n) {
    return n;
  },

  FunctionDeclaration(n) {
    return n;
  }
};

Каждый метод получает узел AST и возвращает:

  • тот же узел (без изменений)
  • изменённый узел
  • null (удаление узла, в зависимости от контекста)
  • другой AST-узел

Рекурсивный обход и автоматическая диспетчеризация

Ключевой особенностью SWC Visitor является автоматическая рекурсия. Разработчику не требуется вручную вызывать обход вложенных узлов.

Например:

const visitor: Visitor = {
  Identifier(n) {
    if (n.value === "debug") {
      return {
        ...n,
        value: "production"
      };
    }
    return n;
  }
};

Даже если Identifier находится глубоко внутри ExpressionStatement → CallExpression → MemberExpression, visitor будет вызван автоматически.


Модель трансформации: immutable AST

AST в SWC рассматривается как неизменяемая структура. Любое изменение создаёт новый узел, а не модифицирует существующий.

Это приводит к важным следствиям:

  • упрощение параллельных трансформаций
  • предсказуемость результата
  • отсутствие побочных эффектов
  • возможность кэширования

Пример замены узла:

CallEx * pression(n) {
  return {
    ...n,
    callee: {
      type: "Identifier",
      value: "patchedFunction",
      span: n.callee.span
    }
  };
}

Порядок обхода: pre-order и post-order

Visitor может работать в двух режимах:

Pre-order (вход сверху вниз)

Обработка узла до обработки детей:

  • позволяет модифицировать структуру до глубокого обхода
  • полезно для ранней оптимизации

Post-order (снизу вверх)

Обработка после посещения всех потомков:

  • позволяет агрегировать результаты
  • используется для анализа зависимостей

В SWC чаще используется комбинация, управляемая внутренним walker’ом.


Трансформации через visitMut

Помимо Visitor существует более низкоуровневый API visitMut, позволяющий изменять узлы “на месте” в рамках управляемой мутации.

Пример:

import { visitMut } from "@swc/core";

visitMut(ast, {
  Ex * pression(n) {
    if (n.type === "UnaryExpression") {
      return {
        ...n,
        operator: "!"
      };
    }
    return n;
  }
});

Особенность:

  • возвращаемые узлы могут заменять текущие
  • структура обхода сохраняется SWC runtime

Композиция visitor’ов

Одним из ключевых преимуществ паттерна является возможность композиции:

const visitorA: Visitor = {
  Identifier(n) {
    return { ...n, value: n.value.toUpperCase() };
  }
};

const visitorB: Visitor = {
  Identifier(n) {
    return { ...n, value: `_${n.value}` };
  }
};

Комбинирование:

import { chain } from "@swc/core";

const combined = chain(visitorA, visitorB);

Порядок применения влияет на итоговый AST, что важно при построении пайплайнов трансформации.


Практическая модель работы с узлами

Внутри SWC каждый узел имеет типизированную структуру. Например:

  • Identifier
  • FunctionDeclaration
  • BinaryExpression
  • CallExpression

Visitor получает строго типизированный объект:

CallEx * pression(n) {
  const args = n.arguments;

  return {
    ...n,
    arguments: args.map(arg => ({
      ...arg,
      expression: arg.expression
    }))
  };
}

Такая модель позволяет безопасно манипулировать деревом без ручной проверки структуры на каждом уровне.


Условная трансформация узлов

Visitor часто используется для условной модификации AST:

VariableDeclaration(n) {
  if (n.kind === "var") {
    return {
      ...n,
      kind: "let"
    };
  }
  return n;
}

Этот подход используется в:

  • строгой модернизации JavaScript-кода
  • миграциях синтаксиса
  • полифиллинге

Контекст выполнения и состояние

Хотя Visitor концептуально статeless, на практике можно хранить состояние:

let counter = 0;

const visitor: Visitor = {
  Identifier(n) {
    counter++;
    return n;
  }
};

Однако более безопасный подход — передача контекста через замыкание:

function createVisitor() {
  let renamed = 0;

  return {
    Identifier(n) {
      renamed++;
      return n;
    }
  };
}

Это позволяет:

  • изолировать трансформации
  • повторно использовать visitor
  • избегать глобального состояния

Работа с удалением узлов

Удаление узла в Visitor модели зависит от типа родительского контейнера.

Пример:

BlockStatement(n) {
  return {
    ...n,
    body: n.body.filter(stmt => {
      return stmt.type !== "EmptyStatement";
    })
  };
}

Удаление обычно реализуется через:

  • фильтрацию массивов узлов
  • замену на пустые конструкции
  • возврат null (в некоторых контекстах)

Ограничения Visitor-модели в SWC

Несмотря на универсальность, подход имеет особенности:

  • глубокие AST могут приводить к значительной нагрузке
  • порядок обхода критичен для зависимых трансформаций
  • сложные изменения требуют аккуратного управления копированием узлов
  • некорректная замена может нарушить целостность дерева

Visitor в экосистеме трансформаций SWC

Visitor используется в ключевых слоях SWC:

  • transpile (TypeScript → JavaScript)
  • JSX transformation
  • minification
  • macro-like transformations
  • custom plugins

Он выступает как фундаментальный слой, поверх которого строятся более высокоуровневые абстракции.


Связь Visitor с другими паттернами трансформации

В архитектуре SWC Visitor часто комбинируется с:

  • Builder (создание новых узлов AST)
  • Factory (генерация типизированных узлов)
  • Chain of Responsibility (пайплайны трансформаций)
  • Strategy (различные режимы обхода)

Visitor при этом остаётся центральной точкой интеграции всех операций над AST.


Типовая структура сложного visitor-плагина

export default function () {
  return {
    Program(program) {
      return program;
    },

    FunctionDeclaration(fn) {
      if (!fn.identifier) return fn;

      return {
        ...fn,
        identifier: {
          ...fn.identifier,
          value: fn.identifier.value + "_patched"
        }
      };
    },

    CallEx * pression(expr) {
      return expr;
    }
  };
}

Такая структура демонстрирует стандартный подход к расширению компилятора через декларативные AST-правила.


Механика обхода внутри SWC runtime

Внутренне SWC реализует Visitor через:

  • рекурсивные функции обхода узлов
  • таблицы соответствий типов узлов и обработчиков
  • оптимизированные match-ветвления
  • минимизацию аллокаций при проходе AST

Это позволяет добиться высокой производительности при трансформации даже больших кодовых баз.


Итоговая модель поведения Visitor в SWC

  • AST обходится рекурсивно
  • каждый узел обрабатывается соответствующим методом
  • изменения возвращаются через новые объекты
  • структура дерева сохраняется или перестраивается по правилам visitor’а
  • композиция visitor’ов формирует полный трансформационный pipeline