Skip to main content
Glama
SiroSuzume

MCP ts-morph Refactoring Tools

by SiroSuzume
build-new-file-import-section.ts7.44 kB
import logger from "../../../utils/logger"; import { calculateRelativePath } from "../../_utils/calculate-relative-path"; import type { DependencyClassification, NeededExternalImports, } from "../../types"; type ExtendedImportInfo = { defaultName?: string; namedImports: Set<string>; isNamespaceImport: boolean; namespaceImportName?: string; }; export type ImportMap = Map<string, ExtendedImportInfo>; function aggregateImports( importMap: ImportMap, relativePath: string, importName: string, isDefault: boolean, ) { if (isDefault) { const actualDefaultName = importName; if (!importMap.has(relativePath)) { importMap.set(relativePath, { namedImports: new Set(), isNamespaceImport: false, }); } const entry = importMap.get(relativePath); if (!entry || entry.isNamespaceImport) { logger.warn( `Skipping default import aggregation for ${relativePath} due to existing namespace import or missing entry.`, ); return; } entry.defaultName = actualDefaultName; logger.debug( `Aggregated default import: ${actualDefaultName} for path: ${relativePath}`, ); return; } const nameToAdd = importName; if (!importMap.has(relativePath)) { importMap.set(relativePath, { namedImports: new Set(), isNamespaceImport: false, }); } const entry = importMap.get(relativePath); if (!entry || entry.isNamespaceImport) { logger.warn( `Skipping named import aggregation for ${relativePath} due to existing namespace import or missing entry.`, ); return; } entry.namedImports.add(nameToAdd); logger.debug( `Aggregated named import: ${nameToAdd} for path: ${relativePath}`, ); } function processExternalImports( importMap: ImportMap, neededExternalImports: NeededExternalImports, newFilePath: string, ): void { logger.debug("Processing external imports..."); for (const [ originalModuleSpecifier, { names, declaration, isNamespaceImport, namespaceImportName }, ] of neededExternalImports.entries()) { const moduleSourceFile = declaration?.getModuleSpecifierSourceFile(); let relativePath = ""; let isSelfReference = false; if ( moduleSourceFile && !moduleSourceFile.getFilePath().includes("/node_modules/") ) { const absoluteModulePath = moduleSourceFile.getFilePath(); if (absoluteModulePath === newFilePath) { isSelfReference = true; } else { relativePath = calculateRelativePath(newFilePath, absoluteModulePath); logger.debug( `Calculated relative path for NON-node_modules import: ${relativePath} (from ${absoluteModulePath})`, ); } } else { relativePath = originalModuleSpecifier; logger.debug( `Using original module specifier for node_modules or unresolved import: ${relativePath}`, ); } if (isSelfReference) { logger.debug(`Skipping self-reference import for path: ${newFilePath}`); continue; } if (isNamespaceImport && namespaceImportName) { if (!importMap.has(relativePath)) { importMap.set(relativePath, { namedImports: new Set(), isNamespaceImport: true, namespaceImportName: namespaceImportName, }); logger.debug( `Added namespace import: ${namespaceImportName} for path: ${relativePath}`, ); } else { logger.warn( `Namespace import for ${relativePath} conflicts with existing non-namespace imports. Skipping.`, ); } continue; } const defaultImportNode = declaration?.getDefaultImport(); const actualDefaultName = defaultImportNode?.getText(); for (const name of names) { const isDefaultFlag = name === "default" && !!actualDefaultName; if (isDefaultFlag) { if (!actualDefaultName) { logger.warn( `Default import name was expected but not found for ${relativePath}. Skipping default import.`, ); continue; } aggregateImports(importMap, relativePath, actualDefaultName, true); } else { aggregateImports(importMap, relativePath, name, false); } } } } function processInternalDependencies( importMap: ImportMap, classifiedDependencies: DependencyClassification[], newFilePath: string, originalFilePath: string, ): void { logger.debug("Processing internal dependencies for import map..."); if (newFilePath === originalFilePath) { logger.debug( "Skipping internal dependency processing as source and target files are the same.", ); return; } const dependenciesToImportNames = new Set<string>(); for (const dep of classifiedDependencies) { if (dep.type === "importFromOriginal" || dep.type === "addExport") { logger.debug(`Internal dependency to import from original: ${dep.name}`); dependenciesToImportNames.add(dep.name); } } if (dependenciesToImportNames.size === 0) { logger.debug("No internal dependencies need importing from original file."); return; } const internalImportPath = calculateRelativePath( newFilePath, originalFilePath, ); logger.debug( `Calculated relative path for internal import: ${internalImportPath}`, ); if (internalImportPath !== "." && internalImportPath !== "./") { for (const name of dependenciesToImportNames) { aggregateImports(importMap, internalImportPath, name, false); } } else { logger.debug("Skipping aggregation for self-referencing internal path."); } } function buildImportStatementString( defaultImportName: string | undefined, namedImportSpecifiers: string, relativePath: string, isNamespaceImport: boolean, namespaceImportName?: string, ): string { const fromPart = `from "${relativePath}";`; if (isNamespaceImport && namespaceImportName) { return `import * as ${namespaceImportName} ${fromPart}`; } if (!defaultImportName && !namedImportSpecifiers) { logger.debug(`Building side-effect import for ${relativePath}`); return `import ${fromPart}`; } const defaultPart = defaultImportName ? `${defaultImportName}` : ""; const namedPart = namedImportSpecifiers ? `{ ${namedImportSpecifiers} }` : ""; const separator = defaultPart && namedPart ? ", " : ""; return `import ${defaultPart}${separator}${namedPart} ${fromPart}`; } export function calculateRequiredImportMap( neededExternalImports: NeededExternalImports, classifiedDependencies: DependencyClassification[], newFilePath: string, originalFilePath: string, ): ImportMap { const importMap: ImportMap = new Map(); processExternalImports(importMap, neededExternalImports, newFilePath); processInternalDependencies( importMap, classifiedDependencies, newFilePath, originalFilePath, ); return importMap; } export function buildImportSectionStringFromMap(importMap: ImportMap): string { logger.debug("Generating import section string..."); let importSection = ""; const sortedPaths = [...importMap.keys()].sort(); for (const path of sortedPaths) { const importData = importMap.get(path); if (!importData) { logger.warn(`Import data not found for path ${path} during generation.`); continue; } const { defaultName, namedImports, isNamespaceImport, namespaceImportName, } = importData; const sortedNamedImports = [...namedImports].sort().join(", "); const importStatement = buildImportStatementString( defaultName, sortedNamedImports, path, isNamespaceImport, namespaceImportName, ); if (importStatement) { importSection += `${importStatement}\n`; } } if (importSection) { importSection += "\n"; } logger.debug(`Generated Import Section String: ${importSection}`); return importSection; }

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