| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- /**
- * Reference Resolution Orchestrator
- *
- * Coordinates all reference resolution strategies.
- */
- import * as fs from 'fs';
- import * as path from 'path';
- import { Node, UnresolvedReference, Edge } from '../types';
- import { QueryBuilder } from '../db/queries';
- import {
- UnresolvedRef,
- ResolvedRef,
- ResolutionResult,
- ResolutionContext,
- FrameworkResolver,
- } from './types';
- import { matchReference } from './name-matcher';
- import { resolveViaImport } from './import-resolver';
- import { detectFrameworks } from './frameworks';
- import { logDebug } from '../errors';
- // Re-export types
- export * from './types';
- /**
- * Reference Resolver
- *
- * Orchestrates reference resolution using multiple strategies.
- */
- export class ReferenceResolver {
- private projectRoot: string;
- private queries: QueryBuilder;
- private context: ResolutionContext;
- private frameworks: FrameworkResolver[] = [];
- private nodeCache: Map<string, Node[]> = new Map();
- private fileCache: Map<string, string | null> = new Map();
- constructor(projectRoot: string, queries: QueryBuilder) {
- this.projectRoot = projectRoot;
- this.queries = queries;
- this.context = this.createContext();
- }
- /**
- * Initialize the resolver (detect frameworks, etc.)
- */
- initialize(): void {
- this.frameworks = detectFrameworks(this.context);
- this.clearCaches();
- }
- /**
- * Clear internal caches
- */
- clearCaches(): void {
- this.nodeCache.clear();
- this.fileCache.clear();
- }
- /**
- * Create the resolution context
- */
- private createContext(): ResolutionContext {
- return {
- getNodesInFile: (filePath: string) => {
- if (!this.nodeCache.has(filePath)) {
- this.nodeCache.set(filePath, this.queries.getNodesByFile(filePath));
- }
- return this.nodeCache.get(filePath)!;
- },
- getNodesByName: (name: string) => {
- return this.queries.searchNodes(name, { limit: 100 }).map((r) => r.node);
- },
- getNodesByQualifiedName: (qualifiedName: string) => {
- // Search for exact qualified name match
- return this.queries
- .searchNodes(qualifiedName, { limit: 50 })
- .filter((r) => r.node.qualifiedName === qualifiedName)
- .map((r) => r.node);
- },
- getNodesByKind: (kind: Node['kind']) => {
- return this.queries.getNodesByKind(kind);
- },
- fileExists: (filePath: string) => {
- const fullPath = path.join(this.projectRoot, filePath);
- try {
- return fs.existsSync(fullPath);
- } catch (error) {
- logDebug('Error checking file existence', { filePath, error: String(error) });
- return false;
- }
- },
- readFile: (filePath: string) => {
- if (this.fileCache.has(filePath)) {
- return this.fileCache.get(filePath)!;
- }
- const fullPath = path.join(this.projectRoot, filePath);
- try {
- const content = fs.readFileSync(fullPath, 'utf-8');
- this.fileCache.set(filePath, content);
- return content;
- } catch (error) {
- logDebug('Failed to read file for resolution', { filePath, error: String(error) });
- this.fileCache.set(filePath, null);
- return null;
- }
- },
- getProjectRoot: () => this.projectRoot,
- getAllFiles: () => {
- return this.queries.getAllFiles().map((f) => f.path);
- },
- };
- }
- /**
- * Resolve all unresolved references
- */
- resolveAll(unresolvedRefs: UnresolvedReference[]): ResolutionResult {
- const resolved: ResolvedRef[] = [];
- const unresolved: UnresolvedRef[] = [];
- const byMethod: Record<string, number> = {};
- // Convert to our internal format
- const refs: UnresolvedRef[] = unresolvedRefs.map((ref) => ({
- fromNodeId: ref.fromNodeId,
- referenceName: ref.referenceName,
- referenceKind: ref.referenceKind,
- line: ref.line,
- column: ref.column,
- filePath: this.getFilePathFromNodeId(ref.fromNodeId),
- language: this.getLanguageFromNodeId(ref.fromNodeId),
- }));
- for (const ref of refs) {
- const result = this.resolveOne(ref);
- if (result) {
- resolved.push(result);
- byMethod[result.resolvedBy] = (byMethod[result.resolvedBy] || 0) + 1;
- } else {
- unresolved.push(ref);
- }
- }
- return {
- resolved,
- unresolved,
- stats: {
- total: refs.length,
- resolved: resolved.length,
- unresolved: unresolved.length,
- byMethod,
- },
- };
- }
- /**
- * Resolve a single reference
- */
- resolveOne(ref: UnresolvedRef): ResolvedRef | null {
- // Skip built-in/external references
- if (this.isBuiltInOrExternal(ref)) {
- return null;
- }
- // Strategy 1: Try framework-specific resolution first
- for (const framework of this.frameworks) {
- const result = framework.resolve(ref, this.context);
- if (result) {
- return result;
- }
- }
- // Strategy 2: Try import-based resolution
- const importResult = resolveViaImport(ref, this.context);
- if (importResult) {
- return importResult;
- }
- // Strategy 3: Try name matching
- const nameResult = matchReference(ref, this.context);
- if (nameResult) {
- return nameResult;
- }
- return null;
- }
- /**
- * Create edges from resolved references
- */
- createEdges(resolved: ResolvedRef[]): Edge[] {
- return resolved.map((ref) => ({
- source: ref.original.fromNodeId,
- target: ref.targetNodeId,
- kind: ref.original.referenceKind,
- line: ref.original.line,
- column: ref.original.column,
- metadata: {
- confidence: ref.confidence,
- resolvedBy: ref.resolvedBy,
- },
- }));
- }
- /**
- * Resolve and persist edges to database
- */
- resolveAndPersist(unresolvedRefs: UnresolvedReference[]): ResolutionResult {
- const result = this.resolveAll(unresolvedRefs);
- // Create edges from resolved references
- const edges = this.createEdges(result.resolved);
- // Insert edges into database
- if (edges.length > 0) {
- this.queries.insertEdges(edges);
- }
- return result;
- }
- /**
- * Get detected frameworks
- */
- getDetectedFrameworks(): string[] {
- return this.frameworks.map((f) => f.name);
- }
- /**
- * Check if reference is to a built-in or external symbol
- */
- private isBuiltInOrExternal(ref: UnresolvedRef): boolean {
- const name = ref.referenceName;
- // JavaScript/TypeScript built-ins
- const jsBuiltIns = [
- 'console', 'window', 'document', 'global', 'process',
- 'Promise', 'Array', 'Object', 'String', 'Number', 'Boolean',
- 'Date', 'Math', 'JSON', 'RegExp', 'Error', 'Map', 'Set',
- 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
- 'fetch', 'require', 'module', 'exports', '__dirname', '__filename',
- ];
- if (jsBuiltIns.includes(name)) {
- return true;
- }
- // Common library calls
- if (name.startsWith('console.') || name.startsWith('Math.') || name.startsWith('JSON.')) {
- return true;
- }
- // React hooks from React itself
- const reactHooks = ['useState', 'useEffect', 'useContext', 'useReducer', 'useCallback', 'useMemo', 'useRef', 'useLayoutEffect', 'useImperativeHandle', 'useDebugValue'];
- if (reactHooks.includes(name)) {
- return true;
- }
- // Python built-ins
- const pythonBuiltIns = [
- 'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple',
- 'open', 'input', 'type', 'isinstance', 'hasattr', 'getattr', 'setattr',
- 'super', 'self', 'cls', 'None', 'True', 'False',
- ];
- if (ref.language === 'python' && pythonBuiltIns.includes(name)) {
- return true;
- }
- return false;
- }
- /**
- * Get file path from node ID
- */
- private getFilePathFromNodeId(nodeId: string): string {
- const node = this.queries.getNodeById(nodeId);
- return node?.filePath || '';
- }
- /**
- * Get language from node ID
- */
- private getLanguageFromNodeId(nodeId: string): UnresolvedRef['language'] {
- const node = this.queries.getNodeById(nodeId);
- return node?.language || 'unknown';
- }
- }
- /**
- * Create a reference resolver instance
- */
- export function createResolver(projectRoot: string, queries: QueryBuilder): ReferenceResolver {
- const resolver = new ReferenceResolver(projectRoot, queries);
- resolver.initialize();
- return resolver;
- }
|