UNPKG

ripple-ai-detector

Version:

🌊 Ripple AI Bug Detector - Built by an AI that knows its flaws. Catch AI-generated bugs before you commit.

184 lines • 7.89 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FunctionSignatureDetector = void 0; const simple_parser_1 = require("../analysis/simple-parser"); const file_utils_1 = require("../utils/file-utils"); class FunctionSignatureDetector { parser; constructor() { this.parser = new simple_parser_1.SimpleParser(); } // Main detection method async detect(files) { const issues = []; for (const file of files) { try { const fileIssues = await this.analyzeFile(file, files); issues.push(...fileIssues); } catch (error) { // Skip files that can't be parsed console.warn(`Failed to analyze ${file}: ${error}`); } } return issues; } // Analyze a single file for function signature changes async analyzeFile(filePath, allFiles) { const issues = []; // Read current file content const fileInfo = await file_utils_1.FileUtils.getFileInfo(filePath); if (!fileInfo.isJavaScript) { return issues; } // Parse current version const currentFunctions = this.parser.extractFunctions(fileInfo.content, filePath); // For MVP, we'll simulate "old" version by checking git diff // In a full implementation, we'd compare with git HEAD const changes = await this.detectFunctionChanges(currentFunctions, filePath); // For each changed function, find call sites for (const change of changes) { const affectedFiles = await this.findCallSites(change.newFunction, allFiles); if (affectedFiles.length > 0) { const issue = { id: `func-sig-${change.newFunction.name}-${Date.now()}`, type: 'function-signature-change', severity: 'error', message: this.createIssueMessage(change), file: filePath, line: change.newFunction.line, column: change.newFunction.column, details: { functionName: change.newFunction.name, oldSignature: this.getFunctionSignature(change.oldFunction), newSignature: this.getFunctionSignature(change.newFunction), affectedFiles, context: change.details }, suggestions: this.generateSuggestions(change, affectedFiles), confidence: 0.9 // High confidence for function signature changes }; issues.push(issue); } } return issues; } // Detect function changes (simplified for MVP) async detectFunctionChanges(currentFunctions, filePath) { const changes = []; // For MVP, we'll use heuristics to detect likely AI changes // This is where we'd normally compare with git HEAD for (const func of currentFunctions) { // Detect common AI patterns that indicate function signature changes if (this.looksLikeAIChange(func)) { // Simulate an "old" version for demo purposes const oldFunction = this.simulateOldFunction(func); changes.push({ oldFunction, newFunction: func, changeType: 'parameters', details: 'Function parameters appear to have been modified' }); } } return changes; } // Heuristic to detect if function looks like it was changed by AI looksLikeAIChange(func) { // AI commonly adds options/config parameters const hasOptionsParam = func.parameters.some(p => p.name.toLowerCase().includes('option') || p.name.toLowerCase().includes('config') || p.name.toLowerCase().includes('setting')); // AI often adds optional parameters at the end const hasOptionalParams = func.parameters.some(p => p.optional); // AI tends to add type annotations const hasTypeAnnotations = func.parameters.some(p => p.type); // For MVP demo, trigger on any of these patterns return hasOptionsParam || (hasOptionalParams && hasTypeAnnotations); } // Simulate old function for demo (in real implementation, get from git) simulateOldFunction(newFunc) { // Remove the last parameter (common AI pattern) const oldParameters = newFunc.parameters.slice(0, -1); return { ...newFunc, parameters: oldParameters }; } // Find all call sites of a function across files async findCallSites(func, allFiles) { const affectedFiles = []; for (const file of allFiles) { if (file === func.file) continue; // Skip the file where function is defined try { const fileInfo = await file_utils_1.FileUtils.getFileInfo(file); if (!fileInfo.isJavaScript) continue; const calls = this.parser.extractFunctionCalls(fileInfo.content, file); // Find calls to our function const matchingCalls = calls.filter(call => call.name === func.name); for (const call of matchingCalls) { affectedFiles.push({ path: file, line: call.line, column: call.column, context: call.context, suggestion: this.generateCallSiteSuggestion(func, call.context) }); } } catch (error) { // Skip files that can't be parsed } } return affectedFiles; } // Create human-readable issue message createIssueMessage(change) { const oldSig = this.getFunctionSignature(change.oldFunction); const newSig = this.getFunctionSignature(change.newFunction); return `Function signature changed without updating callers: ${oldSig} → ${newSig}`; } // Get function signature string getFunctionSignature(func) { const params = func.parameters.map(p => { let param = p.name; if (p.type) param += `: ${p.type}`; if (p.optional) param += '?'; if (p.defaultValue) param += ` = ${p.defaultValue}`; return param; }).join(', '); return `${func.name}(${params})`; } // Generate suggestions for fixing the issue generateSuggestions(change, affectedFiles) { const suggestions = []; suggestions.push(`Update ${affectedFiles.length} call sites to match new signature`); suggestions.push(`Add default values to new parameters to maintain compatibility`); suggestions.push(`Consider creating a wrapper function for backward compatibility`); return suggestions; } // Generate suggestion for specific call site generateCallSiteSuggestion(func, context) { const newParams = func.parameters.map(p => { if (p.defaultValue) return p.defaultValue; if (p.optional) return 'undefined'; if (p.type === 'string') return "''"; if (p.type === 'number') return '0'; if (p.type === 'boolean') return 'false'; return 'null'; }); return `Add missing parameters: ${newParams.slice(-1).join(', ')}`; } } exports.FunctionSignatureDetector = FunctionSignatureDetector; //# sourceMappingURL=function-signature-detector.js.map