UNPKG

mira-consciousness

Version:

Memory & Intelligence Retention Archive - Preserving The Spark

227 lines 9.1 kB
/** * Code Complexity Analyzer * Measures cyclomatic and cognitive complexity of functions */ import fs from 'fs-extra'; import * as path from 'path'; import { glob } from 'glob'; export class ComplexityAnalyzer { projectRoot; constructor(projectRoot) { this.projectRoot = projectRoot; } async analyze(issues) { try { const files = await glob('**/*.{ts,js,jsx,tsx}', { cwd: this.projectRoot, ignore: ['node_modules/**', '.git/**', 'dist/**', '**/*.test.*', '**/*.spec.*'] }); let totalFunctions = 0; let totalComplexity = 0; const highComplexityFunctions = []; for (const file of files) { const filePath = path.join(this.projectRoot, file); try { const content = await fs.readFile(filePath, 'utf-8'); const functions = this.extractFunctions(content); for (const func of functions) { const complexity = this.calculateCyclomaticComplexity(func.body); totalFunctions++; totalComplexity += complexity; if (complexity > 10) { highComplexityFunctions.push({ file, function: func.name, complexity, line: func.line }); issues.push({ file, line: func.line, type: 'high_complexity', message: `Function "${func.name}" has high complexity (${complexity})`, impact: complexity > 20 ? 'high' : 'medium', recommendation: 'Consider breaking down into smaller functions' }); } } } catch (error) { // Skip files that can't be read } } const averageComplexity = totalFunctions > 0 ? totalComplexity / totalFunctions : 0; return { totalFunctions, averageComplexity, highComplexityFunctions: highComplexityFunctions .sort((a, b) => b.complexity - a.complexity) .slice(0, 20), cognitiveComplexityScore: this.calculateCognitiveComplexity(highComplexityFunctions) }; } catch (error) { return undefined; } } extractFunctions(content) { const functions = []; const lines = content.split('\n'); // Patterns to match different function declarations const functionPatterns = [ // Standard function declaration /^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)/, // Method in object /^\s*(\w+)\s*:\s*(?:async\s+)?function/, // Arrow function assigned to variable /^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=]+)\s*=>/, // Class method /^\s*(?:static\s+)?(?:async\s+)?(\w+)\s*\(/, // Object method shorthand /^\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*{/ ]; for (let i = 0; i < lines.length; i++) { const line = lines[i]; for (const pattern of functionPatterns) { const match = line.match(pattern); if (match) { const functionName = match[1] || 'anonymous'; const { body, endLine } = this.extractFunctionBody(lines, i); if (body) { functions.push({ name: functionName, body, line: i + 1 }); // Skip to end of function to avoid duplicate detection i = endLine; break; } } } } return functions; } extractFunctionBody(lines, startLine) { let braceCount = 0; let parenCount = 0; let inString = false; let stringChar = ''; let body = ''; let foundStart = false; for (let i = startLine; i < lines.length; i++) { const line = lines[i]; body += line + '\n'; // Track string literals to avoid counting braces inside strings for (let j = 0; j < line.length; j++) { const char = line[j]; const prevChar = j > 0 ? line[j - 1] : ''; if (!inString && (char === '"' || char === "'" || char === '`')) { inString = true; stringChar = char; } else if (inString && char === stringChar && prevChar !== '\\') { inString = false; } if (!inString) { if (char === '(') parenCount++; if (char === ')') parenCount--; if (char === '{') { braceCount++; foundStart = true; } if (char === '}') braceCount--; } } // Check if we've found the complete function if (foundStart && braceCount === 0) { return { body, endLine: i }; } // Handle arrow functions without braces if (!foundStart && parenCount === 0 && line.includes('=>')) { // Single line arrow function if (!line.includes('{')) { return { body: line, endLine: i }; } } } return { body, endLine: lines.length - 1 }; } calculateCyclomaticComplexity(code) { let complexity = 1; // Base complexity // Remove comments and strings to avoid false positives const cleanedCode = this.removeCommentsAndStrings(code); // Count decision points const decisionPatterns = [ /\bif\b/g, /\belse\s+if\b/g, /\bwhile\b/g, /\bfor\b/g, /\bdo\b/g, /\bcase\b/g, /\bcatch\b/g, /\b\?\s*[^:]+:/g, // Ternary operator /\b&&\b/g, // Logical AND /\b\|\|\b/g, // Logical OR /\b\?\?\b/g, // Nullish coalescing /\?\./g // Optional chaining (adds complexity) ]; for (const pattern of decisionPatterns) { const matches = cleanedCode.match(pattern); if (matches) { complexity += matches.length; } } // Additional complexity for nested structures const nestingLevel = this.calculateNestingLevel(cleanedCode); if (nestingLevel > 3) { complexity += nestingLevel - 3; } return complexity; } removeCommentsAndStrings(code) { // Remove single-line comments code = code.replace(/\/\/.*$/gm, ''); // Remove multi-line comments code = code.replace(/\/\*[\s\S]*?\*\//g, ''); // Remove string literals (basic approach) code = code.replace(/"[^"]*"/g, '""'); code = code.replace(/'[^']*'/g, "''"); code = code.replace(/`[^`]*`/g, '``'); return code; } calculateNestingLevel(code) { let maxNesting = 0; let currentNesting = 0; for (const char of code) { if (char === '{') { currentNesting++; maxNesting = Math.max(maxNesting, currentNesting); } else if (char === '}') { currentNesting--; } } return maxNesting; } calculateCognitiveComplexity(highComplexityFunctions) { if (highComplexityFunctions.length === 0) return 0; // Cognitive complexity considers: // 1. Average complexity of complex functions // 2. Number of highly complex functions // 3. Distribution of complexity const avgComplexity = highComplexityFunctions.reduce((sum, func) => sum + func.complexity, 0) / highComplexityFunctions.length; const veryHighComplexity = highComplexityFunctions.filter(f => f.complexity > 20).length; const extremeComplexity = highComplexityFunctions.filter(f => f.complexity > 30).length; // Calculate cognitive score (higher is worse) let cognitiveScore = avgComplexity; cognitiveScore += veryHighComplexity * 2; cognitiveScore += extremeComplexity * 5; return Math.round(cognitiveScore); } } //# sourceMappingURL=ComplexityAnalyzer.js.map