| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- /**
- * Name Matcher
- *
- * Handles symbol name matching for reference resolution.
- */
- import { Node } from '../types';
- import { UnresolvedRef, ResolvedRef, ResolutionContext } from './types';
- /**
- * Try to resolve a reference by exact name match
- */
- export function matchByExactName(
- ref: UnresolvedRef,
- context: ResolutionContext
- ): ResolvedRef | null {
- const candidates = context.getNodesByName(ref.referenceName);
- if (candidates.length === 0) {
- return null;
- }
- // If only one match, use it
- if (candidates.length === 1) {
- return {
- original: ref,
- targetNodeId: candidates[0]!.id,
- confidence: 0.9,
- resolvedBy: 'exact-match',
- };
- }
- // Multiple matches - try to narrow down
- const bestMatch = findBestMatch(ref, candidates, context);
- if (bestMatch) {
- return {
- original: ref,
- targetNodeId: bestMatch.id,
- confidence: 0.7,
- resolvedBy: 'exact-match',
- };
- }
- return null;
- }
- /**
- * Try to resolve by qualified name
- */
- export function matchByQualifiedName(
- ref: UnresolvedRef,
- context: ResolutionContext
- ): ResolvedRef | null {
- // Check if the reference name looks qualified (contains :: or .)
- if (!ref.referenceName.includes('::') && !ref.referenceName.includes('.')) {
- return null;
- }
- const candidates = context.getNodesByQualifiedName(ref.referenceName);
- if (candidates.length === 1) {
- return {
- original: ref,
- targetNodeId: candidates[0]!.id,
- confidence: 0.95,
- resolvedBy: 'qualified-name',
- };
- }
- // Try partial qualified name match
- const parts = ref.referenceName.split(/[:.]/);
- const lastName = parts[parts.length - 1];
- if (lastName) {
- const partialCandidates = context.getNodesByName(lastName);
- for (const candidate of partialCandidates) {
- if (candidate.qualifiedName.endsWith(ref.referenceName)) {
- return {
- original: ref,
- targetNodeId: candidate.id,
- confidence: 0.85,
- resolvedBy: 'qualified-name',
- };
- }
- }
- }
- return null;
- }
- /**
- * Try to resolve by method name on a class/object
- */
- export function matchMethodCall(
- ref: UnresolvedRef,
- context: ResolutionContext
- ): ResolvedRef | null {
- // Parse method call patterns like "obj.method" or "Class::method"
- const dotMatch = ref.referenceName.match(/^(\w+)\.(\w+)$/);
- const colonMatch = ref.referenceName.match(/^(\w+)::(\w+)$/);
- const match = dotMatch || colonMatch;
- if (!match) {
- return null;
- }
- const [, objectOrClass, methodName] = match;
- // Find the class/object first
- const classCandidates = context.getNodesByName(objectOrClass!);
- for (const classNode of classCandidates) {
- if (classNode.kind === 'class' || classNode.kind === 'struct' || classNode.kind === 'interface') {
- // Look for method in the same file
- const nodesInFile = context.getNodesInFile(classNode.filePath);
- const methodNode = nodesInFile.find(
- (n) =>
- n.kind === 'method' &&
- n.name === methodName &&
- n.qualifiedName.includes(classNode.name)
- );
- if (methodNode) {
- return {
- original: ref,
- targetNodeId: methodNode.id,
- confidence: 0.85,
- resolvedBy: 'qualified-name',
- };
- }
- }
- }
- return null;
- }
- /**
- * Find the best matching node when there are multiple candidates
- */
- function findBestMatch(
- ref: UnresolvedRef,
- candidates: Node[],
- _context: ResolutionContext
- ): Node | null {
- // Prioritization rules:
- // 1. Same file > different file
- // 2. Same language > different language
- // 3. Functions/methods > classes/types (for call references)
- // 4. Exported > non-exported
- let bestScore = -1;
- let bestNode: Node | null = null;
- for (const candidate of candidates) {
- let score = 0;
- // Same file bonus
- if (candidate.filePath === ref.filePath) {
- score += 100;
- }
- // Same language bonus
- if (candidate.language === ref.language) {
- score += 50;
- }
- // For call references, prefer functions/methods
- if (ref.referenceKind === 'calls') {
- if (candidate.kind === 'function' || candidate.kind === 'method') {
- score += 25;
- }
- }
- // Exported bonus
- if (candidate.isExported) {
- score += 10;
- }
- // Closer line number (within same file)
- if (candidate.filePath === ref.filePath && candidate.startLine) {
- const distance = Math.abs(candidate.startLine - ref.line);
- score += Math.max(0, 20 - distance / 10);
- }
- if (score > bestScore) {
- bestScore = score;
- bestNode = candidate;
- }
- }
- return bestNode;
- }
- /**
- * Fuzzy match - last resort with lower confidence
- */
- export function matchFuzzy(
- ref: UnresolvedRef,
- context: ResolutionContext
- ): ResolvedRef | null {
- const lowerName = ref.referenceName.toLowerCase();
- // Use pre-built lowercase index for O(1) lookup instead of scanning all nodes
- const candidates = context.getNodesByLowerName(lowerName);
- // Filter to callable kinds only (function, method, class)
- const callableKinds = new Set(['function', 'method', 'class']);
- const callableCandidates = candidates.filter((n) => callableKinds.has(n.kind));
- if (callableCandidates.length === 1) {
- return {
- original: ref,
- targetNodeId: callableCandidates[0]!.id,
- confidence: 0.5,
- resolvedBy: 'fuzzy',
- };
- }
- return null;
- }
- /**
- * Match all strategies in order of confidence
- */
- export function matchReference(
- ref: UnresolvedRef,
- context: ResolutionContext
- ): ResolvedRef | null {
- // Try strategies in order of confidence
- let result: ResolvedRef | null;
- // 1. Qualified name match (highest confidence)
- result = matchByQualifiedName(ref, context);
- if (result) return result;
- // 2. Method call pattern
- result = matchMethodCall(ref, context);
- if (result) return result;
- // 3. Exact name match
- result = matchByExactName(ref, context);
- if (result) return result;
- // 4. Fuzzy match (lowest confidence)
- result = matchFuzzy(ref, context);
- if (result) return result;
- return null;
- }
|