1const debug = require('./debug.js')
2
3const checkTree = (tree, checkUnreachable = true) => {
4  const log = [['START TREE CHECK', tree.path]]
5
6  // this can only happen in tests where we have a "tree" object
7  // that isn't actually a tree.
8  if (!tree.root || !tree.root.inventory) {
9    return tree
10  }
11
12  const { inventory } = tree.root
13  const seen = new Set()
14  const check = (node, via = tree, viaType = 'self') => {
15    log.push([
16      'CHECK',
17      node && node.location,
18      via && via.location,
19      viaType,
20      'seen=' + seen.has(node),
21      'promise=' + !!(node && node.then),
22      'root=' + !!(node && node.isRoot),
23    ])
24
25    if (!node || seen.has(node) || node.then) {
26      return
27    }
28
29    seen.add(node)
30
31    if (node.isRoot && node !== tree.root) {
32      throw Object.assign(new Error('double root'), {
33        node: node.path,
34        realpath: node.realpath,
35        tree: tree.path,
36        root: tree.root.path,
37        via: via.path,
38        viaType,
39        log,
40      })
41    }
42
43    if (node.root !== tree.root) {
44      throw Object.assign(new Error('node from other root in tree'), {
45        node: node.path,
46        realpath: node.realpath,
47        tree: tree.path,
48        root: tree.root.path,
49        via: via.path,
50        viaType,
51        otherRoot: node.root && node.root.path,
52        log,
53      })
54    }
55
56    if (!node.isRoot && node.inventory.size !== 0) {
57      throw Object.assign(new Error('non-root has non-zero inventory'), {
58        node: node.path,
59        tree: tree.path,
60        root: tree.root.path,
61        via: via.path,
62        viaType,
63        inventory: [...node.inventory.values()].map(node =>
64          [node.path, node.location]),
65        log,
66      })
67    }
68
69    if (!node.isRoot && !inventory.has(node) && !node.dummy) {
70      throw Object.assign(new Error('not in inventory'), {
71        node: node.path,
72        tree: tree.path,
73        root: tree.root.path,
74        via: via.path,
75        viaType,
76        log,
77      })
78    }
79
80    const devEdges = [...node.edgesOut.values()].filter(e => e.dev)
81    if (!node.isTop && devEdges.length) {
82      throw Object.assign(new Error('dev edges on non-top node'), {
83        node: node.path,
84        tree: tree.path,
85        root: tree.root.path,
86        via: via.path,
87        viaType,
88        devEdges: devEdges.map(e => [e.type, e.name, e.spec, e.error]),
89        log,
90      })
91    }
92
93    if (node.path === tree.root.path && node !== tree.root && !tree.root.isLink) {
94      throw Object.assign(new Error('node with same path as root'), {
95        node: node.path,
96        tree: tree.path,
97        root: tree.root.path,
98        via: via.path,
99        viaType,
100        log,
101      })
102    }
103
104    if (!node.isLink && node.path !== node.realpath) {
105      throw Object.assign(new Error('non-link with mismatched path/realpath'), {
106        node: node.path,
107        tree: tree.path,
108        realpath: node.realpath,
109        root: tree.root.path,
110        via: via.path,
111        viaType,
112        log,
113      })
114    }
115
116    const { parent, fsParent, target } = node
117    check(parent, node, 'parent')
118    check(fsParent, node, 'fsParent')
119    check(target, node, 'target')
120    log.push(['CHILDREN', node.location, ...node.children.keys()])
121    for (const kid of node.children.values()) {
122      check(kid, node, 'children')
123    }
124    for (const kid of node.fsChildren) {
125      check(kid, node, 'fsChildren')
126    }
127    for (const link of node.linksIn) {
128      check(link, node, 'linksIn')
129    }
130    for (const top of node.tops) {
131      check(top, node, 'tops')
132    }
133    log.push(['DONE', node.location])
134  }
135  check(tree)
136  if (checkUnreachable) {
137    for (const node of inventory.values()) {
138      if (!seen.has(node) && node !== tree.root) {
139        throw Object.assign(new Error('unreachable in inventory'), {
140          node: node.path,
141          realpath: node.realpath,
142          location: node.location,
143          root: tree.root.path,
144          tree: tree.path,
145          log,
146        })
147      }
148    }
149  }
150  return tree
151}
152
153// should only ever run this check in debug mode
154module.exports = tree => tree
155debug(() => module.exports = checkTree)
156