(options: BlastRadiusOptions)
| 18 | } |
| 19 | |
| 20 | export async function getBlastRadius(options: BlastRadiusOptions): Promise<string> { |
| 21 | const entries = await walkDirectory({ rootDir: options.rootDir, depthLimit: 0 }); |
| 22 | const files = entries.filter((e) => !e.isDirectory && isSupportedFile(e.path)); |
| 23 | const usages: SymbolUsage[] = []; |
| 24 | const symbolPattern = new RegExp(`\\b${escapeRegex(options.symbolName)}\\b`, "g"); |
| 25 | |
| 26 | for (const file of files) { |
| 27 | try { |
| 28 | const content = await readFile(file.path, "utf-8"); |
| 29 | const lines = content.split("\n"); |
| 30 | |
| 31 | for (let i = 0; i < lines.length; i++) { |
| 32 | if (symbolPattern.test(lines[i])) { |
| 33 | const isDefinition = options.fileContext && file.relativePath === options.fileContext && isDefinitionLine(lines[i], options.symbolName); |
| 34 | if (!isDefinition) { |
| 35 | usages.push({ |
| 36 | file: file.relativePath, |
| 37 | line: i + 1, |
| 38 | context: lines[i].trim().substring(0, 120), |
| 39 | }); |
| 40 | } |
| 41 | symbolPattern.lastIndex = 0; |
| 42 | } |
| 43 | } |
| 44 | } catch { |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | if (usages.length === 0) return `Symbol "${options.symbolName}" is not used anywhere in the codebase.`; |
| 49 | |
| 50 | const byFile = new Map<string, SymbolUsage[]>(); |
| 51 | for (const u of usages) { |
| 52 | const existing = byFile.get(u.file) ?? []; |
| 53 | existing.push(u); |
| 54 | byFile.set(u.file, existing); |
| 55 | } |
| 56 | |
| 57 | const lines: string[] = [ |
| 58 | `Blast radius for "${options.symbolName}": ${usages.length} usages in ${byFile.size} files\n`, |
| 59 | ]; |
| 60 | |
| 61 | for (const [file, fileUsages] of byFile) { |
| 62 | lines.push(` ${file}:`); |
| 63 | for (const u of fileUsages) { |
| 64 | lines.push(` L${u.line}: ${u.context}`); |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | if (usages.length <= 1) { |
| 69 | lines.push(`\n⚠ LOW USAGE: This symbol is used only ${usages.length} time(s). Consider inlining if it's under 20 lines.`); |
| 70 | } |
| 71 | |
| 72 | return lines.join("\n"); |
| 73 | } |
| 74 | |
| 75 | function escapeRegex(str: string): string { |
| 76 | return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); |
no test coverage detected