UNPKG

unist-util-walker

Version:

Walk unist trees with enter and leave functions

157 lines (156 loc) 5.52 kB
export function walk(node, { enter, leave }) { const instance = new Walker(enter, leave); return instance.visit(true, node); } class Walker { constructor(enter, leave) { Object.defineProperty(this, "enter", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "leave", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "context", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "should_skip", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "should_break", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "should_remove", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "should_replace", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.enter = enter; this.leave = leave; this.context = { skip: () => { this.should_skip = true; }, break: () => { this.should_break = true; }, remove: () => { this.should_remove = true; }, replace: (node) => { this.should_replace = node; }, }; this.should_skip = false; this.should_break = false; this.should_remove = false; this.should_replace = null; } visit(root, node, parent, index) { // Enter function let should_skip = false; if (this.enter) { const savedContext = { skip: this.should_skip, break: this.should_break, remove: this.should_remove, replace: this.should_replace }; this.enter.call(this.context, node, parent, index); if (this.should_replace) { if (root) { for (const key of [...Object.keys(node), ...Object.keys(this.should_replace)]) { if (key in this.should_replace) { node[key] = this.should_replace[key]; } else if (key in node) { delete node[key]; } } } else { node = this.should_replace; } } if (this.should_remove && !root) { this.should_skip = savedContext.skip; this.should_break = savedContext.break; this.should_remove = savedContext.remove; this.should_replace = savedContext.replace; return null; } if (this.should_break) { this.should_skip = savedContext.skip; this.should_break = savedContext.break; this.should_remove = savedContext.remove; this.should_replace = savedContext.replace; return node; } should_skip = this.should_skip; this.should_skip = savedContext.skip; this.should_break = savedContext.break; this.should_remove = savedContext.remove; this.should_replace = savedContext.replace; } // Recurse over children if (!should_skip && this.nodeIsParent(node)) { for (let i = 0; i < node.children.length; i++) { const returnNode = this.visit(false, node.children[i], node, i); if (returnNode) { node.children[i] = returnNode; } else { node.children.splice(i, 1); i--; } } } // Leave function if (this.leave) { const savedContext = { remove: this.should_remove, replace: this.should_replace }; this.leave.call(this.context, node, parent, index); if (this.should_replace) { if (root) { for (const key of [...Object.keys(node), ...Object.keys(this.should_replace)]) { if (key in this.should_replace) { node[key] = this.should_replace[key]; } else if (key in node) { delete node[key]; } } } else { node = this.should_replace; } } if (this.should_remove && !root) { this.should_remove = savedContext.remove; this.should_replace = savedContext.replace; return null; } this.should_remove = savedContext.remove; this.should_replace = savedContext.replace; } return node; } nodeIsParent(node) { return 'children' in node; } }