@suchipi/traverse
Version:
Basic depth-first traversal function
72 lines (71 loc) • 2.33 kB
JavaScript
;
const stop = Symbol("stop");
function isNonPrimitive(value) {
return ((typeof value === "object" && value != null) || typeof value === "function");
}
function doTraverse(obj, descriptor, filter, beforeCallback, afterCallback, path, seens) {
if (seens.has(obj)) {
return;
}
if (isNonPrimitive(obj)) {
seens.add(obj);
}
if (beforeCallback) {
const result = beforeCallback(obj, path, descriptor);
if (result === stop) {
return;
}
}
if (Array.isArray(obj)) {
const children = Array.from(obj);
for (let i = 0; i < children.length; i++) {
const childPath = path.concat(i);
const childDescriptor = Object.getOwnPropertyDescriptor(obj, i);
if (filter(childDescriptor, childPath)) {
const child = children[i];
doTraverse(child, childDescriptor, filter, beforeCallback, afterCallback, childPath, seens);
}
}
}
else if (isNonPrimitive(obj)) {
const descriptors = Object.getOwnPropertyDescriptors(obj);
const keys = Object.keys(descriptors);
for (const key of keys) {
const childPath = path.concat(key);
const childDescriptor = descriptors[key];
if (filter(childDescriptor, childPath)) {
const value = obj[key];
doTraverse(value, childDescriptor, filter, beforeCallback, afterCallback, childPath, seens);
}
}
}
if (afterCallback) {
afterCallback(obj, path, descriptor);
}
}
const defaultFilter = (descriptor, _path) => !descriptor.get;
function traverse(
/**
* The tree structure to traverse.
*/
tree,
/**
* Callbacks to call during traversal
*/
callbacks) {
const { before, after, filter = defaultFilter } = callbacks ?? {};
const syntheticRootDescriptor = {
value: tree,
writable: true,
enumerable: true,
configurable: true,
};
if (!filter(syntheticRootDescriptor, [])) {
return;
}
const setConstructor = typeof WeakSet === "undefined" ? Set : WeakSet;
doTraverse(tree, syntheticRootDescriptor, filter, before, after, [], new setConstructor());
}
traverse.stop = stop;
traverse.default = traverse;
module.exports = traverse;