Back to list
robBowes

ts-morph-refactoring

by robBowes

0🍴 0📅 Jan 13, 2026

SKILL.md


name: ts-morph-refactoring description: TypeScript refactoring using ts-morph in pnpm monorepos. Use when renaming variables, functions, classes, or types across packages; updating imports; moving files; or performing codemod operations. Triggers on requests involving TypeScript AST manipulation, bulk code changes, or cross-package refactoring.

ts-morph Refactoring in pnpm Monorepos

Setup

import { Project } from "ts-morph";

const project = new Project({
  tsConfigFilePath: "./tsconfig.json", // Root tsconfig with project references
});

// For monorepo: add all package tsconfigs
project.addSourceFilesFromTsConfig("./packages/*/tsconfig.json");

Rename Variable/Function

const sourceFile = project.getSourceFileOrThrow("src/utils.ts");

// Find and rename
const fn = sourceFile.getFunctionOrThrow("oldName");
fn.rename("newName"); // Updates all references across project

// For exports - also updates import statements
const exportedFn = sourceFile.getExportedDeclarations().get("oldExport")?.[0];
exportedFn?.asKind(SyntaxKind.FunctionDeclaration)?.rename("newExport");

project.saveSync();

Rename Across Packages

// Find declaration in any package
const declaration = project.getSourceFiles()
  .flatMap(sf => sf.getExportedDeclarations().get("targetName") ?? [])
  .find(d => d.getSourceFile().getFilePath().includes("packages/shared"));

declaration?.asKind(SyntaxKind.FunctionDeclaration)?.rename("newTargetName");
// All imports in all packages update automatically

project.saveSync();

Update Import Paths

// Change import path across all files
project.getSourceFiles().forEach(sourceFile => {
  sourceFile.getImportDeclarations().forEach(imp => {
    const moduleSpec = imp.getModuleSpecifierValue();
    if (moduleSpec === "@old/package") {
      imp.setModuleSpecifier("@new/package");
    }
    // Relative path updates
    if (moduleSpec.startsWith("../old-folder/")) {
      imp.setModuleSpecifier(moduleSpec.replace("../old-folder/", "../new-folder/"));
    }
  });
});

project.saveSync();

Add/Remove Named Imports

sourceFile.getImportDeclarations().forEach(imp => {
  if (imp.getModuleSpecifierValue() === "@company/utils") {
    // Add named import
    imp.addNamedImport("newUtil");
    
    // Remove named import
    imp.getNamedImports()
      .find(n => n.getName() === "deprecatedUtil")
      ?.remove();
    
    // Rename named import
    imp.getNamedImports()
      .find(n => n.getName() === "oldUtil")
      ?.setName("renamedUtil");
  }
});

Move Export Between Packages

// Get source declaration
const srcFile = project.getSourceFileOrThrow("packages/old/src/helper.ts");
const fn = srcFile.getFunctionOrThrow("helperFn");
const fnText = fn.getFullText();

// Add to destination
const destFile = project.getSourceFileOrThrow("packages/new/src/helper.ts");
destFile.addStatements(fnText);

// Update all imports
project.getSourceFiles().forEach(sf => {
  sf.getImportDeclarations()
    .filter(i => i.getModuleSpecifierValue() === "@company/old")
    .forEach(imp => {
      const namedImport = imp.getNamedImports().find(n => n.getName() === "helperFn");
      if (namedImport) {
        namedImport.remove();
        if (imp.getNamedImports().length === 0) imp.remove();
        
        // Add new import if not exists
        const existingNew = sf.getImportDeclaration("@company/new");
        if (existingNew) {
          existingNew.addNamedImport("helperFn");
        } else {
          sf.addImportDeclaration({
            moduleSpecifier: "@company/new",
            namedImports: ["helperFn"]
          });
        }
      }
    });
});

// Remove from source after updating imports
fn.remove();
project.saveSync();

Common Patterns

Find All References

const refs = declaration.findReferencesAsNodes();
refs.forEach(ref => console.log(ref.getSourceFile().getFilePath(), ref.getStartLineNumber()));

Batch Rename Pattern

const renames = [["oldA", "newA"], ["oldB", "newB"]];
renames.forEach(([old, new_]) => {
  project.getSourceFiles().forEach(sf => {
    sf.getExportedDeclarations().get(old)?.[0]
      ?.asKind(SyntaxKind.FunctionDeclaration)?.rename(new_);
  });
});
project.saveSync();

Dry Run

// Preview changes without saving
project.getSourceFiles().forEach(sf => {
  if (sf.wasSaved() === false) {
    console.log(`Would modify: ${sf.getFilePath()}`);
    console.log(sf.getFullText());
  }
});

Notes

  • Always use project.saveSync() after refactoring
  • rename() handles cross-file references automatically
  • For pnpm workspaces: ensure root tsconfig has references to all packages
  • Use SyntaxKind enum for type narrowing: import { SyntaxKind } from "ts-morph"

Score

Total Score

50/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon