UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

277 lines 10.7 kB
/** * AST Analyzer for Code Analysis * * Analyzes Abstract Syntax Trees for code understanding * and intelligent routing decisions. * * @module ast-analyzer */ const DEFAULT_CONFIG = { maxFileSize: 1024 * 1024, languages: ['typescript', 'javascript', 'python', 'rust', 'go'], includeComments: true, extractTypes: true, maxDepth: 20, }; const LANGUAGE_PATTERNS = { typescript: [/\.tsx?$/, /^import\s+.*from\s+['"]/, /:\s*(string|number|boolean|void)/], javascript: [/\.jsx?$/, /^(const|let|var)\s+\w+\s*=/, /module\.exports/], python: [/\.py$/, /^(def|class|import|from)\s+/, /^#!/], rust: [/\.rs$/, /^(fn|struct|impl|use)\s+/, /^(pub\s+)?mod\s+/], go: [/\.go$/, /^package\s+/, /^func\s+/], }; export class ASTAnalyzer { config; ruvectorEngine = null; useNative = false; analysisCache = new Map(); constructor(config = {}) { this.config = { ...DEFAULT_CONFIG, ...config }; } async initialize() { try { // @ruvector/ast is optional - gracefully fallback if not installed const ruvector = await import('@ruvector/ast').catch(() => null); if (ruvector) { this.ruvectorEngine = ruvector.createASTAnalyzer?.(this.config); this.useNative = !!this.ruvectorEngine; } } catch { this.useNative = false; } } analyze(code, filePath = 'unknown') { const startTime = performance.now(); if (code.length > this.config.maxFileSize) { throw new Error(`File too large: ${code.length} bytes exceeds ${this.config.maxFileSize}`); } const cacheKey = this.getCacheKey(code, filePath); const cached = this.analysisCache.get(cacheKey); if (cached) return cached; const language = this.detectLanguage(code, filePath); const root = this.parseAST(code, language); const functions = this.extractFunctions(root); const classes = this.extractClasses(root); const imports = this.extractImports(code, language); const exports = this.extractExports(code, language); const complexity = this.calculateComplexity(code, root); const durationMs = performance.now() - startTime; const analysis = { filePath, language, root, functions, classes, imports, exports, complexity, timestamp: Date.now(), durationMs, }; this.analysisCache.set(cacheKey, analysis); return analysis; } getFunctionAtLine(analysis, line) { for (const func of analysis.functions) { if (line >= func.startLine && line <= func.endLine) return func; } return null; } getClassAtLine(analysis, line) { for (const cls of analysis.classes) { if (line >= cls.startLine && line <= cls.endLine) return cls; } return null; } getSymbols(analysis) { const symbols = []; for (const func of analysis.functions) symbols.push(func.name); for (const cls of analysis.classes) symbols.push(cls.name); return symbols; } getStats() { return { cacheSize: this.analysisCache.size, useNative: this.useNative ? 1 : 0 }; } clearCache() { this.analysisCache.clear(); } getCacheKey(code, filePath) { let hash = 0; const str = filePath + code.substring(0, 1000); for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } return `ast_${hash}_${code.length}`; } detectLanguage(code, filePath) { for (const [lang, patterns] of Object.entries(LANGUAGE_PATTERNS)) { for (const pattern of patterns) { if (pattern.test(filePath) || pattern.test(code)) return lang; } } return 'unknown'; } parseAST(code, language) { const lines = code.split('\n'); const root = { type: 'program', name: 'root', startLine: 1, endLine: lines.length, children: [] }; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const lineNum = i + 1; const funcMatch = this.matchFunction(line, language); if (funcMatch) { const funcNode = { type: 'function', name: funcMatch.name, startLine: lineNum, endLine: lineNum + this.findBlockEnd(lines, i), children: [], metadata: { params: funcMatch.params }, }; root.children.push(funcNode); continue; } const classMatch = this.matchClass(line, language); if (classMatch) { const classNode = { type: 'class', name: classMatch.name, startLine: lineNum, endLine: lineNum + this.findBlockEnd(lines, i), children: [], metadata: { extends: classMatch.extends }, }; root.children.push(classNode); } } return root; } matchFunction(line, language) { const patterns = { typescript: /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/, javascript: /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/, python: /def\s+(\w+)\s*\(([^)]*)\)/, rust: /(?:pub\s+)?(?:async\s+)?fn\s+(\w+)\s*\(([^)]*)\)/, go: /func\s+(?:\([^)]*\)\s+)?(\w+)\s*\(([^)]*)\)/, }; const pattern = patterns[language]; if (!pattern) return null; const match = line.match(pattern); if (match) return { name: match[1], params: match[2] || '' }; const arrowMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/); if (arrowMatch) return { name: arrowMatch[1], params: '' }; const methodMatch = line.match(/^\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*\w+)?\s*\{/); if (methodMatch && methodMatch[1] !== 'if' && methodMatch[1] !== 'while' && methodMatch[1] !== 'for') { return { name: methodMatch[1], params: '' }; } return null; } matchClass(line, language) { const patterns = { typescript: /(?:export\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?/, javascript: /(?:export\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?/, python: /class\s+(\w+)(?:\((\w+)\))?/, rust: /(?:pub\s+)?struct\s+(\w+)/, go: /type\s+(\w+)\s+struct/, }; const pattern = patterns[language]; if (!pattern) return null; const match = line.match(pattern); if (match) return { name: match[1], extends: match[2] }; return null; } findBlockEnd(lines, startIdx) { let depth = 0; let inBlock = false; for (let i = startIdx; i < lines.length; i++) { const line = lines[i]; const opens = (line.match(/[\{]/g) || []).length; const closes = (line.match(/[\}]/g) || []).length; if (opens > 0) inBlock = true; depth += opens - closes; if (inBlock && depth <= 0) return i - startIdx; } return Math.min(50, lines.length - startIdx); } extractFunctions(root) { const functions = []; const visit = (node) => { if (node.type === 'function') functions.push(node); for (const child of node.children) visit(child); }; visit(root); return functions; } extractClasses(root) { const classes = []; const visit = (node) => { if (node.type === 'class') classes.push(node); for (const child of node.children) visit(child); }; visit(root); return classes; } extractImports(code, language) { const imports = []; const lines = code.split('\n'); const patterns = { typescript: /import\s+(?:.*\s+from\s+)?['"]([^'"]+)['"]/, javascript: /(?:import\s+.*from\s+|require\s*\(\s*)['"]([^'"]+)['"]/, python: /(?:from\s+(\S+)\s+import|import\s+(\S+))/, rust: /use\s+(\S+)/, go: /import\s+(?:\(\s*)?["']([^"']+)["']/, }; const pattern = patterns[language]; if (!pattern) return imports; for (const line of lines) { const match = line.match(pattern); if (match) imports.push(match[1] || match[2]); } return imports; } extractExports(code, language) { const exports = []; const lines = code.split('\n'); for (const line of lines) { const exportMatch = line.match(/export\s+(?:default\s+)?(?:const|let|var|function|class|interface|type|enum)\s+(\w+)/); if (exportMatch) exports.push(exportMatch[1]); const namedExportMatch = line.match(/export\s*\{\s*([^}]+)\s*\}/); if (namedExportMatch) { const names = namedExportMatch[1].split(',').map(n => n.trim().split(/\s+as\s+/)[0]); exports.push(...names); } } return exports; } calculateComplexity(code, root) { const lines = code.split('\n'); const nonEmptyLines = lines.filter(l => l.trim().length > 0); const commentLines = lines.filter(l => /^\s*(\/\/|\/\*|\*|#)/.test(l)); const decisionPoints = (code.match(/\b(if|else|for|while|switch|case|catch|&&|\|\||\?)\b/g) || []).length; const cyclomatic = decisionPoints + 1; let cognitive = 0; let nestingLevel = 0; for (const line of lines) { const opens = (line.match(/[\{]/g) || []).length; const closes = (line.match(/[\}]/g) || []).length; if (/\b(if|for|while|switch)\b/.test(line)) cognitive += 1 + nestingLevel; nestingLevel += opens - closes; nestingLevel = Math.max(0, nestingLevel); } return { cyclomatic, cognitive, loc: nonEmptyLines.length, commentDensity: lines.length > 0 ? commentLines.length / lines.length : 0, }; } } export function createASTAnalyzer(config) { return new ASTAnalyzer(config); } //# sourceMappingURL=ast-analyzer.js.map