unist-util-walker
Version:
Walk unist trees with enter and leave functions
157 lines (156 loc) • 5.52 kB
JavaScript
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;
}
}