UNPKG

tree-hugger-js

Version:

A friendly tree-sitter wrapper for JavaScript and TypeScript

282 lines 9.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TreeNode = void 0; const pattern_parser_1 = require("./pattern-parser"); const visitor_1 = require("./visitor"); const errors_1 = require("./errors"); class TreeNode { constructor(node, sourceCode, parent) { this.node = node; this.sourceCode = sourceCode; this.parent = parent; // Defensive check for undefined node - addresses tree-sitter race condition in CI environments if (!node) { throw new errors_1.ParseError('TreeNode constructor received undefined SyntaxNode. This may be caused by a race condition in tree-sitter native bindings, commonly seen in CI environments with concurrent test execution.'); } // Validate that the node has required properties if (typeof node.type === 'undefined') { throw new errors_1.ParseError('TreeNode constructor received invalid SyntaxNode - missing type property. This indicates a problem with tree-sitter native binding initialization.'); } } get text() { return this.sourceCode.slice(this.node.startIndex, this.node.endIndex); } get type() { return this.node.type; } get startPosition() { return this.node.startPosition; } get endPosition() { return this.node.endPosition; } get children() { this._children ?? (this._children = this.node.children.map(child => new TreeNode(child, this.sourceCode, this))); return this._children; } get line() { return this.startPosition.row + 1; } get column() { return this.startPosition.column + 1; } get endLine() { return this.endPosition.row + 1; } get hasError() { return this.node.hasError; } get name() { const nameNode = this.node.childForFieldName('name'); return nameNode ? this.sourceCode.slice(nameNode.startIndex, nameNode.endIndex) : undefined; } /** * Extract parameters from function-like nodes (functions, methods, arrow functions) */ extractParameters() { const parameters = []; // Handle different function types if (this.type === 'function_declaration' || this.type === 'function_expression' || this.type === 'method_definition' || this.type === 'arrow_function') { const paramsNode = this.node.childForFieldName('parameters'); if (paramsNode) { const paramsWrapper = new TreeNode(paramsNode, this.sourceCode, this); // Extract formal parameters - look directly in the formal_parameters node for (const child of paramsWrapper.children) { if (child.type === 'identifier' || child.type === 'rest_pattern' || child.type === 'assignment_pattern' || child.type === 'object_pattern' || child.type === 'array_pattern' || child.type === 'required_parameter') { parameters.push(child.text); } } } else { // For some function types, look for formal_parameters as direct child for (const child of this.children) { if (child.type === 'formal_parameters') { for (const param of child.children) { if (param.type === 'identifier' || param.type === 'rest_pattern' || param.type === 'assignment_pattern' || param.type === 'object_pattern' || param.type === 'array_pattern' || param.type === 'required_parameter') { parameters.push(param.text); } } } } } } return parameters; } /** * Check if this function-like node is async */ isAsync() { if (this.type === 'function_declaration' || this.type === 'function_expression' || this.type === 'method_definition' || this.type === 'arrow_function') { // Check for async modifier for (const child of this.children) { if (child.type === 'async' || child.text === 'async') { return true; } } // Also check the text content as fallback return this.text.includes('async'); } return false; } /** * Get the body range of a function or class */ getBodyRange() { const bodyNode = this.node.childForFieldName('body'); if (bodyNode) { return { startLine: bodyNode.startPosition.row + 1, endLine: bodyNode.endPosition.row + 1, }; } return null; } // Navigation methods find(pattern) { const predicate = this.parsePattern(pattern); return this.findNode(predicate); } findAll(pattern) { const predicate = this.parsePattern(pattern); return this.findAllNodes(predicate); } findNode(predicate) { if (predicate(this)) return this; for (const child of this.children) { const result = child.findNode(predicate); if (result) return result; } return null; } findAllNodes(predicate) { const results = []; if (predicate(this)) { results.push(this); } for (const child of this.children) { results.push(...child.findAllNodes(predicate)); } return results; } parsePattern(pattern) { try { return new pattern_parser_1.PatternParser().parse(pattern); } catch { // Fallback to simple type matching for backward compatibility return (node) => node.type === pattern; } } // Common queries functions() { return this.findAll('function'); } classes() { return this.findAll('class'); } imports() { return this.findAll('import_statement'); } variables() { return this.findAll('variable_declarator'); } comments() { return this.findAll('comment'); } // Export analysis exports() { return this.findAll('export_statement').concat(this.findAll('export_specifier')); } // JSX-specific helpers jsxComponents() { return this.findAll('jsx_element').concat(this.findAll('jsx_self_closing_element')); } jsxProps(componentName) { const components = componentName ? this.jsxComponents().filter(c => { const opening = c.find('jsx_opening_element'); const name = opening?.find('identifier')?.text ?? opening?.find('member_expression')?.text; return name === componentName; }) : this.jsxComponents(); const props = []; components.forEach(component => { props.push(...component.findAll('jsx_attribute')); }); return props; } // React hooks hooks() { return this.findAll('call_expression').filter(call => { const func = call.node.childForFieldName('function'); return func && func.text.startsWith('use') && /^use[A-Z]/.test(func.text); }); } // Parent/sibling navigation getParent(type) { let current = this.parent; while (current) { if (!type || current.type === type) return current; current = current.parent; } return null; } siblings() { if (!this.parent) return []; return this.parent.children.filter(child => child !== this); } ancestors() { const result = []; let current = this.parent; while (current) { result.push(current); current = current.parent; } return result; } descendants(type) { return type ? this.findAll(type) : this.getAllDescendants(); } getAllDescendants() { const result = []; for (const child of this.children) { result.push(child); result.push(...child.getAllDescendants()); } return result; } // Visitor pattern support visit(visitor) { (0, visitor_1.visit)(this, visitor); } // Get path from this node to root getPath() { const path = [this]; let current = this.parent; while (current) { path.unshift(current); current = current.parent; } return path; } // Find node at specific position nodeAt(line, column) { const pos = { row: line - 1, column: column - 1 }; if (this.startPosition.row > pos.row || (this.startPosition.row === pos.row && this.startPosition.column > pos.column) || this.endPosition.row < pos.row || (this.endPosition.row === pos.row && this.endPosition.column < pos.column)) { return null; } // Check children first (more specific) for (const child of this.children) { const found = child.nodeAt(line, column); if (found) return found; } // This node contains the position return this; } } exports.TreeNode = TreeNode; //# sourceMappingURL=node-wrapper.js.map