1 |
|
2 |
|
3 | import type {Edge, Node, NodeId} from './types';
|
4 | import type {TraversalActions, GraphVisitor} from '@parcel/types';
|
5 |
|
6 | import assert from 'assert';
|
7 | import {DefaultMap} from '@parcel/utils';
|
8 | import nullthrows from 'nullthrows';
|
9 |
|
10 | export type GraphOpts<TNode, TEdgeType: string | null = null> = {|
|
11 | nodes?: Map<NodeId, TNode>,
|
12 | edges?: Array<Edge<TEdgeType | null>>,
|
13 | rootNodeId?: ?NodeId,
|
14 | |};
|
15 |
|
16 | type AdjacencyList<TEdgeType> = DefaultMap<
|
17 | NodeId,
|
18 | DefaultMap<TEdgeType, Set<NodeId>>,
|
19 | >;
|
20 |
|
21 | export const ALL_EDGE_TYPES = '@@all_edge_types';
|
22 |
|
23 | export 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),
|
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 |
|
59 |
|
60 |
|
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 |
|
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 |
|
190 |
|
191 | false ,
|
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 |
|
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 |
|
266 |
|
267 |
|
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 |
|
278 |
|
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 |
|
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 |
|
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 |
|
559 | export 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 |
|
593 | function 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 | }
|