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