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