UNPKG

tree-hugger-js

Version:

A friendly tree-sitter wrapper for JavaScript and TypeScript

172 lines 5.42 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ScopeAnalyzer = exports.TreeVisitor = void 0; exports.visit = visit; class TreeVisitor { visit(node, visitor) { // Handle simple function visitor if (typeof visitor === 'function') { visitor = { enter: visitor }; } this.visitNode(node, visitor, undefined); } visitNode(node, visitor, parent) { // Call enter visitor if (visitor.enter) { const result = visitor.enter(node, parent); // If visitor returns false, stop entire traversal if (result === false) { return false; } } // Visit children for (const child of node.children) { const shouldContinue = this.visitNode(child, visitor, node); if (!shouldContinue) { return false; } } // Call exit visitor if (visitor.exit) { const result = visitor.exit(node, parent); if (result === false) { return false; } } return true; } // Utility method to collect nodes matching a condition collect(node, predicate) { const results = []; this.visit(node, n => { if (predicate(n)) { results.push(n); } }); return results; } // Find the first node matching a condition findFirst(node, predicate) { let result = null; this.visit(node, n => { if (predicate(n)) { result = n; return false; // Stop traversal } }); return result; } // Get the path from root to a specific node getPath(root, target) { const path = []; let found = false; const traverse = (node, currentPath) => { currentPath.push(node); if (node === target) { path.push(...currentPath); found = true; return false; } for (const child of node.children) { if (!traverse(child, currentPath)) { return false; } } currentPath.pop(); return true; }; traverse(root, []); return found ? path : []; } } exports.TreeVisitor = TreeVisitor; // Convenience function for visiting function visit(node, visitor) { new TreeVisitor().visit(node, visitor); } class ScopeAnalyzer { constructor() { this.scopes = new Map(); this.currentScope = null; } analyze(root) { this.scopes.clear(); // Create root scope const rootScope = { node: root, bindings: new Map(), parent: undefined, }; this.scopes.set(root, rootScope); this.currentScope = rootScope; visit(root, { enter: node => { if (this.createsScope(node)) { const scope = { node, bindings: new Map(), parent: this.currentScope ?? undefined, }; this.scopes.set(node, scope); this.currentScope = scope; } // Track variable declarations if (node.type === 'variable_declarator') { const name = node.name; if (name && this.currentScope) { this.currentScope.bindings.set(name, node); } } // Track function parameters if (node.type === 'formal_parameters' && this.currentScope) { // Look for parameter nodes which might be identifiers or patterns const params = node.findAll('identifier'); params.forEach(param => { if (param.text) { this.currentScope?.bindings.set(param.text, param); } }); } }, exit: node => { if (this.createsScope(node) && this.currentScope) { this.currentScope = this.currentScope.parent ?? null; } }, }); return this.scopes; } createsScope(node) { return [ 'function_declaration', 'function_expression', 'arrow_function', 'method_definition', 'class_declaration', 'class_expression', 'for_statement', 'for_in_statement', 'for_of_statement', 'catch_clause', ].includes(node.type); } getScope(node) { return this.scopes.get(node); } findBinding(node, name) { let current = node; while (current) { const scope = this.scopes.get(current); if (scope?.bindings.has(name)) { return scope.bindings.get(name) ?? null; } // Move up to parent scope if (!current.parent) break; current = current.parent; } return null; } } exports.ScopeAnalyzer = ScopeAnalyzer; //# sourceMappingURL=visitor.js.map