TypeScript Compiler API
TypeScript Compiler API — программный интерфейс к компилятору TypeScript (
typescriptnpm-пакет), позволяющий парсить 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 для инкрементальных сборок.