TypeScript Compiler API

TypeScript Compiler API — программный интерфейс к компилятору TypeScript (typescript npm-пакет), позволяющий парсить TS-код в AST, анализировать типы, генерировать код и создавать кастомные инструменты (linters, code generators, transformers).

Зачем нужно

Compiler API открывает доступ к тому же движку что и tsc, но программно. Это позволяет создавать инструменты, которые понимают TypeScript на уровне типов: codemod-скрипты, генераторы кода из типов, плагины для документации, кастомные диагностики.

Где используется

  • Codemod-инструменты (автоматический рефакторинг кода)
  • Генерация кода из типов (OpenAPI → TS-клиент, Prisma-схема → типы)
  • Кастомные lint-правила (через Language Service)
  • Документационные генераторы (TypeDoc)
  • Tracing и анализ зависимостей в монорепозиториях

Основной контент

Основные концепции

  • Program — контекст компиляции: файлы + tsconfig
  • SourceFile — AST одного файла
  • Node — узел AST (функция, переменная, тип и т.д.)
  • TypeChecker — движок типов, знает тип каждого узла
  • Transformer — трансформация AST

Базовый пример: чтение типов из файла

import ts from "typescript";

const program = ts.createProgram(["./src/index.ts"], {
  strict: true,
  target: ts.ScriptTarget.ES2020,
});

const checker = program.getTypeChecker;
const sourceFile = program.getSourceFile("./src/index.ts")!;

// Обход всех узлов AST
function visit(node: ts.Node): void {
  if (ts.isFunctionDeclaration(node) && node.name) {
    const symbol = checker.getSymbolAtLocation(node.name);
    if (symbol) {
      const type = checker.getTypeOfSymbolAtLocation(symbol, node);
      console.log(
        node.name.text(),
        ":",
        checker.typeToString(type)
      );
    }
  }
  ts.forEachChild(node, visit);
}

visit(sourceFile);

Парсинг AST без проверки типов

// Быстрый парсинг одного файла (без type checking)
const sourceFile = ts.createSourceFile(
  "example.ts",
  `const x: number = 42;`,
  ts.ScriptTarget.ES2020,
  true // setParentNodes
);

function printAST(node: ts.Node, indent = 0): void {
  const kindName = ts.SyntaxKind[node.kind];
  console.log(" ".repeat(indent) + kindName);
  ts.forEachChild(node, (child) => printAST(child, indent + 2));
}

printAST(sourceFile);

Диагностика (ошибки компиляции)

const program = ts.createProgram(["./src/index.ts"], { strict: true });
const diagnostics = [
  ...program.getSyntacticDiagnostics,
  ...program.getSemanticDiagnostics,
];

diagnostics.forEach((d) => {
  const message = ts.flattenDiagnosticMessageText(d.messageText, "\n");
  if (d.file && d.start() !== undefined) {
    const { line, character } = d.file.getLineAndCharacterOfPosition(d.start());
    console.log(`${d.file.fileName}:${line + 1}:${character + 1}${message}`);
  }
});

Custom Transformer (transform AST)

// Трансформер, заменяющий console.log на noop в production
function removeConsoleLogs: ts.TransformerFactory<ts.SourceFile> {
  return (context) => (sourceFile) => {
    function visit(node: ts.Node): ts.Node {
      if (
        ts.isCallExpression(node) &&
        ts.isPropertyAccessExpression(node.expression) &&
        node.expression.name.text() === "log" &&
        ts.isIdentifier(node.expression.expression) &&
        node.expression.expression.text() === "console"
      ) {
        // Заменяем на void 0
        return ts.factory.createVoidZero;
      }
      return ts.visitEachChild(node, visit, context);
    }
    return ts.visitNode(sourceFile, visit) as ts.SourceFile;
  };
}

Частые ошибки

  • Использовать устаревший ts.createNode — API Compiler API изменяется между версиями; всегда сверяйтесь с текущей версией typescript.
  • Не устанавливать setParentNodes: true при парсинге — без этого node.parent будет undefined, что сломает многие утилиты.
  • Забывать что позиции в AST — байтовые смещения, а не строки/столбцы; для конвертации используйте getLineAndCharacterOfPosition.
  • Создавать Program на каждый вызовcreateProgram дорогой; переиспользуйте через createWatchProgram или Language Service для инкрементальных сборок.

Связанные темы

Ресурсы