ripbug-ai-detector
Version:
🔥 RipBug AI Bug Detector - Built by an AI that rips its own bugs. Destroy AI-generated bugs before you commit.
247 lines • 11 kB
JavaScript
;
// Step 5: Enhanced Signature Detector - Sophisticated Signature Analysis
// Using tree-sitter's AST parsing for advanced pattern detection
Object.defineProperty(exports, "__esModule", { value: true });
exports.EnhancedSignatureDetector = void 0;
const ast_parser_enhanced_1 = require("./ast-parser-enhanced");
const feature_flags_1 = require("../config/feature-flags");
class EnhancedSignatureDetector {
parser;
constructor() {
// Use tree-sitter for sophisticated analysis
this.parser = new ast_parser_enhanced_1.EnhancedASTParser({
enableTreeSitter: true,
fallbackToRegex: true,
debugMode: true // Enable debug for Step 5 testing
});
}
// Main signature change detection method
async detectSignatureChanges(currentFunctions, allFiles) {
const changes = [];
for (const func of currentFunctions) {
// For Step 5 testing: temporarily analyze all functions, not just exported ones
// TODO: Re-enable export check once export detection is fixed
// if (!func.isExported) continue;
// Use tree-sitter for sophisticated signature pattern analysis
const potentialChange = this.analyzeSignaturePattern(func);
// Debug logging
if (feature_flags_1.FeatureFlags.getConfig().debugMode) {
console.log(`Analyzing ${func.name}: confidence=${potentialChange.confidence}, patterns=[${potentialChange.aiPatterns.join(', ')}]`);
}
if (potentialChange.isLikelyChanged) {
const callSites = await this.findCallSitesWithTreeSitter(func, allFiles);
const breakingCalls = this.validateCallCompatibility(func, callSites);
if (breakingCalls.length > 0) {
changes.push({
function: func,
changeType: potentialChange.changeType,
breakingCalls,
confidence: potentialChange.confidence,
severity: this.determineSeverity(potentialChange, breakingCalls),
aiPatterns: potentialChange.aiPatterns
});
}
}
}
return changes;
}
// Enhanced AI pattern detection with sophisticated analysis
analyzeSignaturePattern(func) {
const patterns = [];
const reasoning = [];
let confidence = 0;
// Pattern 1: AI loves adding "options" or "config" parameters
const hasOptionsParam = func.parameters.some(p => p.name.toLowerCase().includes('option') ||
p.name.toLowerCase().includes('config') ||
p.name.toLowerCase().includes('setting'));
if (hasOptionsParam) {
patterns.push('options-parameter');
reasoning.push('Function has options/config parameter (common AI pattern)');
confidence += 0.4;
}
// Pattern 2: Complex nested object types (AI signature)
const hasComplexTypes = func.parameters.some(p => p.type && (p.type.includes('{') ||
p.type.includes('<') ||
p.type.includes('|') ||
p.type.includes('Record<') ||
p.type.includes('Pick<') ||
p.type.includes('Omit<')));
if (hasComplexTypes) {
patterns.push('complex-types');
reasoning.push('Function uses complex TypeScript utility types');
confidence += 0.3;
}
// Pattern 3: Default values (AI often adds these)
const hasDefaultValues = func.parameters.some(p => p.defaultValue);
if (hasDefaultValues) {
patterns.push('default-values');
reasoning.push('Function has parameters with default values');
confidence += 0.2;
}
// Pattern 4: High parameter count (AI tends to over-parameterize)
if (func.parameters.length > 3) {
patterns.push('high-parameter-count');
reasoning.push(`Function has ${func.parameters.length} parameters (high complexity)`);
confidence += 0.1;
}
// Pattern 5: Generic parameters (AI loves generics)
const hasGenerics = func.parameters.some(p => p.type && (p.type.includes('<T>') || p.type.includes('<K>') || p.type.includes('<V>')));
if (hasGenerics) {
patterns.push('generic-parameters');
reasoning.push('Function uses generic type parameters');
confidence += 0.15;
}
// Pattern 6: Destructuring parameters (AI pattern)
const hasDestructuring = func.parameters.some(p => p.name.includes('{') || p.name.includes('['));
if (hasDestructuring) {
patterns.push('destructuring-parameters');
reasoning.push('Function uses destructuring in parameters');
confidence += 0.1;
}
// Pattern 7: Async/Promise patterns (AI often adds these)
const isAsyncFunction = func.isAsync || (func.name.includes('async') || func.name.includes('Async'));
if (isAsyncFunction) {
patterns.push('async-pattern');
reasoning.push('Function is async or has async naming pattern');
confidence += 0.05;
}
return {
isLikelyChanged: confidence > 0.4, // Lowered threshold for better detection
changeType: this.determineChangeType(func, patterns),
confidence: Math.min(confidence, 0.99), // Cap at 99%
aiPatterns: patterns,
reasoning
};
}
// Determine the type of signature change based on patterns
determineChangeType(func, patterns) {
// If has options parameter, likely parameter addition
if (patterns.includes('options-parameter')) {
return 'parameter-added';
}
// If has complex types, likely type changes
if (patterns.includes('complex-types')) {
return 'type-changed';
}
// If has destructuring, likely signature restructuring
if (patterns.includes('destructuring-parameters')) {
return 'signature-restructured';
}
// Default to parameter addition (most common AI pattern)
return 'parameter-added';
}
// Find call sites using tree-sitter's enhanced parsing
async findCallSitesWithTreeSitter(func, allFiles) {
const callSites = [];
for (const file of allFiles) {
if (file === func.file)
continue; // Skip the file where function is defined
try {
const fileContent = require('fs').readFileSync(file, 'utf8');
const calls = this.parser.extractFunctionCalls(fileContent, file);
// Find calls to our function with enhanced matching
const matchingCalls = calls.filter(call => call.name === func.name ||
call.name.endsWith(`.${func.name}`) ||
call.context.includes(`${func.name}(`));
for (const call of matchingCalls) {
callSites.push({
filePath: file,
line: call.line,
column: call.column,
context: call.context,
arguments: call.arguments || []
});
}
}
catch (error) {
// Skip files that can't be read or parsed
continue;
}
}
return callSites;
}
// Validate call compatibility with sophisticated analysis
validateCallCompatibility(func, callSites) {
const breakingCalls = [];
for (const callSite of callSites) {
const requiredParams = func.parameters.filter(p => !p.optional && !p.defaultValue);
const providedArgs = callSite.arguments.length;
const expectedArgs = requiredParams.length;
// Check for missing parameters
if (providedArgs < expectedArgs) {
const missingParams = requiredParams.slice(providedArgs);
breakingCalls.push({
filePath: callSite.filePath,
line: callSite.line,
column: callSite.column,
context: callSite.context,
expectedArgs,
providedArgs,
missingParams,
typeIssues: this.analyzeTypeCompatibility(func.parameters, callSite.arguments)
});
}
}
return breakingCalls;
}
// Analyze type compatibility between expected and provided arguments
analyzeTypeCompatibility(expectedParams, providedArgs) {
const typeIssues = [];
for (let i = 0; i < Math.min(expectedParams.length, providedArgs.length); i++) {
const param = expectedParams[i];
const arg = providedArgs[i];
if (param.type && this.hasTypeIncompatibility(param.type, arg)) {
typeIssues.push({
parameterName: param.name,
expectedType: param.type,
providedType: this.inferArgType(arg),
severity: 'warning'
});
}
}
return typeIssues;
}
// Check for type incompatibility (simplified heuristic)
hasTypeIncompatibility(expectedType, providedArg) {
// Simple type checking heuristics
if (expectedType.includes('string') && !providedArg.includes('"') && !providedArg.includes("'")) {
return true;
}
if (expectedType.includes('number') && isNaN(Number(providedArg))) {
return true;
}
if (expectedType.includes('boolean') && !['true', 'false'].includes(providedArg)) {
return true;
}
return false;
}
// Infer argument type from its string representation
inferArgType(arg) {
if (arg.startsWith('"') || arg.startsWith("'"))
return 'string';
if (!isNaN(Number(arg)))
return 'number';
if (['true', 'false'].includes(arg))
return 'boolean';
if (arg.startsWith('{'))
return 'object';
if (arg.startsWith('['))
return 'array';
return 'unknown';
}
// Determine severity based on analysis and breaking calls
determineSeverity(analysis, breakingCalls) {
// High confidence + breaking calls = error
if (analysis.confidence > 0.8 && breakingCalls.length > 0) {
return 'error';
}
// Any breaking calls = error
if (breakingCalls.length > 0) {
return 'error';
}
// Otherwise warning
return 'warning';
}
}
exports.EnhancedSignatureDetector = EnhancedSignatureDetector;
//# sourceMappingURL=enhanced-signature-detector.js.map