UNPKG

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