UNPKG

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
"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