Skip to main content
Glama
refactor-tool.ts6.34 kB
import { existsSync } from 'fs'; import { searchFiles, readFileContent, writeFileContent, } from '../utils/file-utils.js'; export interface RefactorOptions { searchPattern: string; replacePattern: string; contextPattern?: string; filePattern?: string; dryRun?: boolean; } export interface RefactorMatch { line: number; content: string; original: string; replaced: string; captureGroups?: string[]; } export interface RefactorResult { filePath: string; replacements: number; matches: RefactorMatch[]; modified: boolean; } export async function performRefactor( options: RefactorOptions ): Promise<RefactorResult[]> { const files = await searchFiles(options.filePattern); const results: RefactorResult[] = []; for (const filePath of files) { if (!existsSync(filePath)) continue; const content = readFileContent(filePath); const lines = content.split('\n'); let modified = false; let fileReplacements = 0; const matchedLines: RefactorMatch[] = []; const searchRegex = new RegExp(options.searchPattern, 'g'); const contextRegex = options.contextPattern ? new RegExp(options.contextPattern, 'g') : null; let newContent = content; if (contextRegex) { const matches = [...content.matchAll(searchRegex)]; for (const match of matches) { if (match.index !== undefined) { const beforeMatch = content.substring(0, match.index); const afterMatch = content.substring(match.index + match[0].length); const contextBefore = beforeMatch.split('\n').slice(-5).join('\n'); const contextAfter = afterMatch.split('\n').slice(0, 5).join('\n'); const contextArea = contextBefore + match[0] + contextAfter; if (contextRegex.test(contextArea)) { const lineNumber = beforeMatch.split('\n').length; const originalLine = lines[lineNumber - 1]; // Extract capture groups if any const captureGroups = match .slice(1) .filter(group => group !== undefined); const replaced = match[0].replace( new RegExp(options.searchPattern), options.replacePattern ); matchedLines.push({ line: lineNumber, content: originalLine, original: match[0], replaced, captureGroups: captureGroups.length > 0 ? captureGroups : undefined, }); newContent = newContent.replace(match[0], replaced); fileReplacements++; modified = true; } } } } else { const matches = [...content.matchAll(searchRegex)]; for (const match of matches) { if (match.index !== undefined) { const beforeMatch = content.substring(0, match.index); const lineNumber = beforeMatch.split('\n').length; const originalLine = lines[lineNumber - 1]; // Extract capture groups if any const captureGroups = match .slice(1) .filter(group => group !== undefined); const replaced = match[0].replace( new RegExp(options.searchPattern), options.replacePattern ); matchedLines.push({ line: lineNumber, content: originalLine, original: match[0], replaced, captureGroups: captureGroups.length > 0 ? captureGroups : undefined, }); } } const replacedContent = content.replace( searchRegex, options.replacePattern ); if (replacedContent !== content) { newContent = replacedContent; fileReplacements = (content.match(searchRegex) || []).length; modified = true; } } if (modified) { if (!options.dryRun) { writeFileContent(filePath, newContent); } results.push({ filePath, replacements: fileReplacements, matches: matchedLines, modified: true, }); } } return results; } export interface RefactorFormatOptions { includeCaptureGroups?: boolean; includeMatchedText?: boolean; dryRun?: boolean; } export function formatRefactorResults( results: RefactorResult[], options?: RefactorFormatOptions | boolean ): string { // Handle backward compatibility - if boolean is passed, treat as dryRun const formatOptions: RefactorFormatOptions = typeof options === 'boolean' ? { dryRun: options } : options || {}; if (results.length === 0) { return 'No matches found for the given pattern'; } if (formatOptions.includeCaptureGroups || formatOptions.includeMatchedText) { return formatDetailedRefactorResults(results, formatOptions); } const formattedResults = results.map( result => `${result.filePath}: ${result.replacements} replacements${formatOptions.dryRun ? ' (dry run)' : ''}` ); const totalReplacements = results.reduce( (sum, result) => sum + result.replacements, 0 ); return `Refactoring completed:\n${formattedResults.join('\n')}\n\nTotal: ${totalReplacements} replacements in ${results.length} files`; } function formatDetailedRefactorResults( results: RefactorResult[], options: RefactorFormatOptions ): string { const output: string[] = [ `Refactoring completed${options.dryRun ? ' (dry run)' : ''}:`, ]; for (const result of results) { output.push(`\n${result.filePath}: ${result.replacements} replacements`); for (const match of result.matches) { if (options.includeMatchedText) { output.push( ` Line ${match.line}: ${match.original} → ${match.replaced}` ); } else { output.push(` Line ${match.line}: ${match.content}`); } if ( options.includeCaptureGroups && match.captureGroups && match.captureGroups.length > 0 ) { output.push(` └─ Captured: [${match.captureGroups.join(', ')}]`); } } } const totalReplacements = results.reduce( (sum, result) => sum + result.replacements, 0 ); output.push( `\nTotal: ${totalReplacements} replacements in ${results.length} files${options.dryRun ? ' (dry run)' : ''}` ); return output.join('\n'); }

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/myuon/refactor-mcp'

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