Skip to content

ts-morph

Developer UtilitiesUtilityTypeScript

What it is

ts-morph wraps the TypeScript compiler API in a navigable, mutable object model, making programmatic code analysis and refactoring practical.

Load a project from tsconfig, traverse or query the AST, mutate nodes, and save. Useful for codemods, generation and architectural checks.

Installation

npm install --save-dev ts-morph

Getting started

The smallest useful thing you can do with it, and what each part means.

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

const project = new Project({ tsConfigFilePath: './tsconfig.json' });

for (const file of project.getSourceFiles('src/**/*.ts')) {
  for (const cls of file.getClasses()) {
    const publicMethods = cls.getMethods().filter((m) => !m.hasModifier(SyntaxKind.PrivateKeyword));
    if (publicMethods.length > 20) {
      console.warn(`${cls.getName()} has ${publicMethods.length} public methods`);
    }
  }
}
This is a real use: enforcing architectural rules that a linter cannot express, run as a build step.

Advanced usage

Where the library earns its place over a simpler alternative.

A codemod that rewrites imports
const project = new Project({ tsConfigFilePath: './tsconfig.json' });

for (const file of project.getSourceFiles()) {
  for (const decl of file.getImportDeclarations()) {
    if (decl.getModuleSpecifierValue() === 'lodash') {
      // lodash -> per-function imports, for tree shaking
      const names = decl.getNamedImports().map((n) => n.getName());
      decl.remove();
      for (const name of names) {
        file.addImportDeclaration({
          defaultImport: name,
          moduleSpecifier: `lodash/${name}`,
        });
      }
    }
  }
}

await project.save(); // writes every modified file
Changes are held in memory until save(), so you can inspect the result first. Run codemods on a clean working tree so the diff is reviewable.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Changes are not written to disk
project.save() was not awaited. Mutations live in memory until then.
Analysis is very slow
The whole program is being loaded. Restrict the file glob, or set skipAddingFilesFromTsConfig and add only what you need.

Best practices

  • Run codemods against a clean git tree so the change can be reviewed and reverted.
  • Use getSourceFiles with a glob rather than loading the whole project when you only need part of it.
  • Call save() once at the end; per-file saves are much slower.
  • Prefer a lint rule when one exists — ts-morph is for what linters cannot express.

Background

Why it exists, and what it was reacting to.

The raw compiler API is powerful and famously awkward. ts-morph, by David Sherret, gives it an ergonomic interface — the basis of many codemods and code generators.