UNPKG

ripple-ai-detector

Version:

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

220 lines (177 loc) • 7.35 kB
import { SimpleParser } from '../analysis/simple-parser'; import { FileUtils } from '../utils/file-utils'; import { Issue, FunctionInfo, AffectedFile } from '../types/analysis'; import path from 'path'; interface FunctionChange { oldFunction: FunctionInfo; newFunction: FunctionInfo; changeType: 'parameters' | 'name' | 'signature'; details: string; } export class FunctionSignatureDetector { private parser: SimpleParser; constructor() { this.parser = new SimpleParser(); } // Main detection method async detect(files: string[]): Promise<Issue[]> { const issues: Issue[] = []; 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 private async analyzeFile(filePath: string, allFiles: string[]): Promise<Issue[]> { const issues: Issue[] = []; // Read current file content const fileInfo = await 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: 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) private async detectFunctionChanges(currentFunctions: FunctionInfo[], filePath: string): Promise<FunctionChange[]> { const changes: FunctionChange[] = []; // 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 private looksLikeAIChange(func: FunctionInfo): boolean { // 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) private simulateOldFunction(newFunc: FunctionInfo): FunctionInfo { // 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 private async findCallSites(func: FunctionInfo, allFiles: string[]): Promise<AffectedFile[]> { const affectedFiles: AffectedFile[] = []; for (const file of allFiles) { if (file === func.file) continue; // Skip the file where function is defined try { const fileInfo = await 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 private createIssueMessage(change: FunctionChange): string { 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 private getFunctionSignature(func: FunctionInfo): string { 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 private generateSuggestions(change: FunctionChange, affectedFiles: AffectedFile[]): string[] { const suggestions: string[] = []; 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 private generateCallSiteSuggestion(func: FunctionInfo, context: string): string { 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(', ')}`; } }