11cb0ef41Sopenharmony_ci// Perform a depth-first walk of a tree. 21cb0ef41Sopenharmony_ci// 31cb0ef41Sopenharmony_ci// `visit(node)` is called when the node is first encountered. 41cb0ef41Sopenharmony_ci// `leave(node, children)` is called when all of the node's children 51cb0ef41Sopenharmony_ci// have been left or (in the case of cyclic graphs) visited. 61cb0ef41Sopenharmony_ci// 71cb0ef41Sopenharmony_ci// Only one of visit or leave is required. (Technically both are optional, 81cb0ef41Sopenharmony_ci// but if you don't provide at least one, the tree is just walked without 91cb0ef41Sopenharmony_ci// doing anything, which is a bit pointless.) If visit is provided, and 101cb0ef41Sopenharmony_ci// leave is not, then this is a root->leaf traversal. If leave is provided, 111cb0ef41Sopenharmony_ci// and visit is not, then it's leaf->root. Both can be provided for a 121cb0ef41Sopenharmony_ci// map-reduce operation. 131cb0ef41Sopenharmony_ci// 141cb0ef41Sopenharmony_ci// If either visit or leave return a Promise for any node, then the 151cb0ef41Sopenharmony_ci// walk returns a Promise. 161cb0ef41Sopenharmony_ci 171cb0ef41Sopenharmony_ciconst depthDescent = require('./depth-descent.js') 181cb0ef41Sopenharmony_ciconst depth = ({ 191cb0ef41Sopenharmony_ci visit, 201cb0ef41Sopenharmony_ci leave, 211cb0ef41Sopenharmony_ci filter = () => true, 221cb0ef41Sopenharmony_ci seen = new Map(), 231cb0ef41Sopenharmony_ci getChildren, 241cb0ef41Sopenharmony_ci tree, 251cb0ef41Sopenharmony_ci}) => { 261cb0ef41Sopenharmony_ci if (!leave) { 271cb0ef41Sopenharmony_ci return depthDescent({ visit, filter, getChildren, tree }) 281cb0ef41Sopenharmony_ci } 291cb0ef41Sopenharmony_ci 301cb0ef41Sopenharmony_ci if (seen.has(tree)) { 311cb0ef41Sopenharmony_ci return seen.get(tree) 321cb0ef41Sopenharmony_ci } 331cb0ef41Sopenharmony_ci 341cb0ef41Sopenharmony_ci seen.set(tree, null) 351cb0ef41Sopenharmony_ci 361cb0ef41Sopenharmony_ci const visitNode = () => { 371cb0ef41Sopenharmony_ci const res = visit ? visit(tree) : tree 381cb0ef41Sopenharmony_ci if (isPromise(res)) { 391cb0ef41Sopenharmony_ci const fullResult = res.then(resThen => { 401cb0ef41Sopenharmony_ci seen.set(tree, resThen) 411cb0ef41Sopenharmony_ci return kidNodes() 421cb0ef41Sopenharmony_ci }) 431cb0ef41Sopenharmony_ci seen.set(tree, fullResult) 441cb0ef41Sopenharmony_ci return fullResult 451cb0ef41Sopenharmony_ci } else { 461cb0ef41Sopenharmony_ci seen.set(tree, res) 471cb0ef41Sopenharmony_ci return kidNodes() 481cb0ef41Sopenharmony_ci } 491cb0ef41Sopenharmony_ci } 501cb0ef41Sopenharmony_ci 511cb0ef41Sopenharmony_ci const kidNodes = () => { 521cb0ef41Sopenharmony_ci const kids = getChildren(tree, seen.get(tree)) 531cb0ef41Sopenharmony_ci return isPromise(kids) ? kids.then(processKids) : processKids(kids) 541cb0ef41Sopenharmony_ci } 551cb0ef41Sopenharmony_ci 561cb0ef41Sopenharmony_ci const processKids = nodes => { 571cb0ef41Sopenharmony_ci const kids = (nodes || []).filter(filter).map(kid => 581cb0ef41Sopenharmony_ci depth({ visit, leave, filter, seen, getChildren, tree: kid })) 591cb0ef41Sopenharmony_ci return kids.some(isPromise) 601cb0ef41Sopenharmony_ci ? Promise.all(kids).then(leaveNode) 611cb0ef41Sopenharmony_ci : leaveNode(kids) 621cb0ef41Sopenharmony_ci } 631cb0ef41Sopenharmony_ci 641cb0ef41Sopenharmony_ci const leaveNode = kids => { 651cb0ef41Sopenharmony_ci const res = leave(seen.get(tree), kids) 661cb0ef41Sopenharmony_ci seen.set(tree, res) 671cb0ef41Sopenharmony_ci // if it's a promise at this point, the caller deals with it 681cb0ef41Sopenharmony_ci return res 691cb0ef41Sopenharmony_ci } 701cb0ef41Sopenharmony_ci 711cb0ef41Sopenharmony_ci return visitNode() 721cb0ef41Sopenharmony_ci} 731cb0ef41Sopenharmony_ci 741cb0ef41Sopenharmony_ciconst isPromise = p => p && typeof p.then === 'function' 751cb0ef41Sopenharmony_ci 761cb0ef41Sopenharmony_cimodule.exports = depth 77