Введение в 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 Также в библиотеке
HTTP-клиент для Go (Golang): топ библиотек для работы с HTTP
Конфигурация приложений Kotlin: библиотеки и подходы
Мониторинг Java-приложений: библиотека Micrometer и интеграция с Prometheus