diff --git a/src/core/ingestion/call-processor.ts b/src/core/ingestion/call-processor.ts index 77e07a6c1..5ce4eb10f 100644 --- a/src/core/ingestion/call-processor.ts +++ b/src/core/ingestion/call-processor.ts @@ -173,8 +173,22 @@ export class CallProcessor { if (node.label === 'Function' || node.label === 'Method') { const filePath = node.properties.filePath as string; const functionName = node.properties.name as string; - const key = `${filePath}:${functionName}`; - this.functionNodes.set(key, node); + + // Create different keys for Functions vs Methods to avoid conflicts + if (node.label === 'Method') { + const parentClass = node.properties.parentClass as string; + const methodKey = `${filePath}:method:${parentClass}.${functionName}`; + this.functionNodes.set(methodKey, node); + + // Also add a general method key for resolution when class context is unknown + const generalMethodKey = `${filePath}:method:${functionName}`; + if (!this.functionNodes.has(generalMethodKey)) { + this.functionNodes.set(generalMethodKey, node); + } + } else { + const functionKey = `${filePath}:function:${functionName}`; + this.functionNodes.set(functionKey, node); + } } } } @@ -330,18 +344,26 @@ export class CallProcessor { const callerNode = this.findCallerNode(graph, call); if (!callerNode) return; - // Try different resolution strategies + // Improved resolution order: prioritize imports to avoid self-referential calls const targetNode = this.resolveBuiltinFunction(call) || - this.resolveImportedFunction(call) || - this.resolveLocalFunction(call) || - this.resolveMethodCall(graph, call); + this.resolveImportedFunction(call) || // Check imports first + this.resolveMethodCall(graph, call) || + this.resolveLocalFunction(call); // Check local functions last if (targetNode) { - // Ensure the target node exists in the graph before creating relationship + // Prevent self-referential calls unless it's actually recursive + if (callerNode.id === targetNode.id) { + // Only allow self-calls if the function name matches exactly (true recursion) + const callerName = callerNode.properties.name as string; + if (callerName !== call.calledName) { + console.warn(`Prevented incorrect self-referential call from ${callerName} to ${call.calledName}`); + return; + } + } + const existingNode = graph.nodes.find(node => node.id === targetNode.id); if (!existingNode) { - // Add the node to the graph if it doesn't exist graph.nodes.push(targetNode); } @@ -375,15 +397,22 @@ export class CallProcessor { const imports = this.importCache.get(call.callerFilePath) || []; for (const importInfo of imports) { - // Check direct import match + // Handle direct import match (from module import function) if (importInfo.importedName === call.calledName || importInfo.alias === call.calledName) { - // For now, create a placeholder node for imported functions - // In a full implementation, this would resolve to actual function nodes return this.getOrCreateImportedNode(call.calledName, importInfo.fromModule); } - // Check module.function pattern - if (call.callType === 'method' && call.objectName === importInfo.importedName) { + // Handle module.function pattern (import module; module.function()) + if (call.callType === 'method' && call.objectName) { + // Check if objectName matches imported module or alias + if (importInfo.importedName === call.objectName || importInfo.alias === call.objectName) { + // This is a call to an imported module's function + return this.getOrCreateImportedNode(call.calledName, importInfo.fromModule); + } + } + + // Handle import module as alias patterns + if (call.callType === 'method' && call.objectName === importInfo.alias && importInfo.importType === 'module') { return this.getOrCreateImportedNode(call.calledName, importInfo.fromModule); } } @@ -392,21 +421,35 @@ export class CallProcessor { } private resolveLocalFunction(call: FunctionCall): GraphNode | null { - const key = `${call.callerFilePath}:${call.calledName}`; - return this.functionNodes.get(key) || null; + // Try function key first + const functionKey = `${call.callerFilePath}:function:${call.calledName}`; + const functionNode = this.functionNodes.get(functionKey); + if (functionNode) { + return functionNode; + } + + // Try method key if function not found + const methodKey = `${call.callerFilePath}:method:${call.calledName}`; + return this.functionNodes.get(methodKey) || null; } private resolveMethodCall(graph: KnowledgeGraph, call: FunctionCall): GraphNode | null { if (call.callType !== 'method') return null; - // Look for method in the same file + // First try to find methods in the same file using the new key format + const methodKey = `${call.callerFilePath}:method:${call.calledName}`; + const methodNode = this.functionNodes.get(methodKey); + if (methodNode) { + return methodNode; + } + + // Fallback: look for method nodes in the graph (for cases where key lookup fails) const methods = graph.nodes.filter(node => node.label === 'Method' && node.properties.filePath === call.callerFilePath && node.properties.name === call.calledName ); - // Return first matching method (could be refined with class context) return methods[0] || null; } @@ -530,3 +573,5 @@ export class CallProcessor { } } } + + diff --git a/src/core/ingestion/parsing-processor.ts b/src/core/ingestion/parsing-processor.ts index 824db388f..00a4269eb 100644 --- a/src/core/ingestion/parsing-processor.ts +++ b/src/core/ingestion/parsing-processor.ts @@ -296,17 +296,28 @@ export class ParsingProcessor { } try { - // Parse the file content using tree-sitter const tree = this.parser.parse(fileContent); - // Cache the AST for later use this.astCache.set(filePath, tree); const definitions: ParsedDefinition[] = []; const rootNode = tree.rootNode; + const processedMethodNodes = new Set(); + // First pass: identify all methods inside classes this.traverseNode(rootNode, (currentNode: Parser.SyntaxNode) => { - if (currentNode.type === 'function_definition') { + if (currentNode.type === 'class_definition') { + this.traverseNode(currentNode, (methodNode: Parser.SyntaxNode) => { + if (methodNode.type === 'function_definition' && methodNode !== currentNode) { + processedMethodNodes.add(methodNode); + } + }); + } + }); + + // Second pass: process all definitions, skipping methods that will be handled as class methods + this.traverseNode(rootNode, (currentNode: Parser.SyntaxNode) => { + if (currentNode.type === 'function_definition' && !processedMethodNodes.has(currentNode)) { const nameNode = currentNode.childForFieldName('name'); if (nameNode) { definitions.push({ @@ -327,7 +338,6 @@ export class ParsingProcessor { endLine: currentNode.endPosition.row + 1 }); - // Extract methods from this class const methods = this.extractMethodsFromClass(currentNode, className); definitions.push(...methods); } diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo index 2e0d2dcc8..af38010c2 100644 --- a/tsconfig.app.tsbuildinfo +++ b/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/ai/cypher-generator.ts","./src/ai/index.ts","./src/ai/langchain-orchestrator.ts","./src/ai/llm-service.ts","./src/ai/orchestrator.ts","./src/core/graph/types.ts","./src/core/ingestion/call-processor.ts","./src/core/ingestion/parsing-processor.ts","./src/core/ingestion/pipeline.ts","./src/core/ingestion/structure-processor.ts","./src/core/tree-sitter/parser-loader.ts","./src/lib/export.ts","./src/lib/polyfills.ts","./src/lib/utils.ts","./src/lib/workerutils.ts","./src/services/github.ts","./src/services/zip.ts","./src/ui/index.ts","./src/ui/components/errorboundary.tsx","./src/ui/components/index.ts","./src/ui/components/chat/chatinterface.tsx","./src/ui/components/chat/codeassistant.tsx","./src/ui/components/chat/index.ts","./src/ui/components/graph/graphexplorer.tsx","./src/ui/components/graph/sourceviewer.tsx","./src/ui/components/graph/visualization.tsx","./src/ui/components/graph/index.ts","./src/ui/pages/homepage.tsx","./src/ui/pages/index.ts","./src/workers/ingestion.worker.ts"],"errors":true,"version":"5.8.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/ai/cypher-generator.ts","./src/ai/index.ts","./src/ai/langchain-orchestrator.ts","./src/ai/llm-service.ts","./src/ai/orchestrator.ts","./src/core/graph/types.ts","./src/core/ingestion/call-processor.ts","./src/core/ingestion/parsing-processor.ts","./src/core/ingestion/pipeline.ts","./src/core/ingestion/structure-processor.ts","./src/core/tree-sitter/parser-loader.ts","./src/lib/export.ts","./src/lib/polyfills.ts","./src/lib/preload.ts","./src/lib/utils.ts","./src/lib/workerutils.ts","./src/services/github.ts","./src/services/ingestion.service.ts","./src/services/zip.ts","./src/ui/index.ts","./src/ui/components/errorboundary.tsx","./src/ui/components/index.ts","./src/ui/components/chat/chatinterface.tsx","./src/ui/components/chat/codeassistant.tsx","./src/ui/components/chat/index.ts","./src/ui/components/graph/graphexplorer.tsx","./src/ui/components/graph/sourceviewer.tsx","./src/ui/components/graph/visualization.tsx","./src/ui/components/graph/index.ts","./src/ui/pages/homepage.tsx","./src/ui/pages/index.ts","./src/workers/ingestion.worker.ts"],"errors":true,"version":"5.8.3"} \ No newline at end of file