text: const { depth } = require('treeverse')
  
  const calcDepFlags = (tree, resetRoot = true) => {
    if (resetRoot) {
      tree.dev = false
      tree.optional = false
      tree.devOptional = false
      tree.peer = false
    }
    const ret = depth({
      tree,
      visit: node => calcDepFlagsStep(node),
      filter: node => node,
      getChildren: (node, tree) =>
        [...tree.edgesOut.values()].map(edge => edge.to),
    })
    return ret
  }
  
  const calcDepFlagsStep = (node) => {
    // This rewalk is necessary to handle cases where devDep and optional
    // or normal dependency graphs overlap deep in the dep graph.
    // Since we're only walking through deps that are not already flagged
    // as non-dev/non-optional, it's typically a very shallow traversal
    node.extraneous = false
    resetParents(node, 'extraneous')
    resetParents(node, 'dev')
    resetParents(node, 'peer')
    resetParents(node, 'devOptional')
    resetParents(node, 'optional')
  
    // for links, map their hierarchy appropriately
    if (node.isLink) {
      node.target.dev = node.dev
      node.target.optional = node.optional
      node.target.devOptional = node.devOptional
      node.target.peer = node.peer
      return calcDepFlagsStep(node.target)
    }
  
    node.edgesOut.forEach(({ peer, optional, dev, to }) => {
      // if the dep is missing, then its flags are already maximally unset
      if (!to) {
        return
      }
  
      // everything with any kind of edge into it is not extraneous
      to.extraneous = false
  
      // devOptional is the *overlap* of the dev and optional tree.
      // however, for convenience and to save an extra rewalk, we leave
      // it set when we are in *either* tree, and then omit it from the
      // package-lock if either dev or optional are set.
      const unsetDevOpt = !node.devOptional && !node.dev && !node.optional &&
        !dev && !optional
  
      // if we are not in the devOpt tree, then we're also not in
      // either the dev or opt trees
      const unsetDev = unsetDevOpt || !node.dev && !dev
      const unsetOpt = unsetDevOpt ||
        !node.optional && !optional
      const unsetPeer = !node.peer && !peer
  
      if (unsetPeer) {
        unsetFlag(to, 'peer')
      }
  
      if (unsetDevOpt) {
        unsetFlag(to, 'devOptional')
      }
  
      if (unsetDev) {
        unsetFlag(to, 'dev')
      }
  
      if (unsetOpt) {
        unsetFlag(to, 'optional')
      }
    })
  
    return node
  }
  
  const resetParents = (node, flag) => {
    if (node[flag]) {
      return
    }
  
    for (let p = node; p && (p === node || p[flag]); p = p.resolveParent) {
      p[flag] = false
    }
  }
  
  // typically a short walk, since it only traverses deps that
  // have the flag set.
  const unsetFlag = (node, flag) => {
    if (node[flag]) {
      node[flag] = false
      depth({
        tree: node,
        visit: node => {
          node.extraneous = node[flag] = false
          if (node.isLink) {
            node.target.extraneous = node.target[flag] = false
          }
        },
        getChildren: node => [...node.target.edgesOut.values()]
          .filter(edge => edge.to && edge.to[flag] &&
            (flag !== 'peer' && edge.type === 'peer' || edge.type === 'prod'))
          .map(edge => edge.to),
      })
    }
  }
  
  module.exports = calcDepFlags
  