UNPKG

npm

Version:

a package manager for JavaScript

124 lines (109 loc) 4 kB
// Dep flag (dev, peer, etc.) calculation requires default or reset flags. // Flags are true by default and are unset to false as we walk deps. // We iterate outward edges looking for dep flags that can // be unset based on the current nodes flags and edge type. // Examples: // - a non-optional node with a non-optional edge out, the edge node should not be optional // - a non-peer node with a non-peer edge out, the edge node should not be peer // If a node is changed, we add to the queue and continue until no more changes. // Flags that remain after all this unsetting should be valid. // Examples: // - a node still flagged optional must only be reachable via optional edges // - a node still flagged peer must only be reachable via peer edges const calcDepFlags = (tree, resetRoot = true) => { if (resetRoot) { tree.unsetDepFlags() } const seen = new Set() const queue = [tree] let node while (node = queue.pop()) { seen.add(node) // Unset extraneous from all parents to avoid removal of children. if (!node.extraneous) { for (let n = node.resolveParent; n?.extraneous; n = n.resolveParent) { n.extraneous = false } } // for links, map their hierarchy appropriately if (node.isLink) { // node.target can be null, we check to ensure it's not null before proceeding if (node.target == null) { continue } // Only unset flags, matching the edge walk below, so multiple links to one target can't clobber a correct value. let changed = false if (node.target.dev && !node.dev) { node.target.dev = false changed = true } if (node.target.optional && !node.optional) { node.target.optional = false changed = true } if (node.target.devOptional && !node.devOptional) { node.target.devOptional = false changed = true } if (node.target.peer && !node.peer) { node.target.peer = false changed = true } if (node.target.extraneous && !node.extraneous) { node.target.extraneous = false changed = true } // queue target on first visit so its deps are walked if (changed || !seen.has(node.target)) { queue.push(node.target) } continue } for (const { peer, optional, dev, to } of node.edgesOut.values()) { // if the dep is missing, then its flags are already maximally unset if (!to) { continue } let changed = false // only optional peer dependencies should stay extraneous if (to.extraneous && !node.extraneous && !(peer && optional)) { to.extraneous = false changed = true } if (to.dev && !node.dev && !dev) { to.dev = false changed = true } if (to.optional && !node.optional && !optional) { to.optional = false changed = true } // devOptional is the *overlap* of the dev and optional tree. // A node may be depended on by separate dev and optional nodes. // It SHOULD NOT be removed when pruning dev OR optional. // It SHOULD be removed when pruning dev AND optional. // We only unset here if a node is not dev AND not optional because // if we did unset, it would prevent any overlap deeper in the tree. // We correct this later by removing if dev OR optional is set. if (to.devOptional && !node.devOptional && !node.dev && !node.optional && !dev && !optional) { to.devOptional = false changed = true } if (to.peer && !node.peer && !peer) { to.peer = false changed = true } if (changed) { queue.push(to) } } } // Remove incorrect devOptional flags now that we have walked all deps. seen.delete(tree) for (const node of seen.values()) { if (node.devOptional && (node.dev || node.optional)) { node.devOptional = false } } } module.exports = calcDepFlags