diff --git a/src/resolution/import-resolver.ts b/src/resolution/import-resolver.ts index d68fcc7..7e2570b 100644 --- a/src/resolution/import-resolver.ts +++ b/src/resolution/import-resolver.ts @@ -425,6 +425,16 @@ function extractPHPImports(content: string): ImportMapping[] { return mappings; } +// Cache import mappings per file to avoid re-reading and re-parsing +const importMappingCache = new Map(); + +/** + * Clear the import mapping cache (call between indexing runs) + */ +export function clearImportMappingCache(): void { + importMappingCache.clear(); +} + /** * Resolve a reference using import mappings */ @@ -432,14 +442,17 @@ export function resolveViaImport( ref: UnresolvedRef, context: ResolutionContext ): ResolvedRef | null { - // Read the source file to extract imports - const content = context.readFile(ref.filePath); - if (!content) { - return null; + // Use cached import mappings or extract and cache them + let imports = importMappingCache.get(ref.filePath); + if (!imports) { + const content = context.readFile(ref.filePath); + if (!content) { + return null; + } + imports = extractImportMappings(ref.filePath, content, ref.language); + importMappingCache.set(ref.filePath, imports); } - const imports = extractImportMappings(ref.filePath, content, ref.language); - // Check if the reference name matches any import for (const imp of imports) { if (imp.localName === ref.referenceName || ref.referenceName.startsWith(imp.localName + '.')) { diff --git a/src/resolution/index.ts b/src/resolution/index.ts index a051574..f319233 100644 --- a/src/resolution/index.ts +++ b/src/resolution/index.ts @@ -16,8 +16,8 @@ import { ResolutionContext, FrameworkResolver, } from './types'; -import { matchReference } from './name-matcher'; -import { resolveViaImport } from './import-resolver'; +import { matchReference, clearFuzzyIndex } from './name-matcher'; +import { resolveViaImport, clearImportMappingCache } from './import-resolver'; import { detectFrameworks } from './frameworks'; import { logDebug } from '../errors'; @@ -106,6 +106,8 @@ export class ReferenceResolver { this.qualifiedNameCache.clear(); this.kindCache.clear(); this.nodeByIdCache.clear(); + clearImportMappingCache(); + clearFuzzyIndex(); this.cachesWarmed = false; } diff --git a/src/resolution/name-matcher.ts b/src/resolution/name-matcher.ts index 47595bf..ca46414 100644 --- a/src/resolution/name-matcher.ts +++ b/src/resolution/name-matcher.ts @@ -190,6 +190,16 @@ function findBestMatch( return bestNode; } +// Lazily-built case-insensitive index for fuzzy matching +let fuzzyIndex: Map | null = null; + +/** + * Clear the fuzzy match index (call between indexing runs) + */ +export function clearFuzzyIndex(): void { + fuzzyIndex = null; +} + /** * Fuzzy match - last resort with lower confidence */ @@ -197,21 +207,29 @@ export function matchFuzzy( ref: UnresolvedRef, context: ResolutionContext ): ResolvedRef | null { - // Try case-insensitive match - const allNodes = [ - ...context.getNodesByKind('function'), - ...context.getNodesByKind('method'), - ...context.getNodesByKind('class'), - ]; + // Build case-insensitive index on first use + if (!fuzzyIndex) { + fuzzyIndex = new Map(); + const kinds: Array = ['function', 'method', 'class']; + for (const kind of kinds) { + for (const node of context.getNodesByKind(kind)) { + const lower = node.name.toLowerCase(); + const existing = fuzzyIndex.get(lower); + if (existing) { + existing.push(node); + } else { + fuzzyIndex.set(lower, [node]); + } + } + } + } const lowerName = ref.referenceName.toLowerCase(); - // Exact case-insensitive match - const caseInsensitive = allNodes.filter( - (n) => n.name.toLowerCase() === lowerName - ); + // Exact case-insensitive match via index (O(1) lookup) + const caseInsensitive = fuzzyIndex.get(lowerName); - if (caseInsensitive.length === 1) { + if (caseInsensitive && caseInsensitive.length === 1) { return { original: ref, targetNodeId: caseInsensitive[0]!.id, @@ -220,20 +238,7 @@ export function matchFuzzy( }; } - // Try prefix match (e.g., "get" matches "getUser") - const prefixMatches = allNodes.filter((n) => - n.name.toLowerCase().startsWith(lowerName) - ); - - if (prefixMatches.length === 1) { - return { - original: ref, - targetNodeId: prefixMatches[0]!.id, - confidence: 0.3, - resolvedBy: 'fuzzy', - }; - } - + // Skip prefix matching — too expensive and low value (confidence 0.3) return null; }