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