UNPKG

@moikas/code-audit-mcp

Version:

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

387 lines 13.8 kB
/** * Completeness auditor for identifying incomplete implementations */ import { BaseAuditor } from './base.js'; export class CompletenessAuditor extends BaseAuditor { constructor(config, ollamaClient, modelManager) { super(config, ollamaClient, modelManager); this.auditType = 'completeness'; } /** * Post-process completeness issues with pattern detection */ async postProcessIssues(rawIssues, request, language) { const issues = await super.postProcessIssues(rawIssues, request, language); // Add static pattern detection for common completeness issues const patternIssues = this.detectCompletenessPatterns(request.code, language); // Merge and deduplicate const allIssues = [...issues, ...patternIssues]; return this.deduplicateIssues(allIssues); } /** * Detect completeness patterns using static analysis */ detectCompletenessPatterns(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; const _trimmedLine = line.trim().toLowerCase(); // TODO comments if (this.isTodoComment(line)) { issues.push(this.createTodoIssue(line, lineNumber)); } // FIXME comments if (this.isFixmeComment(line)) { issues.push(this.createFixmeIssue(line, lineNumber)); } // HACK comments if (this.isHackComment(line)) { issues.push(this.createHackIssue(line, lineNumber)); } // Empty function bodies if (this.isEmptyFunction(line, lines, i, language)) { issues.push(this.createEmptyFunctionIssue(line, lineNumber)); } // Missing return statements if (this.isMissingReturn(line, lines, i, language)) { issues.push(this.createMissingReturnIssue(line, lineNumber)); } // Placeholder implementations if (this.isPlaceholderImplementation(line)) { issues.push(this.createPlaceholderIssue(line, lineNumber)); } // Missing error handling if (this.isMissingErrorHandling(line, lines, i, language)) { issues.push(this.createMissingErrorHandlingIssue(line, lineNumber)); } // Unhandled promises if (this.isUnhandledPromise(line, language)) { issues.push(this.createUnhandledPromiseIssue(line, lineNumber)); } } return issues; } /** * Check if line contains TODO comment */ isTodoComment(line) { const todoPatterns = [ /\/\/.*todo/i, /\/\*.*todo.*\*\//i, /#.*todo/i, /<!--.*todo.*-->/i, ]; return todoPatterns.some((pattern) => pattern.test(line)); } /** * Check if line contains FIXME comment */ isFixmeComment(line) { const fixmePatterns = [ /\/\/.*fixme/i, /\/\*.*fixme.*\*\//i, /#.*fixme/i, /<!--.*fixme.*-->/i, ]; return fixmePatterns.some((pattern) => pattern.test(line)); } /** * Check if line contains HACK comment */ isHackComment(line) { const hackPatterns = [ /\/\/.*hack/i, /\/\*.*hack.*\*\//i, /#.*hack/i, /<!--.*hack.*-->/i, ]; return hackPatterns.some((pattern) => pattern.test(line)); } /** * Check if line represents an empty function */ isEmptyFunction(line, lines, index, language) { const functionPatterns = { javascript: /function\s+\w+\s*\([^)]*\)\s*{/, typescript: /function\s+\w+\s*\([^)]*\)\s*:\s*\w+\s*{/, python: /def\s+\w+\s*\([^)]*\)\s*:/, java: /\w+\s+\w+\s*\([^)]*\)\s*{/, csharp: /\w+\s+\w+\s*\([^)]*\)\s*{/, go: /func\s+\w+\s*\([^)]*\)\s*\w*\s*{/, rust: /fn\s+\w+\s*\([^)]*\)\s*->\s*\w+\s*{/, }; const pattern = functionPatterns[language]; if (!pattern || !pattern.test(line)) { return false; } // Check if the function body is empty or only contains comments let braceCount = 0; let hasContent = false; for (let i = index; i < lines.length; i++) { const currentLine = lines[i].trim(); braceCount += (currentLine.match(/{/g) || []).length; braceCount -= (currentLine.match(/}/g) || []).length; if (braceCount === 0 && i > index) { break; } // Check for actual content (not comments or empty lines) if (currentLine && !currentLine.startsWith('//') && !currentLine.startsWith('/*') && !currentLine.startsWith('#')) { const contentLine = currentLine.replace(/[{}]/g, '').trim(); if (contentLine) { hasContent = true; } } } return !hasContent; } /** * Check if function is missing return statement */ isMissingReturn(line, lines, index, language) { // Only check for languages that require explicit returns if (!['javascript', 'typescript', 'java', 'csharp', 'go', 'rust'].includes(language)) { return false; } const functionPattern = /function\s+\w+\s*\([^)]*\)\s*:\s*\w+|def\s+\w+\s*\([^)]*\)\s*->/; if (!functionPattern.test(line) || line.includes('void')) { return false; } // Look for return statement in function body let braceCount = 0; let hasReturn = false; for (let i = index; i < lines.length; i++) { const currentLine = lines[i].trim(); braceCount += (currentLine.match(/{/g) || []).length; braceCount -= (currentLine.match(/}/g) || []).length; if (braceCount === 0 && i > index) { break; } if (currentLine.includes('return ')) { hasReturn = true; break; } } return !hasReturn; } /** * Check if line contains placeholder implementation */ isPlaceholderImplementation(line) { const placeholderPatterns = [ /throw new Error\("not implemented"\)/i, /throw new NotImplementedError/i, /raise NotImplementedError/i, /panic\("not implemented"\)/i, /unimplemented!/i, /\/\/ implementation/i, /\/\/ placeholder/i, ]; return placeholderPatterns.some((pattern) => pattern.test(line)); } /** * Check if line is missing error handling */ isMissingErrorHandling(line, lines, index, _language) { // Look for risky operations without try-catch const riskyPatterns = [ /JSON\.parse/, /JSON\.stringify/, /fetch\(/, /XMLHttpRequest/, /fs\.readFileSync/, /fs\.writeFileSync/, /parseInt/, /parseFloat/, ]; if (!riskyPatterns.some((pattern) => pattern.test(line))) { return false; } // Check if it's already in a try-catch block for (let i = index - 10; i < index; i++) { if (i >= 0 && lines[i] && lines[i].trim().startsWith('try')) { return false; } } return true; } /** * Check if line contains unhandled promise */ isUnhandledPromise(line, language) { if (!['javascript', 'typescript'].includes(language)) { return false; } const promisePatterns = [ /\w+\.\w+\([^)]*\)(?!\s*\.(then|catch|finally))/, /await\s+\w+\([^)]*\)/, // await without try-catch is checked elsewhere ]; return (promisePatterns.some((pattern) => pattern.test(line)) && !line.includes('.then') && !line.includes('.catch') && !line.includes('await')); } /** * Create TODO issue */ createTodoIssue(line, lineNumber) { return { id: `todo_${lineNumber}`, location: { line: lineNumber }, severity: 'medium', type: 'todo_comment', category: 'completeness', title: 'TODO comment indicates incomplete implementation', description: `Found TODO comment: ${line.trim()}`, suggestion: 'Implement the missing functionality or remove the TODO comment', confidence: 1.0, fixable: false, ruleId: 'COMP001', }; } /** * Create FIXME issue */ createFixmeIssue(line, lineNumber) { return { id: `fixme_${lineNumber}`, location: { line: lineNumber }, severity: 'high', type: 'fixme_comment', category: 'completeness', title: 'FIXME comment indicates broken functionality', description: `Found FIXME comment: ${line.trim()}`, suggestion: 'Fix the issue mentioned in the FIXME comment', confidence: 1.0, fixable: false, ruleId: 'COMP002', }; } /** * Create HACK issue */ createHackIssue(line, lineNumber) { return { id: `hack_${lineNumber}`, location: { line: lineNumber }, severity: 'medium', type: 'hack_comment', category: 'completeness', title: 'HACK comment indicates suboptimal implementation', description: `Found HACK comment: ${line.trim()}`, suggestion: 'Replace the hack with a proper implementation', confidence: 1.0, fixable: false, ruleId: 'COMP003', }; } /** * Create empty function issue */ createEmptyFunctionIssue(line, lineNumber) { return { id: `empty_function_${lineNumber}`, location: { line: lineNumber }, severity: 'medium', type: 'empty_function', category: 'completeness', title: 'Empty function body', description: 'Function has no implementation', suggestion: 'Implement the function body or add appropriate error handling', confidence: 0.9, fixable: false, ruleId: 'COMP004', }; } /** * Create missing return issue */ createMissingReturnIssue(line, lineNumber) { return { id: `missing_return_${lineNumber}`, location: { line: lineNumber }, severity: 'high', type: 'missing_return', category: 'completeness', title: 'Function missing return statement', description: 'Function declares a return type but has no return statement', suggestion: 'Add appropriate return statement or change return type to void', confidence: 0.8, fixable: false, ruleId: 'COMP005', }; } /** * Create placeholder issue */ createPlaceholderIssue(line, lineNumber) { return { id: `placeholder_${lineNumber}`, location: { line: lineNumber }, severity: 'high', type: 'placeholder_implementation', category: 'completeness', title: 'Placeholder implementation found', description: `Placeholder implementation: ${line.trim()}`, suggestion: 'Replace placeholder with actual implementation', confidence: 1.0, fixable: false, ruleId: 'COMP006', }; } /** * Create missing error handling issue */ createMissingErrorHandlingIssue(line, lineNumber) { return { id: `missing_error_handling_${lineNumber}`, location: { line: lineNumber }, severity: 'medium', type: 'missing_error_handling', category: 'completeness', title: 'Missing error handling for risky operation', description: 'Operation that can throw errors is not wrapped in try-catch', suggestion: 'Add appropriate error handling (try-catch block)', confidence: 0.7, fixable: true, ruleId: 'COMP007', }; } /** * Create unhandled promise issue */ createUnhandledPromiseIssue(line, lineNumber) { return { id: `unhandled_promise_${lineNumber}`, location: { line: lineNumber }, severity: 'medium', type: 'unhandled_promise', category: 'completeness', title: 'Promise without error handling', description: 'Promise is not handled with .catch() or try-catch', suggestion: 'Add .catch() handler or wrap in try-catch if using await', confidence: 0.8, fixable: true, ruleId: 'COMP008', }; } /** * 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=completeness.js.map