UNPKG

arela

Version:

AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.

444 lines 15.4 kB
/** * Flow Tracer Module * Traces execution paths through the codebase using AST-like pattern matching * Identifies function calls, data flow, and dependencies */ import fs from 'fs-extra'; import path from 'path'; import { glob } from 'glob'; /** * Trace execution paths from entry points through the codebase */ export async function traceExecutionPaths(cwd, entryPointName) { const startTime = Date.now(); const nodes = new Map(); const visited = new Set(); // Find all TypeScript/JavaScript files const tsFiles = await glob('**/*.{ts,tsx,js,jsx}', { cwd, ignore: ['node_modules/**', 'dist/**', '**/*.test.*', '**/*.spec.*'], }); // First pass: create nodes for all functions for (const file of tsFiles) { const filePath = path.join(cwd, file); try { const content = await fs.readFile(filePath, 'utf-8'); const functions = extractFunctions(content, file); for (const fn of functions) { const nodeId = `${file}:${fn.name}`; nodes.set(nodeId, { id: nodeId, name: fn.name, type: 'function', file, line: fn.line, dependencies: [], calledBy: [], calls: [], asyncOperations: [], dataFlows: [], }); } // Extract imports const imports = extractImports(content, file); for (const imp of imports) { const nodeId = `import:${file}:${imp.name}`; if (!nodes.has(nodeId)) { nodes.set(nodeId, { id: nodeId, name: imp.name, type: 'import', file, line: imp.line, dependencies: [], calledBy: [], calls: [], asyncOperations: [], dataFlows: [], }); } } // Extract variables const variables = extractVariables(content, file); for (const varName of variables) { const nodeId = `var:${file}:${varName}`; if (!nodes.has(nodeId)) { nodes.set(nodeId, { id: nodeId, name: varName, type: 'variable', file, line: 0, dependencies: [], calledBy: [], calls: [], asyncOperations: [], dataFlows: [], }); } } } catch (error) { // Skip files that can't be read } } // Second pass: trace dependencies and calls for (const file of tsFiles) { const filePath = path.join(cwd, file); try { const content = await fs.readFile(filePath, 'utf-8'); // Find function calls const calls = extractFunctionCalls(content, file); for (const call of calls) { // Find the node that makes this call const functions = extractFunctions(content, file); for (const fn of functions) { if (call.line >= fn.line && call.line <= fn.endLine) { const callerNodeId = `${file}:${fn.name}`; const callerNode = nodes.get(callerNodeId); if (callerNode) { callerNode.calls.push(call.name); } // Find the called function const calleeNodeId = `${file}:${call.name}`; const calleeNode = nodes.get(calleeNodeId); if (calleeNode) { calleeNode.calledBy.push(callerNodeId); } } } } // Find async operations const asyncOps = extractAsyncOperations(content, file); for (const asyncOp of asyncOps) { const functions = extractFunctions(content, file); for (const fn of functions) { if (asyncOp.line >= fn.line && asyncOp.line <= fn.endLine) { const nodeId = `${file}:${fn.name}`; const node = nodes.get(nodeId); if (node) { node.asyncOperations.push(asyncOp.operation); } } } } // Find data flows const dataFlows = extractDataFlows(content, file); for (const flow of dataFlows) { const functions = extractFunctions(content, file); for (const fn of functions) { if (flow.line >= fn.line && flow.line <= fn.endLine) { const nodeId = `${file}:${fn.name}`; const node = nodes.get(nodeId); if (node) { node.dataFlows.push({ from: flow.from, to: flow.to, type: flow.type, }); } } } } } catch (error) { // Skip files that can't be read } } // Generate execution paths from entry point const executionPaths = []; const pathsTraced = tracePathsFromNode(`Unknown:${entryPointName}`, nodes, visited, []); for (const tracePath of pathsTraced) { if (tracePath.length > 1) { executionPaths.push({ startPoint: tracePath[0], endPoint: tracePath[tracePath.length - 1], path: tracePath, depth: tracePath.length, hasAsyncCalls: tracePath.some(nodeId => { const node = nodes.get(nodeId); return node && node.asyncOperations.length > 0; }), potentialIssues: detectIssuesInPath(tracePath, nodes), }); } } return { nodes: Array.from(nodes.values()), executionPaths, tracingDuration: Date.now() - startTime, }; } /** * Extract all function definitions from code */ function extractFunctions(content, filePath) { const functions = []; const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Function declarations const fnMatch = line.match(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/); if (fnMatch) { functions.push({ name: fnMatch[1], line: i + 1, endLine: findBlockEnd(lines, i), }); } // Arrow functions (const name = ...) const arrowMatch = line.match(/^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(.*?\)\s*=>/); if (arrowMatch) { functions.push({ name: arrowMatch[1], line: i + 1, endLine: findBlockEnd(lines, i), }); } // Class methods const methodMatch = line.match(/^\s+(?:async\s+)?(\w+)\s*\([^)]*\)\s*[:{]/); if (methodMatch && i > 0 && lines[i - 1].includes('class')) { functions.push({ name: methodMatch[1], line: i + 1, endLine: findBlockEnd(lines, i), }); } } return functions; } /** * Extract all import statements */ function extractImports(content, filePath) { const imports = []; const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; // import { name } from '...' const namedMatch = line.match(/import\s+\{\s*([^}]+)\s*\}\s+from/); if (namedMatch) { const names = namedMatch[1].split(',').map(n => n.trim()); for (const name of names) { imports.push({ name, line: i + 1 }); } } // import * as name from '...' const allMatch = line.match(/import\s+\*\s+as\s+(\w+)\s+from/); if (allMatch) { imports.push({ name: allMatch[1], line: i + 1 }); } // import name from '...' const defaultMatch = line.match(/import\s+(\w+)\s+from/); if (defaultMatch && !line.includes('{')) { imports.push({ name: defaultMatch[1], line: i + 1 }); } } return imports; } /** * Extract variable declarations */ function extractVariables(content, filePath) { const variables = new Set(); const lines = content.split('\n'); for (const line of lines) { // const/let/var declarations const match = line.match(/(?:const|let|var)\s+(\w+)\s*[:=]/); if (match) { variables.add(match[1]); } // Function parameters const paramMatch = line.match(/\(([^)]+)\)/); if (paramMatch) { const params = paramMatch[1].split(',').map(p => { const nameMatch = p.match(/(\w+)\s*[:=]/); return nameMatch ? nameMatch[1] : null; }); for (const param of params) { if (param) variables.add(param); } } } return Array.from(variables); } /** * Extract function calls within the code */ function extractFunctionCalls(content, filePath) { const calls = []; const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Function calls pattern: name() const callPattern = /\b(\w+)\s*\(/g; let match; while ((match = callPattern.exec(line)) !== null) { const name = match[1]; // Skip keywords if (!/^(if|for|while|switch|function|class|return|throw|typeof|instanceof|new|delete|void|await|async)$/.test(name)) { calls.push({ name, line: i + 1 }); } } } return calls; } /** * Extract async operations (await, Promise, etc.) */ function extractAsyncOperations(content, filePath) { const operations = []; const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; // await operations if (line.includes('await ')) { const awaitMatch = line.match(/await\s+(\w+)/); if (awaitMatch) { operations.push({ operation: `await ${awaitMatch[1]}`, line: i + 1, }); } } // Promise operations if (line.includes('Promise')) { operations.push({ operation: 'Promise', line: i + 1, }); } // setTimeout/setInterval if (line.match(/(?:setTimeout|setInterval|setImmediate)/)) { operations.push({ operation: 'async_timer', line: i + 1, }); } } return operations; } /** * Extract data flows (variable assignments, returns, etc.) */ function extractDataFlows(content, filePath) { const flows = []; const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Variable assignments const assignMatch = line.match(/(?:const|let|var|this\.)\s*(\w+)\s*=\s*(.+?)(?:;|$)/); if (assignMatch) { flows.push({ from: assignMatch[2].trim(), to: assignMatch[1], type: 'assignment', line: i + 1, }); } // Return statements const returnMatch = line.match(/return\s+(.+?)(?:;|$)/); if (returnMatch) { flows.push({ from: returnMatch[1].trim(), to: '_return', type: 'return', line: i + 1, }); } } return flows; } /** * Find the end line of a code block */ function findBlockEnd(lines, startLine) { let braceCount = 0; let parenCount = 0; let foundStart = false; for (let i = startLine; i < lines.length && i < startLine + 500; i++) { const line = lines[i]; for (const char of line) { if (char === '{') braceCount++; if (char === '}') { braceCount--; if (foundStart && braceCount === 0) return i + 1; } if (char === '(') parenCount++; if (char === ')') parenCount--; if (char === '{') foundStart = true; } } return startLine + 50; } /** * Trace execution paths from a starting node */ function tracePathsFromNode(nodeId, nodes, visited, currentPath, maxDepth = 10) { if (visited.has(nodeId) || currentPath.length > maxDepth) { return [currentPath]; } visited.add(nodeId); const paths = []; const node = nodes.get(nodeId); if (!node) { return [currentPath]; } currentPath.push(nodeId); if (node.calls.length === 0) { paths.push(currentPath); } else { for (const callName of node.calls) { // Try to find the called function for (const [key] of nodes) { if (key.endsWith(`:${callName}`) || key.includes(callName)) { const subPaths = tracePathsFromNode(key, nodes, new Set(visited), [...currentPath], maxDepth); paths.push(...subPaths); break; } } } } return paths; } /** * Detect potential issues in an execution path */ function detectIssuesInPath(path, nodes) { const issues = []; // Check for circular dependencies if (new Set(path).size < path.length) { issues.push('Potential circular dependency detected'); } // Check for unhandled async operations const hasAsync = path.some(nodeId => { const node = nodes.get(nodeId); return node && node.asyncOperations.length > 0; }); if (hasAsync) { // Check if all async operations are awaited for (const nodeId of path) { const node = nodes.get(nodeId); if (node && node.asyncOperations.length > 0) { issues.push(`Async operation in ${node.name} - ensure proper error handling`); } } } // Check for missing error handling const hasPromises = path.some(nodeId => { const node = nodes.get(nodeId); return node && node.asyncOperations.some(op => op.includes('Promise')); }); if (hasPromises) { issues.push('Promise detected - add .catch() or try/catch for error handling'); } return issues; } //# sourceMappingURL=tracer.js.map