mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
227 lines • 9.1 kB
JavaScript
/**
* 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