Skip to main content
Glama
SiroSuzume

MCP ts-morph Refactoring Tools

by SiroSuzume
classify-dependencies.test.ts6.21 kB
import { describe, it, expect } from "vitest"; import { Project, SyntaxKind } from "ts-morph"; import { findTopLevelDeclarationByName } from "./find-declaration"; import { getInternalDependencies } from "./internal-dependencies"; import type { DependencyClassification } from "../types"; import { classifyDependencies } from "./classify-dependencies"; // テスト用ヘルパー: コードからプロジェクトと主要要素を取得 const setupTest = ( code: string, targetSymbolName: string, targetKind: SyntaxKind, ) => { const project = new Project({ useInMemoryFileSystem: true }); const sourceFile = project.createSourceFile("/src/module.ts", code); const targetDeclaration = findTopLevelDeclarationByName( sourceFile, targetSymbolName, targetKind, ); const internalDependencies = targetDeclaration ? getInternalDependencies(targetDeclaration) : []; if (!targetDeclaration) { throw new Error(`Target symbol '${targetSymbolName}' not found.`); } return { project, sourceFile, targetDeclaration, internalDependencies }; }; describe("classifyDependencies", () => { it("exportされておらず、移動対象からのみ参照される依存は moveToNewFile に分類される", () => { const code = ` function helper() { return 1; } export const main = () => helper(); `; const { targetDeclaration, internalDependencies } = setupTest( code, "main", SyntaxKind.VariableStatement, ); expect(internalDependencies.length).toBe(1); const helperDep = internalDependencies[0]; expect(helperDep).toBeDefined(); if (!helperDep) return; expect(helperDep.getKind()).toBe(SyntaxKind.FunctionDeclaration); const classified = classifyDependencies( targetDeclaration, internalDependencies, ); expect(classified).toEqual<DependencyClassification[]>([ { type: "moveToNewFile", statement: helperDep }, ]); }); it("exportされており、移動対象から参照される依存は importFromOriginal に分類される", () => { const code = ` export function sharedHelper() { return 2; } // export されている export const main = () => sharedHelper(); // 他の参照があってもなくても export されていれば B' 扱い `; const { targetDeclaration, internalDependencies } = setupTest( code, "main", SyntaxKind.VariableStatement, ); expect(internalDependencies.length).toBe(1); const sharedHelperDep = internalDependencies[0]; expect(sharedHelperDep).toBeDefined(); if (!sharedHelperDep) return; expect(sharedHelperDep.getKind()).toBe(SyntaxKind.FunctionDeclaration); const classified = classifyDependencies( targetDeclaration, internalDependencies, ); expect(classified).toEqual<DependencyClassification[]>([ { type: "importFromOriginal", statement: sharedHelperDep, name: "sharedHelper", }, ]); }); it("exportされておらず、移動対象以外からも参照される依存は addExport に分類される", () => { const code = ` function util() { return 3; } // export されていない export const main = () => util(); export const another = () => util(); `; const { targetDeclaration, internalDependencies } = setupTest( code, "main", // main を移動対象とする SyntaxKind.VariableStatement, ); expect(internalDependencies.length).toBe(1); const utilDep = internalDependencies[0]; expect(utilDep).toBeDefined(); if (!utilDep) return; expect(utilDep.getKind()).toBe(SyntaxKind.FunctionDeclaration); const classified = classifyDependencies( targetDeclaration, internalDependencies, ); expect(classified).toEqual<DependencyClassification[]>([ { type: "addExport", statement: utilDep, name: "util" }, ]); }); it("内部依存関係がない場合は空配列を返す", () => { const code = "export const main = 123;"; const { targetDeclaration, internalDependencies } = setupTest( code, "main", SyntaxKind.VariableStatement, ); expect(internalDependencies.length).toBe(0); const classified = classifyDependencies( targetDeclaration, internalDependencies, ); expect(classified).toEqual([]); }); it("複数の依存関係が混在する場合、それぞれ正しく分類される", () => { const code = ` function privateHelper() { return 'A'; } // Case A: main からのみ参照 export function sharedExportedHelper() { return 'B'; } // Case B': export 済み function sharedNonExportedUtil() { return 'C'; } // Case B'': main と another から参照 export const main = () => { return privateHelper() + sharedExportedHelper() + sharedNonExportedUtil(); }; export const another = () => { // sharedExportedHelper も参照 return sharedExportedHelper() + sharedNonExportedUtil(); }; `; const { project, sourceFile, targetDeclaration, internalDependencies } = setupTest(code, "main", SyntaxKind.VariableStatement); const privateHelperDep = findTopLevelDeclarationByName( sourceFile, "privateHelper", SyntaxKind.FunctionDeclaration, ); const sharedExportedDep = findTopLevelDeclarationByName( sourceFile, "sharedExportedHelper", SyntaxKind.FunctionDeclaration, ); const sharedNonExportedDep = findTopLevelDeclarationByName( sourceFile, "sharedNonExportedUtil", SyntaxKind.FunctionDeclaration, ); expect(internalDependencies.length).toBe(3); expect(privateHelperDep).toBeDefined(); expect(sharedExportedDep).toBeDefined(); expect(sharedNonExportedDep).toBeDefined(); if (!privateHelperDep || !sharedExportedDep || !sharedNonExportedDep) return; const classified = classifyDependencies( targetDeclaration, internalDependencies, ); // 順序は不定なので、内容が一致するか確認 expect(classified).toHaveLength(3); expect(classified).toEqual( expect.arrayContaining<DependencyClassification>([ { type: "moveToNewFile", statement: privateHelperDep }, { type: "importFromOriginal", statement: sharedExportedDep, name: "sharedExportedHelper", }, { type: "addExport", statement: sharedNonExportedDep, name: "sharedNonExportedUtil", }, ]), ); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SiroSuzume/mcp-ts-morph'

If you have feedback or need assistance with the MCP directory API, please join our Discord server