UNPKG

@moikas/code-audit-mcp

Version:

AI-powered code auditing via MCP using local Ollama models for security, performance, and quality analysis

407 lines 13.2 kB
/** * Code complexity analysis utilities */ /** * Cyclomatic complexity calculator */ export class CyclomaticComplexityCalculator { /** * Calculate cyclomatic complexity for code * Formula: M = E - N + 2P * Where E = edges, N = nodes, P = connected components * Simplified: Count decision points + 1 */ static calculate(code, language) { let complexity = 1; // Base complexity const decisionPatterns = this.getDecisionPatterns(language); for (const pattern of decisionPatterns) { const matches = code.match(new RegExp(pattern.source, 'gi')); if (matches) { complexity += matches.length; } } return complexity; } /** * Get decision patterns for different languages */ static getDecisionPatterns(language) { const basePatterns = [ /\bif\b/, /\belse\s+if\b/, /\bwhile\b/, /\bfor\b/, /\bdo\b/, /\bswitch\b/, /\bcase\b/, /\btry\b/, /\bcatch\b/, /\bfinally\b/, /&&/, /\|\|/, /\?/, // Ternary operator ]; const languageSpecific = { python: [...basePatterns, /\belif\b/, /\bexcept\b/, /\band\b/, /\bor\b/], javascript: [...basePatterns, /\.then\b/, /\.catch\b/], typescript: [...basePatterns, /\.then\b/, /\.catch\b/], java: [...basePatterns, /\bthrows\b/], go: [...basePatterns, /\bselect\b/, /\bdefer\b/], }; return languageSpecific[language] || basePatterns; } } /** * Cognitive complexity calculator (more human-readable than cyclomatic) */ export class CognitiveComplexityCalculator { /** * Calculate cognitive complexity * Focuses on how hard code is to understand */ static calculate(code, language) { const lines = code.split('\n'); let complexity = 0; let nestingLevel = 0; for (const line of lines) { const trimmedLine = line.trim(); // Track nesting level nestingLevel += this.getNestingChange(trimmedLine, language); // Add complexity for control structures const structureComplexity = this.getStructureComplexity(trimmedLine, language); if (structureComplexity > 0) { // Add base complexity + nesting bonus complexity += structureComplexity + Math.max(0, nestingLevel - 1); } } return complexity; } /** * Get nesting level change for a line */ static getNestingChange(line, language) { let change = 0; // Opening braces/blocks change += (line.match(/{/g) || []).length; change += (line.match(/\bthen\b/g) || []).length; // Python if/then // Closing braces/blocks change -= (line.match(/}/g) || []).length; // Python specific (indentation-based) if (language === 'python') { if (line.match(/^\s*(if|else|elif|while|for|try|except|finally|with|def|class):/)) { change += 1; } } return change; } /** * Get complexity score for control structures */ static getStructureComplexity(line, _language) { const patterns = [ { pattern: /\bif\b/, score: 1 }, { pattern: /\belse\s+if\b|\belif\b/, score: 1 }, { pattern: /\belse\b/, score: 1 }, { pattern: /\bswitch\b/, score: 1 }, { pattern: /\bcase\b/, score: 1 }, { pattern: /\bfor\b/, score: 1 }, { pattern: /\bwhile\b/, score: 1 }, { pattern: /\bdo\b/, score: 1 }, { pattern: /\btry\b/, score: 1 }, { pattern: /\bcatch\b/, score: 1 }, { pattern: /\bfinally\b/, score: 1 }, { pattern: /&&|\|\|/, score: 1 }, { pattern: /\?.*:/, score: 1 }, // Ternary { pattern: /\bgoto\b/, score: 2 }, // Higher penalty for goto { pattern: /\bbreak\b/, score: 1 }, { pattern: /\bcontinue\b/, score: 1 }, ]; let totalScore = 0; for (const { pattern, score } of patterns) { if (pattern.test(line)) { totalScore += score; } } return totalScore; } } /** * Nesting depth calculator */ export class NestingDepthCalculator { /** * Calculate maximum nesting depth */ static calculate(code, language) { const lines = code.split('\n'); let currentDepth = 0; let maxDepth = 0; for (const line of lines) { const trimmedLine = line.trim(); // Track depth changes const depthChange = this.getDepthChange(trimmedLine, language); currentDepth += depthChange; maxDepth = Math.max(maxDepth, currentDepth); // Prevent negative depth currentDepth = Math.max(0, currentDepth); } return maxDepth; } /** * Get depth change for a line */ static getDepthChange(line, language) { if (language === 'python') { return this.calculatePythonDepthChange(line); } // For brace-based languages let change = 0; change += (line.match(/{/g) || []).length; change -= (line.match(/}/g) || []).length; return change; } /** * Calculate depth change for Python (indentation-based) */ static calculatePythonDepthChange(line) { // Simplified Python depth calculation if (line.match(/^\s*(if|else|elif|while|for|try|except|finally|with|def|class):/)) { return 1; } // This is a simplification - real Python would need proper indentation analysis return 0; } } /** * Halstead complexity calculator */ export class HalsteadComplexityCalculator { /** * Calculate Halstead metrics */ static calculate(code, language) { const operators = this.extractOperators(code, language); const operands = this.extractOperands(code, language); const n1 = new Set(operators).size; // Unique operators const n2 = new Set(operands).size; // Unique operands const N1 = operators.length; // Total operators const N2 = operands.length; // Total operands const n = n1 + n2; // Vocabulary const N = N1 + N2; // Length const calculatedLength = n1 * Math.log2(n1) + n2 * Math.log2(n2); const volume = N * Math.log2(n); const difficulty = (n1 / 2) * (N2 / (n2 || 1)); const effort = difficulty * volume; const timeRequiredToProgram = effort / 18; const deliveredBugs = volume / 3000; return { vocabulary: n, length: N, calculatedLength, volume, difficulty, effort, timeRequiredToProgram, deliveredBugs, }; } /** * Extract operators from code */ static extractOperators(code, language) { const operatorPatterns = this.getOperatorPatterns(language); const operators = []; for (const pattern of operatorPatterns) { const matches = code.match(new RegExp(pattern.source, 'g')); if (matches) { operators.push(...matches); } } return operators; } /** * Extract operands from code */ static extractOperands(code, language) { const operandPatterns = this.getOperandPatterns(language); const operands = []; for (const pattern of operandPatterns) { const matches = code.match(new RegExp(pattern.source, 'g')); if (matches) { operands.push(...matches); } } return operands; } /** * Get operator patterns for language */ static getOperatorPatterns(_language) { return [ /\+/g, /-/g, /\*/g, /\//g, /%/g, /==/g, /!=/g, /</g, />/g, /<=/g, />=/g, /&&/g, /\|\|/g, /!/g, /&/g, /\|/g, /\^/g, /~/g, /<</g, />>/g, /=/g, /\+=/g, /-=/g, /\*=/g, /\/=/g, /\+\+/g, /--/g, /\./g, /->/g, /::/g, ]; } /** * Get operand patterns for language */ static getOperandPatterns(_language) { return [ /\b\w+\b/g, // Identifiers /\b\d+\b/g, // Numbers /"[^"]*"/g, // String literals /'[^']*'/g, // Character literals /`[^`]*`/g, // Template literals ]; } } /** * Maintainability index calculator */ export class MaintainabilityIndexCalculator { /** * Calculate maintainability index * Formula: MI = 171 - 5.2 * ln(HV) - 0.23 * CC - 16.2 * ln(LOC) * Where HV = Halstead Volume, CC = Cyclomatic Complexity, LOC = Lines of Code */ static calculate(halsteadVolume, cyclomaticComplexity, linesOfCode, commentRatio = 0) { const base = 171 - 5.2 * Math.log(halsteadVolume) - 0.23 * cyclomaticComplexity - 16.2 * Math.log(linesOfCode); // Add comment bonus const commentBonus = 50 * Math.sin(Math.sqrt(2.4 * commentRatio)); const mi = Math.max(0, ((base + commentBonus) * 100) / 171); return Math.round(mi); } } /** * Main complexity analyzer */ export class ComplexityAnalyzer { /** * Analyze all complexity metrics for code */ static analyze(code, language) { const lines = code.split('\n'); const linesOfCode = lines.filter((line) => line.trim().length > 0).length; const cyclomaticComplexity = CyclomaticComplexityCalculator.calculate(code, language); const cognitiveComplexity = CognitiveComplexityCalculator.calculate(code, language); const nestingDepth = NestingDepthCalculator.calculate(code, language); const halsteadMetrics = HalsteadComplexityCalculator.calculate(code, language); // Calculate comment ratio const commentLines = this.countCommentLines(code, language); const commentRatio = commentLines / Math.max(linesOfCode, 1); const maintainabilityIndex = MaintainabilityIndexCalculator.calculate(halsteadMetrics.volume, cyclomaticComplexity, linesOfCode, commentRatio); return { cyclomaticComplexity, cognitiveComplexity, nestingDepth, linesOfCode, maintainabilityIndex, halsteadMetrics, }; } /** * Count comment lines in code */ static countCommentLines(code, language) { const lines = code.split('\n'); let commentLines = 0; const commentPatterns = this.getCommentPatterns(language); for (const line of lines) { const trimmedLine = line.trim(); for (const pattern of commentPatterns) { if (pattern.test(trimmedLine)) { commentLines++; break; } } } return commentLines; } /** * Get comment patterns for language */ static getCommentPatterns(language) { const patterns = { javascript: [/^\/\//, /^\/\*/, /^\*/], typescript: [/^\/\//, /^\/\*/, /^\*/], python: [/^#/, /^"""/, /^'''/], java: [/^\/\//, /^\/\*/, /^\*/], csharp: [/^\/\//, /^\/\*/, /^\*/], go: [/^\/\//, /^\/\*/, /^\*/], rust: [/^\/\//, /^\/\*/, /^\*/], php: [/^\/\//, /^\/\*/, /^#/], ruby: [/^#/], sql: [/^--/, /^\/\*/], }; return patterns[language] || [/^\/\//, /^#/]; } /** * Get complexity rating */ static getComplexityRating(complexity, type) { if (type === 'cyclomatic') { if (complexity <= 10) return 'Low'; if (complexity <= 20) return 'Moderate'; if (complexity <= 50) return 'High'; return 'Very High'; } else { if (complexity <= 5) return 'Low'; if (complexity <= 10) return 'Moderate'; if (complexity <= 15) return 'High'; return 'Very High'; } } /** * Get maintainability rating */ static getMaintainabilityRating(index) { if (index >= 85) return 'Excellent'; if (index >= 70) return 'Good'; if (index >= 50) return 'Moderate'; if (index >= 25) return 'Poor'; return 'Very Poor'; } } //# sourceMappingURL=complexity.js.map