codekingpro/portable-devtools
114k
1// Dep flag (dev, peer, etc.) calculation requires default or reset flags.
2// Flags are true by default and are unset to false as we walk deps.
3// We iterate outward edges looking for dep flags that can
4// be unset based on the current nodes flags and edge type.
5// Examples:
6// - a non-optional node with a non-optional edge out, the edge node should not be optional
7// - a non-peer node with a non-peer edge out, the edge node should not be peer
8// If a node is changed, we add to the queue and continue until no more changes.
9// Flags that remain after all this unsetting should be valid.
10// Examples:
11// - a node still flagged optional must only be reachable via optional edges
12// - a node still flagged peer must only be reachable via peer edges
13const calcDepFlags = (tree, resetRoot = true) => {
14 if (resetRoot) {
15 tree.unsetDepFlags()
16 }
17
18 const seen = new Set()
19 const queue = [tree]
20
21 let node
22 while (node = queue.pop()) {
23 seen.add(node)
24
25 // Unset extraneous from all parents to avoid removal of children.
26 if (!node.extraneous) {
27 for (let n = node.resolveParent; n?.extraneous; n = n.resolveParent) {
28 n.extraneous = false
29 }
30 }
31
32 // for links, map their hierarchy appropriately
33 if (node.isLink) {
34 // node.target can be null, we check to ensure it's not null before proceeding
35 if (node.target == null) {
36 continue
37 }
38 node.target.dev = node.dev
39 node.target.optional = node.optional
40 node.target.devOptional = node.devOptional
41 node.target.peer = node.peer
42 node.target.extraneous = node.extraneous
43 queue.push(node.target)
44 continue
45 }
46
47 for (const { peer, optional, dev, to } of node.edgesOut.values()) {
48 // if the dep is missing, then its flags are already maximally unset
49 if (!to) {
50 continue
51 }
52
53 let changed = false
54
55 // only optional peer dependencies should stay extraneous
56 if (to.extraneous && !node.extraneous && !(peer && optional)) {
57 to.extraneous = false
58 changed = true
59 }
60
61 if (to.dev && !node.dev && !dev) {
62 to.dev = false
63 changed = true
64 }
65
66 if (to.optional && !node.optional && !optional) {
67 to.optional = false
68 changed = true
69 }
70
71 // devOptional is the *overlap* of the dev and optional tree.
72 // A node may be depended on by separate dev and optional nodes.
73 // It SHOULD NOT be removed when pruning dev OR optional.
74 // It SHOULD be removed when pruning dev AND optional.
75 // We only unset here if a node is not dev AND not optional because
76 // if we did unset, it would prevent any overlap deeper in the tree.
77 // We correct this later by removing if dev OR optional is set.
78 if (to.devOptional && !node.devOptional && !node.dev && !node.optional && !dev && !optional) {
79 to.devOptional = false
80 changed = true
81 }
82
83 if (to.peer && !node.peer && !peer) {
84 to.peer = false
85 changed = true
86 }
87
88 if (changed) {
89 queue.push(to)
90 }
91 }
92 }
93
94 // Remove incorrect devOptional flags now that we have walked all deps.
95 seen.delete(tree)
96 for (const node of seen.values()) {
97 if (node.devOptional && (node.dev || node.optional)) {
98 node.devOptional = false
99 }
100 }
101}
102
103module.exports = calcDepFlags
104 