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.

356 lines • 14.2 kB
"use strict"; // Advanced Tree-sitter AST Parser for RipBug // Replaces SimpleParser with full semantic analysis var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TreeSitterParser = void 0; 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 TreeSitterParser { jsParser; tsParser; constructor() { // Initialize JavaScript parser this.jsParser = new tree_sitter_1.default(); this.jsParser.setLanguage(tree_sitter_javascript_1.default); // Initialize TypeScript parser this.tsParser = new tree_sitter_1.default(); this.tsParser.setLanguage(tree_sitter_typescript_1.default.typescript); } // Parse file and extract functions with full semantic analysis extractFunctions(content, filePath) { const functions = []; const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx'); try { const parser = isTypeScript ? this.tsParser : this.jsParser; const tree = parser.parse(content); this.traverseTree(tree.rootNode, content, filePath, functions); } catch (error) { console.warn(`Failed to parse ${filePath}:`, error); } return functions; } // Extract function calls with precise location and context extractFunctionCalls(content, filePath) { const calls = []; const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx'); try { const parser = isTypeScript ? this.tsParser : this.jsParser; const tree = parser.parse(content); this.findFunctionCalls(tree.rootNode, content, filePath, calls); } catch (error) { console.warn(`Failed to parse calls in ${filePath}:`, error); } return calls; } // Extract imports and exports with full dependency analysis extractImports(content, filePath) { const imports = []; const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx'); try { const parser = isTypeScript ? this.tsParser : this.jsParser; const tree = parser.parse(content); this.findImportsExports(tree.rootNode, content, filePath, imports); } catch (error) { console.warn(`Failed to parse imports in ${filePath}:`, error); } return imports; } // Traverse AST tree to find function definitions traverseTree(node, content, filePath, functions) { // Function declarations: function name() {} if (node.type === 'function_declaration') { const func = this.parseFunctionDeclaration(node, content, filePath); if (func) functions.push(func); } // Arrow functions: const name = () => {} if (node.type === 'variable_declarator') { const func = this.parseArrowFunction(node, content, filePath); if (func) functions.push(func); } // Method definitions: methodName() {} if (node.type === 'method_definition') { const func = this.parseMethodDefinition(node, content, filePath); if (func) functions.push(func); } // Recursively traverse child nodes for (let i = 0; i < node.childCount; i++) { this.traverseTree(node.child(i), content, filePath, functions); } } // Parse function declaration with full parameter analysis parseFunctionDeclaration(node, content, filePath) { const nameNode = node.childForFieldName('name'); const paramsNode = node.childForFieldName('parameters'); if (!nameNode || !paramsNode) return null; const name = nameNode.text; const parameters = this.parseParameters(paramsNode, content); 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 }; } // Parse arrow function with context analysis parseArrowFunction(node, content, filePath) { const nameNode = node.childForFieldName('name'); const valueNode = node.childForFieldName('value'); if (!nameNode || !valueNode || valueNode.type !== 'arrow_function') return null; const name = nameNode.text; const paramsNode = valueNode.childForFieldName('parameters'); const parameters = paramsNode ? this.parseParameters(paramsNode, content) : []; 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 }; } // Parse method definition in classes parseMethodDefinition(node, content, filePath) { const nameNode = node.childForFieldName('name'); const paramsNode = node.childForFieldName('parameters'); if (!nameNode || !paramsNode) return null; const name = nameNode.text; const parameters = this.parseParameters(paramsNode, content); 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 }; } // Parse function parameters with TypeScript support parseParameters(paramsNode, content) { const parameters = []; for (let i = 0; i < paramsNode.childCount; i++) { const child = paramsNode.child(i); if (!child) continue; 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, content); if (param) parameters.push(param); } } return parameters; } // Parse typed parameters (TypeScript) parseTypedParameter(node, content) { const nameNode = node.childForFieldName('pattern') || node.child(0); const typeNode = node.childForFieldName('type'); if (!nameNode) return null; // Check for default value by looking for '=' in the parameter text const hasDefaultValue = node.text.includes(' = '); let defaultValue; if (hasDefaultValue) { const match = node.text.match(/=\s*(.+)$/); defaultValue = match ? match[1].trim() : undefined; } // Extract parameter name (handle destructuring) let paramName = nameNode.text; if (nameNode.type === 'object_pattern' || nameNode.type === 'array_pattern') { // For destructuring, use the first identifier found paramName = this.extractFirstIdentifier(nameNode) || nameNode.text; } return { name: paramName, type: typeNode?.text, optional: node.type === 'optional_parameter' || hasDefaultValue, defaultValue }; } // Extract first identifier from destructuring pattern extractFirstIdentifier(node) { if (node.type === 'identifier') { return node.text; } for (let i = 0; i < node.childCount; i++) { const child = node.child(i); if (child && child.type === 'identifier') { return child.text; } } return null; } // Find function calls in AST - Step 4 Enhancement: Add member call detection findFunctionCalls(node, content, filePath, calls) { if (node.type === 'call_expression') { const funcNode = node.childForFieldName('function'); const argsNode = node.childForFieldName('arguments'); if (funcNode) { const position = node.startPosition; const line = content.split('\n')[position.row] || ''; calls.push({ name: funcNode.text, file: filePath, line: position.row + 1, column: position.column, context: line.trim(), arguments: argsNode ? this.parseArguments(argsNode) : [] }); } } // Step 4 Enhancement: Handle member calls (obj.method()) if (node.type === 'member_expression' && node.parent?.type === 'call_expression') { const memberCall = this.parseMemberCall(node, content, filePath); if (memberCall) { calls.push(memberCall); } } // Recursively traverse child nodes for (let i = 0; i < node.childCount; i++) { this.findFunctionCalls(node.child(i), content, filePath, calls); } } // Step 4 Enhancement: Parse member calls (obj.method()) parseMemberCall(node, content, filePath) { const propertyNode = node.childForFieldName('property'); if (!propertyNode) return null; const position = node.startPosition; const line = content.split('\n')[position.row] || ''; // Get the parent call expression to find arguments const callParent = node.parent; const argsNode = callParent?.childForFieldName('arguments'); return { name: propertyNode.text, file: filePath, line: position.row + 1, column: position.column, context: line.trim(), arguments: argsNode ? this.parseArguments(argsNode) : [] }; } // Parse function call arguments parseArguments(argsNode) { const args = []; for (let i = 0; i < argsNode.childCount; i++) { const child = argsNode.child(i); if (child && child.type !== ',' && child.type !== '(' && child.type !== ')') { args.push(child.text); } } return args; } // Find imports and exports findImportsExports(node, content, filePath, imports) { if (node.type === 'import_statement') { const sourceNode = node.childForFieldName('source'); const position = node.startPosition; if (sourceNode) { imports.push({ type: 'import', source: sourceNode.text.replace(/['"]/g, ''), specifiers: this.parseImportSpecifiers(node), line: position.row + 1, isDefault: this.isDefaultImport(node) }); } } if (node.type === 'export_statement') { const position = node.startPosition; imports.push({ type: 'export', source: '', specifiers: this.parseExportSpecifiers(node), line: position.row + 1, isDefault: this.isDefaultExport(node) }); } // Recursively traverse child nodes for (let i = 0; i < node.childCount; i++) { this.findImportsExports(node.child(i), content, filePath, imports); } } // Helper methods for parsing imports/exports parseImportSpecifiers(node) { // Implementation for parsing import specifiers return []; } parseExportSpecifiers(node) { // Implementation for parsing export specifiers return []; } isExported(node) { // Check if this node or its parent is an export statement let current = node; while (current) { // Direct export statement if (current.type === 'export_statement') { return true; } // Parent is export statement if (current.parent?.type === 'export_statement') { return true; } // Check if any ancestor has export in its text (fallback) if (current.parent && current.parent.text.trim().startsWith('export ')) { return true; } // Check siblings for export keyword (for cases where export is a separate node) if (current.parent) { for (let i = 0; i < current.parent.childCount; i++) { const sibling = current.parent.child(i); if (sibling && sibling.type === 'export' && sibling.text === 'export') { return true; } } } current = current.parent; } // Final fallback: check if the node text itself contains export // This should catch cases where the AST structure is different than expected const nodeText = node.text; if (nodeText.includes('export function') || nodeText.includes('export const') || nodeText.includes('export class')) { return true; } return false; } isAsync(node) { return node.text.includes('async'); } isDefaultImport(node) { return !node.text.includes('{'); } isDefaultExport(node) { return node.text.includes('export default'); } } exports.TreeSitterParser = TreeSitterParser; //# sourceMappingURL=tree-sitter-parser.js.map