UNPKG

12.4 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.mapVisitor = mapVisitor;
7exports.default = exports.ALL_EDGE_TYPES = void 0;
8
9var _assert = _interopRequireDefault(require("assert"));
10
11var _utils = require("@parcel/utils");
12
13var _nullthrows = _interopRequireDefault(require("nullthrows"));
14
15function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16
17function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
18
19const ALL_EDGE_TYPES = '@@all_edge_types';
20exports.ALL_EDGE_TYPES = ALL_EDGE_TYPES;
21
22class Graph {
23 constructor(opts = {}) // flow is dumb
24 {
25 _defineProperty(this, "nodes", void 0);
26
27 _defineProperty(this, "inboundEdges", new _utils.DefaultMap(() => new _utils.DefaultMap(() => new Set())));
28
29 _defineProperty(this, "outboundEdges", new _utils.DefaultMap(() => new _utils.DefaultMap(() => new Set())));
30
31 _defineProperty(this, "rootNodeId", void 0);
32
33 this.nodes = opts.nodes || new Map();
34 this.rootNodeId = opts.rootNodeId;
35
36 if (opts.edges) {
37 for (let edge of opts.edges) {
38 this.addEdge(edge.from, edge.to, edge.type);
39 }
40 }
41 }
42
43 static deserialize(opts) {
44 return new this(opts);
45 }
46
47 serialize() {
48 return {
49 nodes: this.nodes,
50 edges: this.getAllEdges(),
51 rootNodeId: this.rootNodeId
52 };
53 } // Returns a list of all edges in the graph. This can be large, so iterating
54 // the complete list can be costly in large graphs. Used in serialization and
55 // copying of graphs.
56
57
58 getAllEdges() {
59 let edges = [];
60
61 for (let [from, edgeList] of this.outboundEdges) {
62 for (let [type, toNodes] of edgeList) {
63 for (let to of toNodes) {
64 edges.push({
65 from,
66 to,
67 type
68 });
69 }
70 }
71 }
72
73 return edges;
74 }
75
76 addNode(node) {
77 this.nodes.set(node.id, node);
78 return node;
79 }
80
81 hasNode(id) {
82 return this.nodes.has(id);
83 }
84
85 getNode(id) {
86 return this.nodes.get(id);
87 }
88
89 setRootNode(node) {
90 this.addNode(node);
91 this.rootNodeId = node.id;
92 }
93
94 getRootNode() {
95 return this.rootNodeId ? this.getNode(this.rootNodeId) : null;
96 }
97
98 addEdge(from, to, type = null) {
99 if (!this.getNode(from)) {
100 throw new Error(`"from" node '${from}' not found`);
101 }
102
103 if (!this.getNode(to)) {
104 throw new Error(`"to" node '${to}' not found`);
105 }
106
107 this.outboundEdges.get(from).get(type).add(to);
108 this.inboundEdges.get(to).get(type).add(from);
109 }
110
111 hasEdge(from, to, type = null) {
112 return this.outboundEdges.get(from).get(type).has(to);
113 }
114
115 getNodesConnectedTo(node, type = null) {
116 assertHasNode(this, node);
117 let nodes;
118
119 if (type === ALL_EDGE_TYPES) {
120 nodes = new Set();
121
122 for (let [, typeNodes] of this.inboundEdges.get(node.id)) {
123 for (let node of typeNodes) {
124 nodes.add(node);
125 }
126 }
127 } else {
128 nodes = this.inboundEdges.get(node.id).get(type).values();
129 }
130
131 return [...nodes].map(to => (0, _nullthrows.default)(this.nodes.get(to)));
132 }
133
134 getNodesConnectedFrom(node, type = null) {
135 assertHasNode(this, node);
136 let nodes;
137
138 if (type === ALL_EDGE_TYPES) {
139 nodes = new Set();
140
141 for (let [, typeNodes] of this.outboundEdges.get(node.id)) {
142 for (let node of typeNodes) {
143 nodes.add(node);
144 }
145 }
146 } else {
147 nodes = this.outboundEdges.get(node.id).get(type).values();
148 }
149
150 return [...nodes].map(to => (0, _nullthrows.default)(this.nodes.get(to)));
151 }
152
153 merge(graph) {
154 for (let [, node] of graph.nodes) {
155 this.addNode(node);
156 }
157
158 for (let edge of graph.getAllEdges()) {
159 this.addEdge(edge.from, edge.to, edge.type);
160 }
161 } // Removes node and any edges coming from or to that node
162
163
164 removeNode(node) {
165 assertHasNode(this, node);
166
167 for (let [type, nodesForType] of this.inboundEdges.get(node.id)) {
168 for (let from of nodesForType) {
169 this.removeEdge(from, node.id, type, // Do not allow orphans to be removed as this node could be one
170 // and is already being removed.
171 false
172 /* removeOrphans */
173 );
174 }
175 }
176
177 for (let [type, toNodes] of this.outboundEdges.get(node.id)) {
178 for (let to of toNodes) {
179 this.removeEdge(node.id, to, type);
180 }
181 }
182
183 let wasRemoved = this.nodes.delete(node.id);
184 (0, _assert.default)(wasRemoved);
185 }
186
187 removeById(id) {
188 let node = (0, _nullthrows.default)(this.getNode(id));
189 this.removeNode(node);
190 }
191
192 removeEdges(node, type = null) {
193 assertHasNode(this, node);
194
195 for (let to of this.outboundEdges.get(node.id).get(type)) {
196 this.removeEdge(node.id, to, type);
197 }
198 } // Removes edge and node the edge is to if the node is orphaned
199
200
201 removeEdge(from, to, type = null, removeOrphans = true) {
202 if (!this.outboundEdges.get(from).get(type).has(to)) {
203 throw new Error(`Outbound edge from ${from} to ${to} not found!`);
204 }
205
206 if (!this.inboundEdges.get(to).get(type).has(from)) {
207 throw new Error(`Inbound edge from ${to} to ${from} not found!`);
208 }
209
210 this.outboundEdges.get(from).get(type).delete(to);
211 this.inboundEdges.get(to).get(type).delete(from);
212 let connectedNode = (0, _nullthrows.default)(this.nodes.get(to));
213
214 if (removeOrphans && this.isOrphanedNode(connectedNode)) {
215 this.removeNode(connectedNode);
216 }
217 }
218
219 isOrphanedNode(node) {
220 assertHasNode(this, node);
221 let rootNode = this.getRootNode();
222
223 if (rootNode == null) {
224 // If the graph does not have a root, and there are inbound edges,
225 // this node should not be considered orphaned.
226 // return false;
227 for (let [, inboundNodeIds] of this.inboundEdges.get(node.id)) {
228 if (inboundNodeIds.size > 0) {
229 return false;
230 }
231 }
232
233 return true;
234 } // Otherwise, attempt to traverse backwards to the root. If there is a path,
235 // then this is not an orphaned node.
236
237
238 let hasPathToRoot = false;
239 this.traverseAncestors(node, (ancestor, _, actions) => {
240 if (ancestor.id === rootNode.id) {
241 hasPathToRoot = true;
242 actions.stop();
243 }
244 }, // $FlowFixMe
245 ALL_EDGE_TYPES);
246
247 if (hasPathToRoot) {
248 return false;
249 }
250
251 return true;
252 }
253
254 replaceNode(fromNode, toNode, type = null) {
255 assertHasNode(this, fromNode);
256 this.addNode(toNode);
257
258 for (let parent of this.inboundEdges.get(fromNode.id).get(type)) {
259 this.addEdge(parent, toNode.id, type);
260 this.removeEdge(parent, fromNode.id, type);
261 }
262
263 this.removeNode(fromNode);
264 } // Update a node's downstream nodes making sure to prune any orphaned branches
265
266
267 replaceNodesConnectedTo(fromNode, toNodes, replaceFilter, type = null) {
268 assertHasNode(this, fromNode);
269 let outboundEdges = this.outboundEdges.get(fromNode.id).get(type);
270 let childrenToRemove = new Set(replaceFilter ? [...outboundEdges].filter(toNodeId => replaceFilter((0, _nullthrows.default)(this.nodes.get(toNodeId)))) : outboundEdges);
271
272 for (let toNode of toNodes) {
273 this.addNode(toNode);
274 childrenToRemove.delete(toNode.id);
275
276 if (!this.hasEdge(fromNode.id, toNode.id, type)) {
277 this.addEdge(fromNode.id, toNode.id, type);
278 }
279 }
280
281 for (let child of childrenToRemove) {
282 this.removeEdge(fromNode.id, child, type);
283 }
284 }
285
286 traverse(visit, startNode, type = null) {
287 return this.dfs({
288 visit,
289 startNode,
290 getChildren: node => this.getNodesConnectedFrom(node, type)
291 });
292 }
293
294 filteredTraverse(filter, visit, startNode, type) {
295 return this.traverse(mapVisitor(filter, visit), startNode, type);
296 }
297
298 traverseAncestors(startNode, visit, type = null) {
299 return this.dfs({
300 visit,
301 startNode,
302 getChildren: node => this.getNodesConnectedTo(node, type)
303 });
304 }
305
306 dfs({
307 visit,
308 startNode,
309 getChildren
310 }) {
311 let root = startNode !== null && startNode !== void 0 ? startNode : this.getRootNode();
312
313 if (root == null) {
314 throw new Error('A start node is required to traverse');
315 }
316
317 assertHasNode(this, root);
318 let visited = new Set();
319 let stopped = false;
320 let skipped = false;
321 let actions = {
322 skipChildren() {
323 skipped = true;
324 },
325
326 stop() {
327 stopped = true;
328 }
329
330 };
331
332 let walk = (node, context) => {
333 visited.add(node);
334 skipped = false;
335 let enter = typeof visit === 'function' ? visit : visit.enter;
336
337 if (enter) {
338 let newContext = enter(node, context, actions);
339
340 if (typeof newContext !== 'undefined') {
341 context = newContext;
342 }
343 }
344
345 if (skipped) {
346 return;
347 }
348
349 if (stopped) {
350 return context;
351 }
352
353 for (let child of getChildren(node)) {
354 if (visited.has(child)) {
355 continue;
356 }
357
358 visited.add(child);
359 let result = walk(child, context);
360
361 if (stopped) {
362 return result;
363 }
364 }
365
366 if (typeof visit !== 'function' && visit.exit) {
367 let newContext = visit.exit(node, context, actions);
368
369 if (typeof newContext !== 'undefined') {
370 context = newContext;
371 }
372 }
373
374 if (skipped) {
375 return;
376 }
377
378 if (stopped) {
379 return context;
380 }
381 };
382
383 return walk(root);
384 }
385
386 bfs(visit) {
387 let root = this.getRootNode();
388
389 if (!root) {
390 throw new Error('A root node is required to traverse');
391 }
392
393 let queue = [root];
394 let visited = new Set([root]);
395
396 while (queue.length > 0) {
397 let node = queue.shift();
398 let stop = visit(node);
399
400 if (stop === true) {
401 return node;
402 }
403
404 for (let child of this.getNodesConnectedFrom(node)) {
405 if (!visited.has(child)) {
406 visited.add(child);
407 queue.push(child);
408 }
409 }
410 }
411
412 return null;
413 }
414
415 getSubGraph(node) {
416 let graph = new this.constructor();
417 graph.setRootNode(node);
418 let nodes = [];
419 this.traverse(node => {
420 nodes.push(node);
421 graph.addNode(node);
422 }, node);
423
424 for (let node of nodes) {
425 for (let [type, toNodes] of this.outboundEdges.get(node.id)) {
426 for (let to of toNodes) {
427 graph.addEdge(node.id, to, type);
428 }
429 }
430 }
431
432 return graph;
433 }
434
435 findAncestor(node, fn) {
436 let res = null;
437 this.traverseAncestors(node, (node, ctx, traversal) => {
438 if (fn(node)) {
439 res = node;
440 traversal.stop();
441 }
442 });
443 return res;
444 }
445
446 findAncestors(node, fn) {
447 let res = [];
448 this.traverseAncestors(node, (node, ctx, traversal) => {
449 if (fn(node)) {
450 res.push(node);
451 traversal.skipChildren();
452 }
453 });
454 return res;
455 }
456
457 findDescendant(node, fn) {
458 let res = null;
459 this.traverse((node, ctx, traversal) => {
460 if (fn(node)) {
461 res = node;
462 traversal.stop();
463 }
464 }, node);
465 return res;
466 }
467
468 findDescendants(node, fn) {
469 let res = [];
470 this.traverse((node, ctx, traversal) => {
471 if (fn(node)) {
472 res.push(node);
473 traversal.skipChildren();
474 }
475 }, node);
476 return res;
477 }
478
479 findNode(predicate) {
480 return Array.from(this.nodes.values()).find(predicate);
481 }
482
483 findNodes(predicate) {
484 return Array.from(this.nodes.values()).filter(predicate);
485 }
486
487}
488
489exports.default = Graph;
490
491function mapVisitor(filter, visit) {
492 return {
493 enter: (node, context, actions) => {
494 let enter = typeof visit === 'function' ? visit : visit.enter;
495
496 if (!enter) {
497 return;
498 }
499
500 let value = filter(node, actions);
501
502 if (value != null) {
503 return enter(value, context, actions);
504 }
505 },
506 exit: (node, context, actions) => {
507 if (typeof visit === 'function') {
508 return;
509 }
510
511 let exit = visit.exit;
512
513 if (!exit) {
514 return;
515 }
516
517 let value = filter(node, actions);
518
519 if (value != null) {
520 return exit(value, context, actions);
521 }
522 }
523 };
524}
525
526function assertHasNode(graph, node) {
527 if (!graph.hasNode(node.id)) {
528 throw new Error('Does not have node ' + node.id);
529 }
530}
\No newline at end of file