ripbug-ai-detector
Version:
🔥 RipBug AI Bug Detector - Built by an AI that rips its own bugs. Destroy AI-generated bugs before you commit.
500 lines • 18.6 kB
JavaScript
"use strict";
// Enhanced AST Parser with Hybrid Tree-sitter/Regex Approach
// Following Claude S4's incremental roadmap for zero-downtime upgrades
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EnhancedASTParser = void 0;
const simple_parser_1 = require("./simple-parser");
const tree_sitter_1 = __importDefault(require("tree-sitter"));
const tree_sitter_javascript_1 = __importDefault(require("tree-sitter-javascript"));
const tree_sitter_typescript_1 = __importDefault(require("tree-sitter-typescript"));
class EnhancedASTParser {
simpleParser;
treeParser;
useTreeSitter;
fallbackToRegex;
debugMode;
constructor(config = {}) {
// Always keep the working regex parser
this.simpleParser = new simple_parser_1.SimpleParser();
// Feature flags for safe rollout
this.useTreeSitter = config.enableTreeSitter ?? false;
this.fallbackToRegex = config.fallbackToRegex ?? true;
this.debugMode = config.debugMode ?? false;
// Initialize tree-sitter only if enabled
if (this.useTreeSitter) {
try {
this.treeParser = new TreeSitterParser();
if (this.debugMode) {
console.log('✅ Tree-sitter parser initialized successfully');
}
}
catch (error) {
console.warn('⚠️ Tree-sitter initialization failed, using regex fallback:', error);
this.useTreeSitter = false;
}
}
}
// Hybrid extraction method - tries tree-sitter first, falls back to regex
extractFunctions(content, filePath) {
if (this.useTreeSitter && this.treeParser) {
try {
const treeFunctions = this.treeParser.extractFunctions(content, filePath);
if (this.debugMode) {
console.log(`🌳 Tree-sitter found ${treeFunctions.length} functions in ${filePath}`);
}
// Validate tree-sitter results
if (treeFunctions.length > 0) {
return treeFunctions;
}
// If tree-sitter found nothing, fall back to regex
if (this.fallbackToRegex) {
if (this.debugMode) {
console.log('🔄 Tree-sitter found no functions, falling back to regex');
}
return this.simpleParser.extractFunctions(content, filePath);
}
return treeFunctions;
}
catch (error) {
if (this.debugMode) {
console.warn('❌ Tree-sitter failed, falling back to regex:', error);
}
// Fallback to regex on any error
if (this.fallbackToRegex) {
return this.simpleParser.extractFunctions(content, filePath);
}
// If fallback disabled, re-throw error
throw error;
}
}
// Use regex parser (default/fallback)
if (this.debugMode) {
console.log(`📝 Using regex parser for ${filePath}`);
}
return this.simpleParser.extractFunctions(content, filePath);
}
// Extract function calls with enhanced tree-sitter analysis
extractFunctionCalls(content, filePath) {
if (this.useTreeSitter && this.treeParser) {
try {
return this.treeParser.extractFunctionCalls(content, filePath);
}
catch (error) {
if (this.debugMode) {
console.warn('❌ Tree-sitter call extraction failed, falling back to regex:', error);
}
if (this.fallbackToRegex) {
const simpleCalls = this.simpleParser.extractFunctionCalls(content, filePath);
return simpleCalls.map(call => ({
...call,
arguments: [],
argumentCount: 0,
callType: 'function'
}));
}
throw error;
}
}
const simpleCalls = this.simpleParser.extractFunctionCalls(content, filePath);
return simpleCalls.map(call => ({
...call,
arguments: [],
argumentCount: 0,
callType: 'function'
}));
}
// Get parser statistics for monitoring
getParserStats() {
return {
usingTreeSitter: this.useTreeSitter,
fallbackEnabled: this.fallbackToRegex,
parserType: this.useTreeSitter ?
(this.fallbackToRegex ? 'hybrid' : 'tree-sitter') :
'regex'
};
}
// Enable/disable tree-sitter at runtime
setTreeSitterEnabled(enabled) {
this.useTreeSitter = enabled;
if (enabled && !this.treeParser) {
try {
this.treeParser = new TreeSitterParser();
if (this.debugMode) {
console.log('✅ Tree-sitter parser enabled at runtime');
}
}
catch (error) {
console.warn('⚠️ Failed to enable tree-sitter at runtime:', error);
this.useTreeSitter = false;
}
}
}
}
exports.EnhancedASTParser = EnhancedASTParser;
// Basic Tree-sitter Parser Implementation
class TreeSitterParser {
jsParser;
tsParser;
constructor() {
this.jsParser = new tree_sitter_1.default();
this.jsParser.setLanguage(tree_sitter_javascript_1.default);
this.tsParser = new tree_sitter_1.default();
this.tsParser.setLanguage(tree_sitter_typescript_1.default.typescript);
}
extractFunctions(content, filePath) {
const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx');
const parser = isTypeScript ? this.tsParser : this.jsParser;
try {
const tree = parser.parse(content);
return this.walkAST(tree.rootNode, filePath);
}
catch (error) {
throw new Error(`Tree-sitter parsing failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
extractFunctionCalls(content, filePath) {
const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx');
const parser = isTypeScript ? this.tsParser : this.jsParser;
try {
const tree = parser.parse(content);
return this.extractCallsFromAST(tree.rootNode, filePath, content);
}
catch (error) {
throw new Error(`Tree-sitter call extraction failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
walkAST(node, filePath) {
const functions = [];
// Handle function declarations
if (node.type === 'function_declaration') {
const func = this.parseFunctionDeclaration(node, filePath);
if (func)
functions.push(func);
}
// Handle arrow functions in variable declarations
if (node.type === 'variable_declarator') {
const func = this.parseArrowFunction(node, filePath);
if (func)
functions.push(func);
}
// Handle method definitions in classes
if (node.type === 'method_definition') {
const func = this.parseMethodDefinition(node, filePath);
if (func)
functions.push(func);
}
// Recursively process children
if (node.children) {
for (const child of node.children) {
functions.push(...this.walkAST(child, filePath));
}
}
return functions;
}
// Enhanced call extraction with sophisticated AST analysis
extractCallsFromAST(node, filePath, content) {
const calls = [];
const traverse = (currentNode) => {
// Handle function calls: functionName()
if (currentNode.type === 'call_expression') {
const call = this.parseCallExpression(currentNode, filePath, content);
if (call)
calls.push(call);
}
// Handle method calls: obj.method()
if (currentNode.type === 'member_expression') {
const memberCall = this.parseMemberCall(currentNode, filePath, content);
if (memberCall)
calls.push(memberCall);
}
// Handle constructor calls: new ClassName()
if (currentNode.type === 'new_expression') {
const constructorCall = this.parseConstructorCall(currentNode, filePath, content);
if (constructorCall)
calls.push(constructorCall);
}
// Recursively traverse children
if (currentNode.children) {
for (const child of currentNode.children) {
traverse(child);
}
}
};
traverse(node);
return calls;
}
parseFunctionDeclaration(node, filePath) {
try {
const nameNode = this.findChildByType(node, 'identifier');
if (!nameNode)
return null;
const name = nameNode.text;
const parameters = this.parseParameters(node);
const position = node.startPosition;
return {
name,
parameters,
file: filePath,
line: position.row + 1,
column: position.column,
isExported: this.isExported(node),
isAsync: this.isAsync(node),
isArrow: false
};
}
catch (error) {
return null;
}
}
parseArrowFunction(node, filePath) {
try {
const nameNode = this.findChildByType(node, 'identifier');
const valueNode = this.findChildByType(node, 'arrow_function');
if (!nameNode || !valueNode)
return null;
const name = nameNode.text;
const parameters = this.parseParameters(valueNode);
const position = node.startPosition;
return {
name,
parameters,
file: filePath,
line: position.row + 1,
column: position.column,
isExported: this.isExported(node.parent),
isAsync: this.isAsync(valueNode),
isArrow: true
};
}
catch (error) {
return null;
}
}
parseMethodDefinition(node, filePath) {
try {
const nameNode = this.findChildByType(node, 'property_identifier') ||
this.findChildByType(node, 'identifier');
if (!nameNode)
return null;
const name = nameNode.text;
const parameters = this.parseParameters(node);
const position = node.startPosition;
return {
name,
parameters,
file: filePath,
line: position.row + 1,
column: position.column,
isExported: this.isExported(node.parent),
isAsync: this.isAsync(node),
isArrow: false
};
}
catch (error) {
return null;
}
}
// Enhanced call expression parsing with argument counting
parseCallExpression(node, filePath, content) {
try {
const functionNode = this.findChildByType(node, 'identifier') ||
this.findChildByType(node, 'member_expression');
if (!functionNode)
return null;
const position = node.startPosition;
const line = position.row + 1;
const lineContent = content.split('\n')[position.row] || '';
const args = this.parseArguments(node);
return {
name: functionNode.text,
file: filePath,
line,
column: position.column,
context: lineContent.trim(),
arguments: args,
argumentCount: args.length,
callType: 'function'
};
}
catch (error) {
return null;
}
}
// Parse method calls: obj.method()
parseMemberCall(node, filePath, content) {
try {
// Look for parent call_expression
let parent = node.parent;
while (parent && parent.type !== 'call_expression') {
parent = parent.parent;
}
if (!parent)
return null;
const propertyNode = this.findChildByType(node, 'property_identifier');
if (!propertyNode)
return null;
const position = parent.startPosition;
const line = position.row + 1;
const lineContent = content.split('\n')[position.row] || '';
const args = this.parseArguments(parent);
return {
name: propertyNode.text,
file: filePath,
line,
column: position.column,
context: lineContent.trim(),
arguments: args,
argumentCount: args.length,
callType: 'method'
};
}
catch (error) {
return null;
}
}
// Parse constructor calls: new ClassName()
parseConstructorCall(node, filePath, content) {
try {
const constructorNode = this.findChildByType(node, 'identifier');
if (!constructorNode)
return null;
const position = node.startPosition;
const line = position.row + 1;
const lineContent = content.split('\n')[position.row] || '';
const args = this.parseArguments(node);
return {
name: constructorNode.text,
file: filePath,
line,
column: position.column,
context: lineContent.trim(),
arguments: args,
argumentCount: args.length,
callType: 'constructor'
};
}
catch (error) {
return null;
}
}
parseParameters(node) {
const parameters = [];
try {
const paramsNode = this.findChildByType(node, 'formal_parameters');
if (!paramsNode || !paramsNode.children)
return parameters;
for (const child of paramsNode.children) {
if (child.type === 'identifier') {
parameters.push({
name: child.text,
optional: false
});
}
else if (child.type === 'required_parameter' || child.type === 'optional_parameter') {
const param = this.parseTypedParameter(child);
if (param)
parameters.push(param);
}
}
}
catch (error) {
// Return empty parameters on error
}
return parameters;
}
parseTypedParameter(node) {
try {
const nameNode = this.findChildByType(node, 'identifier');
if (!nameNode)
return null;
return {
name: nameNode.text,
optional: node.type === 'optional_parameter',
type: this.extractTypeAnnotation(node)
};
}
catch (error) {
return null;
}
}
// Enhanced argument parsing with better accuracy
parseArguments(node) {
const args = [];
try {
const argsNode = this.findChildByType(node, 'arguments');
if (!argsNode || !argsNode.children)
return args;
let currentArg = '';
let parenDepth = 0;
let bracketDepth = 0;
let braceDepth = 0;
for (const child of argsNode.children) {
const text = child.text;
if (child.type === '(') {
parenDepth++;
if (parenDepth === 1)
continue; // Skip opening paren
}
else if (child.type === ')') {
parenDepth--;
if (parenDepth === 0)
break; // Skip closing paren
}
else if (child.type === ',' && parenDepth === 1 && bracketDepth === 0 && braceDepth === 0) {
// This is a top-level comma separator
if (currentArg.trim()) {
args.push(currentArg.trim());
currentArg = '';
}
continue;
}
// Track nested structures
if (text.includes('['))
bracketDepth++;
if (text.includes(']'))
bracketDepth--;
if (text.includes('{'))
braceDepth++;
if (text.includes('}'))
braceDepth--;
currentArg += text;
}
// Add the last argument
if (currentArg.trim()) {
args.push(currentArg.trim());
}
}
catch (error) {
// Return empty arguments on error
}
return args;
}
// Helper methods
findChildByType(node, type) {
if (!node.children)
return null;
for (const child of node.children) {
if (child.type === type)
return child;
}
return null;
}
isExported(node) {
if (!node)
return false;
return node.text && node.text.includes('export');
}
isAsync(node) {
if (!node)
return false;
return node.text && node.text.includes('async');
}
extractTypeAnnotation(node) {
const typeNode = this.findChildByType(node, 'type_annotation');
return typeNode ? typeNode.text.replace(':', '').trim() : undefined;
}
}
//# sourceMappingURL=ast-parser-enhanced.js.map