ts-morph для TypeScript: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в ts-morph: что это и зачем нужно

ts-morph — это мощная библиотека для программной работы с абстрактным синтаксическим деревом (AST) TypeScript. Она предоставляет удобный и интуитивно понятный API для анализа, модификации и генерации TypeScript-кода. В отличие от прямого использования компилятора TypeScript (TypeScript Compiler API), ts-morph предлагает более высокоуровневый интерфейс, который значительно упрощает многие рутинные задачи.

Зачем нужна эта библиотека? Представьте, что вам нужно:

  • Рефакторинг кода: Автоматически переименовать все вхождения функции, изменить сигнатуру метода или переместить класс в другой файл.
  • Статический анализ: Найти все неиспользуемые переменные, проверить соблюдение корпоративных правил кодирования или собрать метаданные о структуре проекта.
  • Генерация кода: Создать типы на основе JSON-схемы, сгенерировать CRUD-операции для базы данных или автоматически добавить документацию.
  • Создание инструментов разработчика: Написать плагин для линтера, кастомный генератор документации или утилиту для миграции кода.

ts-morph абстрагирует сложность TypeScript Compiler API, предоставляя объектно-ориентированный подход. Вы работаете с проектами, файлами, классами, функциями и другими элементами кода как с обычными объектами, а не с низкоуровневыми узлами AST.

Установка ts-morph

Установка библиотеки производится через npm или yarn. Она доступна как пакет для Node.js.

npm install ts-morph

или, если вы используете yarn:

yarn add ts-morph

После установки вы можете импортировать библиотеку в свой проект:

import { Project } from 'ts-morph';

Для работы ts-morph требуется TypeScript (он указан как peer dependency). Убедитесь, что TypeScript установлен в вашем проекте:

npm install typescript

Быстрый старт с примером кода

Давайте напишем простой скрипт, который создает новый файл TypeScript, добавляет в него функцию и затем выводит сгенерированный код.

import { Project, SyntaxKind } from 'ts-morph';

// Создаем новый проект
const project = new Project({
    useInMemoryFileSystem: true, // Работаем в памяти, без сохранения на диск
});

// Создаем новый файл
const sourceFile = project.createSourceFile('example.ts', '');

// Добавляем функцию
const functionDeclaration = sourceFile.addFunction({
    name: 'greet',
    parameters: [{ name: 'name', type: 'string' }],
    returnType: 'string',
    statements: [
        `return \`Hello, \${name}!\`;`,
    ],
});

// Добавляем JSDoc комментарий
functionDeclaration.addJsDoc({
    description: 'Приветствует пользователя',
    tags: [{ tagName: 'param', text: 'name - Имя пользователя' }],
});

// Выводим сгенерированный код
console.log(sourceFile.getText());

Результат выполнения скрипта:

/**
 * Приветствует пользователя
 * @param name - Имя пользователя
 */
function greet(name: string): string {
    return `Hello, ${name}!`;
}

Как видите, мы программно создали полноценную функцию с JSDoc-документацией. Это лишь малая часть возможностей ts-morph.

Основные методы и функции ts-morph

Рассмотрим 10 ключевых методов библиотеки с подробным описанием и примерами.

1. Project.createSourceFile()

Что делает: Создает новый исходный файл TypeScript в проекте. Файл может быть пустым или содержать начальный код.

Параметры:

  • filePath: string — путь к файлу (относительно корня проекта).
  • sourceText?: string — начальное содержимое файла (необязательно).
  • overwrite?: boolean — перезаписывать ли существующий файл (по умолчанию false).

Пример:

const sourceFile = project.createSourceFile('src/utils.ts', 'export function add(a: number, b: number): number { return a + b; }');

2. SourceFile.addFunction()

Что делает: Добавляет новую функцию в исходный файл. Возвращает объект FunctionDeclaration, который можно дальше модифицировать.

Параметры: Объект с настройками функции:

  • name: string — имя функции.
  • parameters?: ParameterDeclarationStructure[] — массив параметров.
  • returnType?: string — возвращаемый тип.
  • statements?: string[] — массив строк с телом функции.
  • isExported?: boolean — экспортировать ли функцию.

Пример:

const func = sourceFile.addFunction({
    name: 'multiply',
    parameters: [{ name: 'a', type: 'number' }, { name: 'b', type: 'number' }],
    returnType: 'number',
    isExported: true,
    statements: ['return a * b;'],
});

3. SourceFile.addClass()

Что делает: Добавляет новый класс в файл. Возвращает объект ClassDeclaration.

Параметры: Объект с настройками класса:

  • name: string — имя класса.
  • extends?: string — класс, от которого наследуется.
  • implements?: string[] — массив интерфейсов.
  • properties?: PropertyDeclarationStructure[] — свойства класса.
  • methods?: MethodDeclarationStructure[] — методы класса.

Пример:

const myClass = sourceFile.addClass({
    name: 'Calculator',
    properties: [{ name: 'result', type: 'number', initializer: '0' }],
    methods: [{
        name: 'add',
        parameters: [{ name: 'value', type: 'number' }],
        statements: ['this.result += value;'],
    }],
});

4. SourceFile.addInterface()

Что делает: Добавляет новый интерфейс в файл. Возвращает объект InterfaceDeclaration.

Параметры: Объект с настройками интерфейса:

  • name: string — имя интерфейса.
  • extends?: string[] — массив интерфейсов для расширения.
  • properties?: PropertySignatureStructure[] — свойства интерфейса.

Пример:

sourceFile.addInterface({
    name: 'User',
    properties: [
        { name: 'id', type: 'number' },
        { name: 'name', type: 'string' },
        { name: 'email', type: 'string' },
    ],
});

5. Node.getDescendants()

Что делает: Возвращает массив всех дочерних узлов (потомков) для данного узла AST. Позволяет рекурсивно обойти всё дерево.

Параметры: Опциональный объект с фильтрами:

  • kind?: SyntaxKind — фильтр по типу узла.
  • depth?: number — максимальная глубина обхода.

Пример:

const allFunctions = sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration);
allFunctions.forEach(func => {
    console.log(`Найдена функция: ${func.getName()}`);
});

6. Node.getChildren()

Что делает: Возвращает массив прямых дочерних узлов (не рекурсивно). Полезен для анализа структуры конкретного узла.

Параметры: Нет.

Пример:

const classDeclaration = sourceFile.getClasses()[0];
const children = classDeclaration.getChildren();
children.forEach(child => {
    console.log(`Тип дочернего узла: ${child.getKindName()}`);
});

7. Node.getText()

Что делает: Возвращает текстовое представление узла и всех его потомков. Это основной способ получить сгенерированный или модифицированный код.

Параметры: Нет.

Пример:

const sourceFile = project.createSourceFile('test.ts', 'const x: number = 42;');
const variableStatement = sourceFile.getVariableStatements()[0];
console.log(variableStatement.getText()); // Выведет: const x: number = 42;

8. SourceFile.addImportDeclaration()

Что делает: Добавляет импорт в файл. Позволяет программно управлять зависимостями.

Параметры: Объект с настройками импорта:

  • moduleSpecifier: string — путь к модулю.
  • namedImports?: string[] — именованные импорты.
  • defaultImport?: string — импорт по умолчанию.

Пример:

sourceFile.addImportDeclaration({
    moduleSpecifier: 'lodash',
    namedImports: ['debounce', 'throttle'],
});

sourceFile.addImportDeclaration({
    moduleSpecifier: 'express',
    defaultImport: 'express',
});

9. Node.insertText()

Что делает: Вставляет текст в указанную позицию внутри узла. Позволяет делать тонкие изменения в коде, не заменяя весь узел.

Параметры:

  • pos: number — позиция вставки (индекс символа).
  • text: string — текст для вставки.

Пример:

const sourceFile = project.createSourceFile('test.ts', 'function foo() {}');
const func = sourceFile.getFunctions()[0];
// Вставляем тело функции
func.insertText(func.getEnd() - 1, 'console.log("Hello");');
console.log(sourceFile.getText()); // Выведет: function foo() {console.log("Hello");}

10. Project.getSourceFiles()

Что делает: Возвращает массив всех исходных файлов, добавленных в проект. Позволяет итерироваться по всем файлам проекта.

Параметры: Нет.

Пример:

const allFiles = project.getSourceFiles();
allFiles.forEach(file => {
    console.log(`Файл: ${file.getFilePath()}`);
    const classes = file.getClasses();
    classes.forEach(cls => {
        console.log(`  Класс: ${cls.getName()}`);
    });
});

Реальный практический пример использования

Давайте создадим утилиту для автоматического добавления JSDoc-комментариев ко всем публичным функциям в проекте. Это может быть полезно для генерации документации или соблюдения стандартов кодирования.

import { Project, SyntaxKind, FunctionDeclaration } from 'ts-morph';
import * as path from 'path';

// Функция для генерации JSDoc на основе сигнатуры функции
function generateJsDoc(func: FunctionDeclaration): string {
    const params = func.getParameters();
    const returnType = func.getReturnType().getText();
    
    let jsDoc = '/**\n';
    jsDoc += ` * Функция ${func.getName()}\n`;
    
    params.forEach(param => {
        const paramName = param.getName();
        const paramType = param.getType().getText();
        jsDoc += ` * @param ${paramName} - Параметр типа ${paramType}\n`;
    });
    
    if (returnType                    

Рекомендаций