Skip to main content
Glama
SiroSuzume

MCP ts-morph Refactoring Tools

by SiroSuzume
find-declaration.test.ts10.4 kB
import { describe, it, expect } from "vitest"; import { type ClassDeclaration, type FunctionDeclaration, type InterfaceDeclaration, Project, SyntaxKind, type TypeAliasDeclaration, type SourceFile, type VariableStatement, type Statement, } from "ts-morph"; import { findTopLevelDeclarationByName } from "./find-declaration"; import { getIdentifierFromDeclaration } from "./find-declaration"; // import { getTopLevelDeclarationsFromFile } from './move-symbol'; // 不要 // --- Test Setup Helper --- const setupProject = () => { const project = new Project({ useInMemoryFileSystem: true, compilerOptions: { target: 99, module: 99 }, }); project.createDirectory("/src"); return project; }; // --- Test Data --- const commonTestSource = ` import DefaultIface from './default-iface'; // 無視されるべき // 通常の宣言 function funcA() {} const varA = 1; class ClassA {} type TypeA = string; interface IfaceA {} // エクスポートされた宣言 export function funcB() {} export const varB = 2; export class ClassB {} export type TypeB = number; export interface IfaceB<T> {} // ジェネリクス付き // デフォルトエクスポート export default function defaultFunc() {} // export default class DefaultClass {} // 同名は不可なのでコメントアウト // export default const defaultVar = 3; // デフォルトエクスポート変数(あまり一般的ではない) // 同じ名前の宣言 (種類違い) const funcC = "hello"; function funcC() {} // 再宣言 (関数が優先されるはず) // VariableStatement 内の複数宣言 export const multiVar1 = 1, multiVar2 = 2; `; // --- Test Data Structure --- type ExpectedResult = { kind: SyntaxKind; name: string }; type TestCase = [ string, string, SyntaxKind | undefined, ExpectedResult | undefined, ]; const testCases: TestCase[] = [ // description, nameToFind, kindToFind, expectedResult { kind, name } or undefined [ "関数 funcB を種類指定で見つける", "funcB", SyntaxKind.FunctionDeclaration, { kind: SyntaxKind.FunctionDeclaration, name: "funcB" }, ], [ "変数 varB を種類指定で見つける", "varB", SyntaxKind.VariableStatement, { kind: SyntaxKind.VariableStatement, name: "varB" }, ], [ "クラス ClassB を種類指定で見つける", "ClassB", SyntaxKind.ClassDeclaration, { kind: SyntaxKind.ClassDeclaration, name: "ClassB" }, ], [ "型 TypeB を種類指定で見つける", "TypeB", SyntaxKind.TypeAliasDeclaration, { kind: SyntaxKind.TypeAliasDeclaration, name: "TypeB" }, ], [ "インターフェース IfaceB を種類指定で見つける", "IfaceB", SyntaxKind.InterfaceDeclaration, { kind: SyntaxKind.InterfaceDeclaration, name: "IfaceB" }, ], [ "関数 funcA を種類指定なしで見つける", "funcA", undefined, { kind: SyntaxKind.FunctionDeclaration, name: "funcA" }, ], [ "変数 varA を種類指定なしで見つける", "varA", undefined, { kind: SyntaxKind.VariableStatement, name: "varA" }, ], [ "複数宣言の multiVar1 を種類指定で見つける", "multiVar1", SyntaxKind.VariableStatement, { kind: SyntaxKind.VariableStatement, name: "multiVar1" }, ], [ "複数宣言の multiVar2 を種類指定で見つける", "multiVar2", SyntaxKind.VariableStatement, { kind: SyntaxKind.VariableStatement, name: "multiVar2" }, ], // ["デフォルト関数を 'default' で見つける", "default", SyntaxKind.FunctionDeclaration, { kind: SyntaxKind.FunctionDeclaration, name: "defaultFunc" }], // デフォルト名での検索は一旦保留 [ "デフォルト関数を実際の名前(defaultFunc)で見つける", "defaultFunc", SyntaxKind.FunctionDeclaration, { kind: SyntaxKind.FunctionDeclaration, name: "defaultFunc" }, ], [ "種類が異なる場合 (関数funcBをクラスとして検索)", "funcB", SyntaxKind.ClassDeclaration, undefined, ], ["存在しない名前(nonExistent)の場合", "nonExistent", undefined, undefined], // ["同名宣言 funcC (関数が優先されるはず)", "funcC", undefined, { kind: SyntaxKind.FunctionDeclaration, name: "funcC" }], // 同名宣言の挙動は実装次第 ]; describe("findTopLevelDeclarationByName", () => { const setupSourceFile = (content: string): SourceFile => { const project = setupProject(); const filePath = "/src/test-find.ts"; return project.createSourceFile(filePath, content); }; const sourceFile = setupSourceFile(commonTestSource); // 事前に SourceFile を作成 it.each<TestCase>(testCases)( "%s (name: %s, kind: %s)", (description, nameToFind, kindToFind, expectedResult) => { const foundDeclaration = findTopLevelDeclarationByName( sourceFile, nameToFind, kindToFind, ); if (expectedResult) { // 見つかることを期待する場合 expect(foundDeclaration).toBeDefined(); expect(foundDeclaration?.getKind()).toBe(expectedResult.kind); // 名前の一致をチェック if (expectedResult.kind === SyntaxKind.VariableStatement) { // VariableStatement の場合は、指定された名前の VariableDeclaration が含まれるかチェック const varDecls = ( foundDeclaration as VariableStatement )?.getDeclarations(); const specificVarDecl = varDecls?.find( (vd) => vd.getName() === expectedResult.name, ); expect(specificVarDecl).toBeDefined(); } else { // Function, Class, Interface, TypeAlias // デフォルトエクスポートでgetName()がundefinedになる場合も考慮 (今は実際の名前で検索) // ANY TYPE HERE IS INTENTIONAL FOR NOW - will be fixed if test fails expect( ( foundDeclaration as | FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | TypeAliasDeclaration ).getName?.(), ).toBe(expectedResult.name); } } else { // 見つからないことを期待する場合 expect(foundDeclaration).toBeUndefined(); } }, ); // TODO: デフォルトエクスポートの 'default' 名検索、同名宣言に関するテストを別途追加 }); describe("getIdentifierFromDeclaration", () => { // Helper to create a project and get the first statement const getFirstStatement = (code: string): Statement | undefined => { const project = new Project({ useInMemoryFileSystem: true }); const sourceFile = project.createSourceFile("test.ts", code); return sourceFile.getStatements()[0]; }; it("FunctionDeclaration の識別子を返すこと", () => { const statement = getFirstStatement("function myFunction() {}"); const identifier = getIdentifierFromDeclaration(statement); expect(identifier?.getText()).toBe("myFunction"); }); it("ClassDeclaration の識別子を返すこと", () => { const statement = getFirstStatement("class MyClass {}"); const identifier = getIdentifierFromDeclaration(statement); expect(identifier?.getText()).toBe("MyClass"); }); it("InterfaceDeclaration の識別子を返すこと", () => { const statement = getFirstStatement("interface MyInterface {}"); const identifier = getIdentifierFromDeclaration(statement); expect(identifier?.getText()).toBe("MyInterface"); }); it("TypeAliasDeclaration の識別子を返すこと", () => { const statement = getFirstStatement("type MyType = string;"); const identifier = getIdentifierFromDeclaration(statement); expect(identifier?.getText()).toBe("MyType"); }); it("EnumDeclaration の識別子を返すこと", () => { const statement = getFirstStatement("enum MyEnum { A, B }"); const identifier = getIdentifierFromDeclaration(statement); expect(identifier?.getText()).toBe("MyEnum"); }); it("VariableStatement (const) の識別子を返すこと", () => { const statement = getFirstStatement("const myVar = 10;"); const identifier = getIdentifierFromDeclaration(statement); expect(identifier?.getText()).toBe("myVar"); }); it("VariableStatement (複数宣言) の最初の識別子を返すこと", () => { const statement = getFirstStatement("let var1 = 1, var2 = 2;"); const identifier = getIdentifierFromDeclaration(statement); expect(identifier?.getText()).toBe("var1"); // 最初の宣言の識別子を返す仕様 }); it("エクスポートされた FunctionDeclaration の識別子を返すこと", () => { const statement = getFirstStatement("export function myFunction() {}"); const identifier = getIdentifierFromDeclaration(statement); expect(identifier?.getText()).toBe("myFunction"); }); it("デフォルトエクスポートされた名前付き FunctionDeclaration の識別子を返すこと", () => { const statement = getFirstStatement( "export default function myFunction() {}", ); const identifier = getIdentifierFromDeclaration(statement); expect(identifier?.getText()).toBe("myFunction"); }); it("デフォルトエクスポートされた匿名 FunctionDeclaration では undefined を返すこと", () => { const statement = getFirstStatement("export default function() {}"); const identifier = getIdentifierFromDeclaration(statement); expect(identifier).toBeUndefined(); // 匿名なので名前がない }); it("ExportAssignment (識別子) の識別子を返すこと", () => { const code = "const foo = 1;\nexport default foo;"; const project = new Project({ useInMemoryFileSystem: true }); const sourceFile = project.createSourceFile("test.ts", code); const statement = sourceFile.getStatements()[1]; // ExportAssignment を取得 const identifier = getIdentifierFromDeclaration(statement); expect(identifier?.getText()).toBe("foo"); }); it("ExportAssignment (非識別子) では undefined を返すこと", () => { const statement = getFirstStatement("export default { a: 1 };"); const identifier = getIdentifierFromDeclaration(statement); expect(identifier).toBeUndefined(); }); it("サポート外の Statement (ImportDeclarationなど) では undefined を返すこと", () => { const statement = getFirstStatement("import { x } from './other';"); const identifier = getIdentifierFromDeclaration(statement); expect(identifier).toBeUndefined(); }); it("undefined が入力された場合は undefined を返すこと", () => { const identifier = getIdentifierFromDeclaration(undefined); expect(identifier).toBeUndefined(); }); });

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