estree-toolkit
Version:
Traverser, scope tracker, and more tools for working with ESTree AST
147 lines (146 loc) • 5.58 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.traverse = exports.Traverser = void 0;
const nodepath_1 = require("./nodepath");
const definitions_1 = require("./definitions");
const aliases_1 = require("./aliases");
const builders_1 = require("./builders");
class Traverser {
constructor(visitors) {
this.visitors = visitors;
}
visitPath(visitorCtx, path, state, visitedPaths, visitOnlyChildren = false) {
const { node, ctx } = path;
if (visitedPaths.has(path) ||
path.removed ||
node == null) {
return;
}
const nodeType = node.type;
const visitor = this.visitors[nodeType] || {};
ctx.newQueue();
const cleanup = () => ctx.popQueue();
if (!visitOnlyChildren) {
// NOTE: If ctx.makeScope is `false`, it can cause the parent scope to reset to `null`
path.init();
if (ctx.shouldSkip(path))
return cleanup();
if (visitor.enter != null) {
visitor.enter.call(visitorCtx, path, state);
if (ctx.shouldSkip(path) || visitorCtx.stopped)
return cleanup();
}
}
const keys = definitions_1.visitorKeys[nodeType] || Object.keys(node);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = node[key];
if (value == null)
continue;
if (Array.isArray(value)) {
const childNodePaths = value.map((childNode, index) => (nodepath_1.NodePath.for({
node: childNode,
key: index,
listKey: key,
parentPath: path,
ctx: ctx
}).init()));
for (let i = 0; i < childNodePaths.length; i++) {
this.visitPath(visitorCtx, childNodePaths[i], state, visitedPaths);
if (visitorCtx.stopped)
return cleanup();
}
}
else if (typeof value.type === 'string') {
this.visitPath(visitorCtx, nodepath_1.NodePath.for({
node: value,
key: key,
listKey: null,
parentPath: path,
ctx: ctx
}).init(), state, visitedPaths);
if (visitorCtx.stopped)
return cleanup();
}
}
if (!visitOnlyChildren && visitor.leave != null) {
visitor.leave.call(visitorCtx, path, state);
if (visitorCtx.stopped)
return cleanup();
}
visitedPaths.add(path);
const { new: newPaths, unSkipped: unSkippedPaths } = cleanup();
for (let i = 0; i < newPaths.length; i++) {
if (visitorCtx.stopped)
break;
this.visitPath(visitorCtx, newPaths[i], state, visitedPaths);
}
for (let i = 0; i < unSkippedPaths.length; i++) {
if (visitorCtx.stopped)
break;
this.visitPath(visitorCtx, unSkippedPaths[i], state, visitedPaths);
}
}
static expandVisitors(visitors) {
const expandedVisitors = Object.create(null);
// You can use functional approach here
// Because this code won't run many times like other code does
const expand = (obj) => {
Object.keys(obj).forEach((keyName) => {
const keys = [].concat(...keyName.split('|').map((key) => key in aliases_1.aliases ? Object.keys(aliases_1.aliases[key]) : [key]));
const visitor = obj[keyName];
if (typeof visitor == 'function') {
keys.forEach((key) => {
expandedVisitors[key] = { enter: visitor };
});
}
else if (typeof visitor == 'object') {
keys.forEach((key) => {
expandedVisitors[key] = {
enter: visitor.enter,
leave: visitor.leave
};
});
}
});
};
if (visitors.comp != null)
expand(visitors.comp);
expand(visitors);
return expandedVisitors;
}
static traverseNode(data) {
const visitorCtx = {
stopped: false,
stop() {
this.stopped = true;
}
};
const prevNodeValidationEnabled = (0, builders_1.getNodeValidationEnabled)();
(0, builders_1.setNodeValidationEnabled)(data.ctx.shouldValidateNodes);
new Traverser(data.expand ? this.expandVisitors(data.visitors) : data.visitors).visitPath(visitorCtx, nodepath_1.NodePath.for({
node: data.node,
key: null,
listKey: null,
parentPath: data.parentPath,
ctx: data.ctx
}), data.state, new WeakSet(), data.visitOnlyChildren);
(0, builders_1.setNodeValidationEnabled)(prevNodeValidationEnabled);
}
}
exports.Traverser = Traverser;
const traverse = (node, visitors, state) => {
const ctx = new nodepath_1.Context(visitors.$);
if (node.type !== 'Program') {
ctx.makeScope = false;
}
Traverser.traverseNode({
node: node,
parentPath: null,
visitors,
state,
ctx,
expand: true,
});
};
exports.traverse = traverse;