UNPKG

@moikas/code-audit-mcp

Version:

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

469 lines 17.4 kB
/** * Performance auditor for identifying bottlenecks and optimization opportunities */ import { BaseAuditor } from './base.js'; export class PerformanceAuditor extends BaseAuditor { constructor(config, ollamaClient, modelManager) { super(config, ollamaClient, modelManager); this.auditType = 'performance'; } /** * Post-process performance issues with complexity analysis */ async postProcessIssues(rawIssues, request, language) { const issues = await super.postProcessIssues(rawIssues, request, language); // Add static performance pattern detection const patternIssues = this.detectPerformancePatterns(request.code, language); // Merge and deduplicate const allIssues = [...issues, ...patternIssues]; const deduplicatedIssues = this.deduplicateIssues(allIssues); // Adjust severity based on performance criticality return this.adjustSeverityForContext(deduplicatedIssues, request.context); } /** * Detect performance patterns using static analysis */ detectPerformancePatterns(code, language) { const issues = []; const lines = code.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; const lineNumber = i + 1; // Nested loops (O(n²) complexity) if (this.isNestedLoop(line, lines, i, language)) { issues.push(this.createNestedLoopIssue(line, lineNumber)); } // Inefficient string concatenation if (this.isInefficientStringConcatenation(line, language)) { issues.push(this.createStringConcatenationIssue(line, lineNumber)); } // Database queries in loops if (this.isQueryInLoop(line, lines, i, language)) { issues.push(this.createQueryInLoopIssue(line, lineNumber)); } // Synchronous file operations if (this.isSyncFileOperation(line, language)) { issues.push(this.createSyncFileOperationIssue(line, lineNumber)); } // Large object creation in loops if (this.isObjectCreationInLoop(line, lines, i, language)) { issues.push(this.createObjectCreationInLoopIssue(line, lineNumber)); } // Missing caching for expensive operations if (this.isMissingCaching(line, language)) { issues.push(this.createMissingCachingIssue(line, lineNumber)); } // Inefficient array operations if (this.isInefficientArrayOperation(line, language)) { issues.push(this.createInefficientArrayOperationIssue(line, lineNumber)); } // Memory leak potential if (this.isMemoryLeakPotential(line, language)) { issues.push(this.createMemoryLeakIssue(line, lineNumber)); } // Blocking operations in async context if (this.isBlockingInAsyncContext(line, lines, i, language)) { issues.push(this.createBlockingOperationIssue(line, lineNumber)); } // Inefficient DOM operations if (this.isInefficientDOMOperation(line, language)) { issues.push(this.createDOMOperationIssue(line, lineNumber)); } } return issues; } /** * Check for nested loops */ isNestedLoop(line, lines, index, _language) { const loopPatterns = [ /\bfor\s*\(/, /\bwhile\s*\(/, /\.forEach\s*\(/, /\.map\s*\(/, /\.filter\s*\(/, ]; if (!loopPatterns.some((pattern) => pattern.test(line))) { return false; } // Look for another loop within a reasonable distance let braceCount = 0; let foundNestedLoop = false; for (let i = index + 1; i < Math.min(lines.length, index + 20); i++) { const currentLine = lines[i]; braceCount += (currentLine.match(/{/g) || []).length; braceCount -= (currentLine.match(/}/g) || []).length; if (braceCount < 0) break; if (loopPatterns.some((pattern) => pattern.test(currentLine))) { foundNestedLoop = true; break; } } return foundNestedLoop; } /** * Check for inefficient string concatenation */ isInefficientStringConcatenation(line, language) { if (language === 'javascript' || language === 'typescript') { return /\w+\s*\+=\s*["']/.test(line) && line.includes('for'); } if (language === 'python') { return /\w+\s*\+=\s*["']/.test(line); } if (language === 'java' || language === 'csharp') { return /String\s+\w+\s*=.*\+/.test(line) && line.includes('for'); } return false; } /** * Check for database queries in loops */ isQueryInLoop(line, lines, index, _language) { const queryPatterns = [ /\.query\s*\(/, /\.execute\s*\(/, /SELECT\s+/i, /INSERT\s+/i, /UPDATE\s+/i, /DELETE\s+/i, /\.find\s*\(/, /\.save\s*\(/, ]; if (!queryPatterns.some((pattern) => pattern.test(line))) { return false; } // Check if we're inside a loop for (let i = index - 10; i < index; i++) { if (i >= 0 && lines[i]) { if (/\b(for|while|forEach)\b/.test(lines[i])) { return true; } } } return false; } /** * Check for synchronous file operations */ isSyncFileOperation(line, language) { if (language === 'javascript' || language === 'typescript') { const syncPatterns = [ /fs\.readFileSync/, /fs\.writeFileSync/, /fs\.existsSync/, /fs\.statSync/, ]; return syncPatterns.some((pattern) => pattern.test(line)); } return false; } /** * Check for object creation in loops */ isObjectCreationInLoop(line, lines, index, _language) { const objectPatterns = [ /new\s+\w+\s*\(/, /\{\s*\w+:/, /\[\s*\w+/, /Object\.create/, ]; if (!objectPatterns.some((pattern) => pattern.test(line))) { return false; } // Check if we're inside a loop for (let i = index - 10; i < index; i++) { if (i >= 0 && lines[i]) { if (/\b(for|while|forEach)\b/.test(lines[i])) { return true; } } } return false; } /** * Check for missing caching opportunities */ isMissingCaching(line, _language) { const expensiveOperations = [ /Math\.(sin|cos|sqrt|pow)/, /JSON\.parse/, /JSON\.stringify/, /\w+\.match\(/, /\w+\.replace\(/, /fetch\(/, /axios\./, ]; return expensiveOperations.some((pattern) => pattern.test(line)); } /** * Check for inefficient array operations */ isInefficientArrayOperation(line, language) { if (language === 'javascript' || language === 'typescript') { // Array operations that could be optimized const inefficientPatterns = [ /\.indexOf\s*\([^)]+\)\s*!\s*=\s*-1/, // Use includes() instead /\.splice\s*\(\s*0\s*,\s*1\s*\)/, // Use shift() instead /\.slice\s*\(\s*0\s*,\s*-1\s*\)/, // Use pop() instead /for\s*\(.*\.length\s*\)/, // Cache length ]; return inefficientPatterns.some((pattern) => pattern.test(line)); } return false; } /** * Check for memory leak potential */ isMemoryLeakPotential(line, language) { if (language === 'javascript' || language === 'typescript') { const leakPatterns = [ /addEventListener\s*\((?!.*removeEventListener)/, /setInterval\s*\((?!.*clearInterval)/, /setTimeout\s*\((?!.*clearTimeout)/, /new\s+EventSource\s*\((?!.*close)/, ]; return leakPatterns.some((pattern) => pattern.test(line)); } return false; } /** * Check for blocking operations in async context */ isBlockingInAsyncContext(line, lines, index, language) { if (language !== 'javascript' && language !== 'typescript') { return false; } const blockingPatterns = [ /fs\.readFileSync/, /fs\.writeFileSync/, /JSON\.parse\(.*large/i, /while\s*\(true\)/, ]; if (!blockingPatterns.some((pattern) => pattern.test(line))) { return false; } // Check if we're in an async function for (let i = index - 20; i < index; i++) { if (i >= 0 && lines[i]) { if (/async\s+function|async\s*\(/.test(lines[i])) { return true; } } } return false; } /** * Check for inefficient DOM operations */ isInefficientDOMOperation(line, language) { if (language !== 'javascript' && language !== 'typescript') { return false; } const inefficientDOMPatterns = [ /document\.getElementById.*for/, /querySelector.*for/, /innerHTML\s*\+=/, // Use textContent or createElement /document\.createElement.*for/, ]; return inefficientDOMPatterns.some((pattern) => pattern.test(line)); } /** * Create performance issue objects */ createNestedLoopIssue(line, lineNumber) { return { id: `nested_loop_${lineNumber}`, location: { line: lineNumber }, severity: 'high', type: 'nested_loops', category: 'performance', title: 'Nested loops detected (O(n²) complexity)', description: 'Nested loops can cause performance issues with large datasets', suggestion: 'Consider using maps, sets, or other data structures to reduce complexity', confidence: 0.9, fixable: true, ruleId: 'PERF001', effort: 'medium', }; } createStringConcatenationIssue(line, lineNumber) { return { id: `string_concat_${lineNumber}`, location: { line: lineNumber }, severity: 'medium', type: 'inefficient_string_concatenation', category: 'performance', title: 'Inefficient string concatenation', description: 'String concatenation in loops can be slow', suggestion: 'Use StringBuilder, string templates, or array.join() for better performance', confidence: 0.8, fixable: true, ruleId: 'PERF002', effort: 'low', }; } createQueryInLoopIssue(line, lineNumber) { return { id: `query_in_loop_${lineNumber}`, location: { line: lineNumber }, severity: 'critical', type: 'database_query_in_loop', category: 'performance', title: 'Database query inside loop (N+1 problem)', description: 'Running database queries in loops can severely impact performance', suggestion: 'Batch queries or use joins to fetch all data at once', confidence: 0.9, fixable: true, ruleId: 'PERF003', effort: 'medium', }; } createSyncFileOperationIssue(line, lineNumber) { return { id: `sync_file_op_${lineNumber}`, location: { line: lineNumber }, severity: 'medium', type: 'synchronous_file_operation', category: 'performance', title: 'Synchronous file operation blocks event loop', description: 'Synchronous file operations can block the main thread', suggestion: 'Use asynchronous file operations instead', confidence: 0.9, fixable: true, ruleId: 'PERF004', effort: 'low', }; } createObjectCreationInLoopIssue(line, lineNumber) { return { id: `object_creation_loop_${lineNumber}`, location: { line: lineNumber }, severity: 'medium', type: 'object_creation_in_loop', category: 'performance', title: 'Object creation inside loop', description: 'Creating objects in loops can cause garbage collection pressure', suggestion: 'Move object creation outside the loop or use object pooling', confidence: 0.7, fixable: true, ruleId: 'PERF005', effort: 'medium', }; } createMissingCachingIssue(line, lineNumber) { return { id: `missing_caching_${lineNumber}`, location: { line: lineNumber }, severity: 'low', type: 'missing_caching', category: 'performance', title: 'Expensive operation could benefit from caching', description: 'Repetitive expensive operations should be cached', suggestion: 'Implement caching for this operation if called frequently', confidence: 0.6, fixable: true, ruleId: 'PERF006', effort: 'medium', }; } createInefficientArrayOperationIssue(line, lineNumber) { return { id: `inefficient_array_${lineNumber}`, location: { line: lineNumber }, severity: 'low', type: 'inefficient_array_operation', category: 'performance', title: 'Inefficient array operation', description: 'Array operation could be optimized', suggestion: 'Use more efficient array methods or cache array length', confidence: 0.8, fixable: true, ruleId: 'PERF007', effort: 'low', }; } createMemoryLeakIssue(line, lineNumber) { return { id: `memory_leak_${lineNumber}`, location: { line: lineNumber }, severity: 'high', type: 'potential_memory_leak', category: 'performance', title: 'Potential memory leak', description: 'Event listener or timer without cleanup can cause memory leaks', suggestion: 'Add proper cleanup (removeEventListener, clearInterval, etc.)', confidence: 0.7, fixable: true, ruleId: 'PERF008', effort: 'low', }; } createBlockingOperationIssue(line, lineNumber) { return { id: `blocking_async_${lineNumber}`, location: { line: lineNumber }, severity: 'high', type: 'blocking_operation_in_async', category: 'performance', title: 'Blocking operation in async function', description: 'Blocking operations defeat the purpose of async functions', suggestion: 'Use async alternatives for file operations and CPU-intensive tasks', confidence: 0.9, fixable: true, ruleId: 'PERF009', effort: 'medium', }; } createDOMOperationIssue(line, lineNumber) { return { id: `dom_operation_${lineNumber}`, location: { line: lineNumber }, severity: 'medium', type: 'inefficient_dom_operation', category: 'performance', title: 'Inefficient DOM operation', description: 'DOM operations in loops or repetitive queries can be slow', suggestion: 'Cache DOM elements, use document fragments, or batch DOM updates', confidence: 0.8, fixable: true, ruleId: 'PERF010', effort: 'medium', }; } /** * Adjust severity based on performance criticality */ adjustSeverityForContext(issues, context) { if (!context?.performanceCritical) { return issues; } return issues.map((issue) => { // Increase severity for performance-critical code if (issue.severity === 'low') { issue.severity = 'medium'; } else if (issue.severity === 'medium') { issue.severity = 'high'; } issue.impact = 'Performance-critical code requires optimization'; return issue; }); } /** * Deduplicate issues by line number and type */ deduplicateIssues(issues) { const seen = new Set(); return issues.filter((issue) => { const key = `${issue.location.line}_${issue.type}`; if (seen.has(key)) { return false; } seen.add(key); return true; }); } } //# sourceMappingURL=performance.js.map