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
JavaScript
;
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