@parcel/graph 3.2.1-dev.3268 → 3.2.1-dev.3303

Sign up to get free protection for your applications and to get access to all the features.
@@ -240,7 +240,7 @@ class AdjacencyList {
240
240
  *
241
241
  * Note that this method does not increment the node count
242
242
  * (that only happens in `addEdge`), it _may_ preemptively resize
243
- * the nodes array if it is at capacity, under the asumption that
243
+ * the nodes array if it is at capacity, under the assumption that
244
244
  * at least 1 edge to or from this new node will be added.
245
245
  *
246
246
  * Returns the id of the added node.
@@ -33,12 +33,12 @@ class ContentGraph extends _Graph.default {
33
33
 
34
34
  // $FlowFixMe[prop-missing]
35
35
  static deserialize(opts) {
36
- // $FlowFixMe
37
36
  return new ContentGraph(opts);
38
37
  }
39
38
 
40
39
  // $FlowFixMe[prop-missing]
41
40
  serialize() {
41
+ // $FlowFixMe[prop-missing]
42
42
  return {
43
43
  ...super.serialize(),
44
44
  _contentKeyToNodeId: this._contentKeyToNodeId,
package/lib/Graph.js CHANGED
@@ -17,6 +17,15 @@ function _nullthrows() {
17
17
  }
18
18
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19
19
  const ALL_EDGE_TYPES = exports.ALL_EDGE_TYPES = -1;
20
+
21
+ /**
22
+ * Internal type used for queue iterative DFS implementation.
23
+ */
24
+
25
+ /**
26
+ * Options for DFS traversal.
27
+ */
28
+
20
29
  class Graph {
21
30
  constructor(opts) {
22
31
  this.nodes = (opts === null || opts === void 0 ? void 0 : opts.nodes) || [];
@@ -197,7 +206,7 @@ class Graph {
197
206
  });
198
207
  }
199
208
  dfsFast(visit, startNodeId) {
200
- let traversalStartNode = (0, _nullthrows().default)(startNodeId !== null && startNodeId !== void 0 ? startNodeId : this.rootNodeId, 'A start node is required to traverse');
209
+ let traversalStartNode = (0, _nullthrows().default)(startNodeId ?? this.rootNodeId, 'A start node is required to traverse');
201
210
  this._assertHasNodeId(traversalStartNode);
202
211
  let visited;
203
212
  if (!this._visited || this._visited.capacity < this.nodes.length) {
@@ -259,7 +268,7 @@ class Graph {
259
268
 
260
269
  // A post-order implementation of dfsFast
261
270
  postOrderDfsFast(visit, startNodeId) {
262
- let traversalStartNode = (0, _nullthrows().default)(startNodeId !== null && startNodeId !== void 0 ? startNodeId : this.rootNodeId, 'A start node is required to traverse');
271
+ let traversalStartNode = (0, _nullthrows().default)(startNodeId ?? this.rootNodeId, 'A start node is required to traverse');
263
272
  this._assertHasNodeId(traversalStartNode);
264
273
  let visited;
265
274
  if (!this._visited || this._visited.capacity < this.nodes.length) {
@@ -301,12 +310,18 @@ class Graph {
301
310
  }
302
311
  this._visited = visited;
303
312
  }
313
+
314
+ /**
315
+ * Iterative implementation of DFS that supports all use-cases.
316
+ *
317
+ * This replaces `dfs` and will replace `dfsFast`.
318
+ */
304
319
  dfs({
305
320
  visit,
306
321
  startNodeId,
307
322
  getChildren
308
323
  }) {
309
- let traversalStartNode = (0, _nullthrows().default)(startNodeId !== null && startNodeId !== void 0 ? startNodeId : this.rootNodeId, 'A start node is required to traverse');
324
+ let traversalStartNode = (0, _nullthrows().default)(startNodeId ?? this.rootNodeId, 'A start node is required to traverse');
310
325
  this._assertHasNodeId(traversalStartNode);
311
326
  let visited;
312
327
  if (!this._visited || this._visited.capacity < this.nodes.length) {
@@ -328,53 +343,76 @@ class Graph {
328
343
  stopped = true;
329
344
  }
330
345
  };
331
- let walk = (nodeId, context) => {
332
- if (!this.hasNode(nodeId)) return;
333
- visited.add(nodeId);
334
- skipped = false;
335
- let enter = typeof visit === 'function' ? visit : visit.enter;
336
- if (enter) {
337
- let newContext = enter(nodeId, context, actions);
346
+ const queue = [{
347
+ nodeId: traversalStartNode,
348
+ context: null
349
+ }];
350
+ const enter = typeof visit === 'function' ? visit : visit.enter;
351
+ while (queue.length !== 0) {
352
+ const command = queue.pop();
353
+ if (command.exit != null) {
354
+ let {
355
+ nodeId,
356
+ context,
357
+ exit
358
+ } = command;
359
+ let newContext = exit(nodeId, command.context, actions);
338
360
  if (typeof newContext !== 'undefined') {
339
361
  // $FlowFixMe[reassign-const]
340
362
  context = newContext;
341
363
  }
342
- }
343
- if (skipped) {
344
- return;
345
- }
346
- if (stopped) {
347
- return context;
348
- }
349
- for (let child of getChildren(nodeId)) {
350
- if (visited.has(child)) {
364
+ if (skipped) {
351
365
  continue;
352
366
  }
353
- visited.add(child);
354
- let result = walk(child, context);
355
367
  if (stopped) {
356
- return result;
368
+ this._visited = visited;
369
+ return context;
357
370
  }
358
- }
359
- if (typeof visit !== 'function' && visit.exit &&
360
- // Make sure the graph still has the node: it may have been removed between enter and exit
361
- this.hasNode(nodeId)) {
362
- let newContext = visit.exit(nodeId, context, actions);
363
- if (typeof newContext !== 'undefined') {
364
- // $FlowFixMe[reassign-const]
365
- context = newContext;
371
+ } else {
372
+ let {
373
+ nodeId,
374
+ context
375
+ } = command;
376
+ if (!this.hasNode(nodeId) || visited.has(nodeId)) continue;
377
+ visited.add(nodeId);
378
+ skipped = false;
379
+ if (enter) {
380
+ let newContext = enter(nodeId, context, actions);
381
+ if (typeof newContext !== 'undefined') {
382
+ // $FlowFixMe[reassign-const]
383
+ context = newContext;
384
+ }
385
+ }
386
+ if (skipped) {
387
+ continue;
388
+ }
389
+ if (stopped) {
390
+ this._visited = visited;
391
+ return context;
392
+ }
393
+ if (typeof visit !== 'function' && visit.exit) {
394
+ queue.push({
395
+ nodeId,
396
+ exit: visit.exit,
397
+ context
398
+ });
399
+ }
400
+
401
+ // TODO turn into generator function
402
+ const children = getChildren(nodeId);
403
+ for (let i = children.length - 1; i > -1; i -= 1) {
404
+ const child = children[i];
405
+ if (visited.has(child)) {
406
+ continue;
407
+ }
408
+ queue.push({
409
+ nodeId: child,
410
+ context
411
+ });
366
412
  }
367
413
  }
368
- if (skipped) {
369
- return;
370
- }
371
- if (stopped) {
372
- return context;
373
- }
374
- };
375
- let result = walk(traversalStartNode);
414
+ }
376
415
  this._visited = visited;
377
- return result;
378
416
  }
379
417
  bfs(visit) {
380
418
  let rootNodeId = (0, _nullthrows().default)(this.rootNodeId, 'A root node is required to traverse');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parcel/graph",
3
- "version": "3.2.1-dev.3268+10d19ff8a",
3
+ "version": "3.2.1-dev.3303+3edb0d741",
4
4
  "description": "Blazing fast, zero configuration web application bundler",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -17,10 +17,11 @@
17
17
  "main": "lib/index.js",
18
18
  "source": "src/index.js",
19
19
  "engines": {
20
- "node": ">= 12.0.0"
20
+ "node": ">= 16.0.0"
21
21
  },
22
22
  "dependencies": {
23
+ "@parcel/feature-flags": "2.12.1-dev.3303+3edb0d741",
23
24
  "nullthrows": "^1.1.1"
24
25
  },
25
- "gitHead": "10d19ff8a34dd8f479847aadf9865be48a8fad84"
26
+ "gitHead": "3edb0d7419831e49cfa7ea8c52b4f7127a16ae52"
26
27
  }
@@ -30,7 +30,7 @@ export type AdjacencyListOptions<TEdgeType> = {|
30
30
  minGrowFactor?: number,
31
31
  /** The size after which to grow the capacity by the minimum factor. */
32
32
  peakCapacity?: number,
33
- /** The percentage of deleted edges above which the capcity should shink. */
33
+ /** The percentage of deleted edges above which the capacity should shrink. */
34
34
  unloadFactor?: number,
35
35
  /** The amount by which to shrink the capacity. */
36
36
  shrinkFactor?: number,
@@ -328,7 +328,7 @@ export default class AdjacencyList<TEdgeType: number = 1> {
328
328
  *
329
329
  * Note that this method does not increment the node count
330
330
  * (that only happens in `addEdge`), it _may_ preemptively resize
331
- * the nodes array if it is at capacity, under the asumption that
331
+ * the nodes array if it is at capacity, under the assumption that
332
332
  * at least 1 edge to or from this new node will be added.
333
333
  *
334
334
  * Returns the id of the added node.
@@ -12,7 +12,6 @@ export type ContentGraphOpts<TNode, TEdgeType: number = 1> = {|
12
12
  export type SerializedContentGraph<TNode, TEdgeType: number = 1> = {|
13
13
  ...SerializedGraph<TNode, TEdgeType>,
14
14
  _contentKeyToNodeId: Map<ContentKey, NodeId>,
15
- _nodeIdToContentKey: Map<NodeId, ContentKey>,
16
15
  |};
17
16
 
18
17
  export default class ContentGraph<TNode, TEdgeType: number = 1> extends Graph<
@@ -37,14 +36,14 @@ export default class ContentGraph<TNode, TEdgeType: number = 1> extends Graph<
37
36
 
38
37
  // $FlowFixMe[prop-missing]
39
38
  static deserialize(
40
- opts: SerializedContentGraph<TNode, TEdgeType>,
39
+ opts: ContentGraphOpts<TNode, TEdgeType>,
41
40
  ): ContentGraph<TNode, TEdgeType> {
42
- // $FlowFixMe
43
41
  return new ContentGraph(opts);
44
42
  }
45
43
 
46
44
  // $FlowFixMe[prop-missing]
47
45
  serialize(): SerializedContentGraph<TNode, TEdgeType> {
46
+ // $FlowFixMe[prop-missing]
48
47
  return {
49
48
  ...super.serialize(),
50
49
  _contentKeyToNodeId: this._contentKeyToNodeId,
package/src/Graph.js CHANGED
@@ -28,6 +28,51 @@ export type SerializedGraph<TNode, TEdgeType: number = 1> = {|
28
28
  export type AllEdgeTypes = -1;
29
29
  export const ALL_EDGE_TYPES: AllEdgeTypes = -1;
30
30
 
31
+ type DFSCommandVisit<TContext> = {|
32
+ nodeId: NodeId,
33
+ context: TContext | null,
34
+ |};
35
+
36
+ type DFSCommandExit<TContext> = {|
37
+ nodeId: NodeId,
38
+ exit: GraphTraversalCallback<NodeId, TContext>,
39
+ context: TContext | null,
40
+ |};
41
+
42
+ /**
43
+ * Internal type used for queue iterative DFS implementation.
44
+ */
45
+ type DFSCommand<TContext> =
46
+ | DFSCommandVisit<TContext>
47
+ | DFSCommandExit<TContext>;
48
+
49
+ /**
50
+ * Options for DFS traversal.
51
+ */
52
+ export type DFSParams<TContext> = {|
53
+ visit: GraphVisitor<NodeId, TContext>,
54
+ /**
55
+ * Custom function to get next entries to visit.
56
+ *
57
+ * This can be a performance bottleneck as arrays are created on every node visit.
58
+ *
59
+ * @deprecated This will be replaced by a static `traversalType` set of orders in the future
60
+ *
61
+ * Currently, this is only used in 3 ways:
62
+ *
63
+ * - Traversing down the tree (normal DFS)
64
+ * - Traversing up the tree (ancestors)
65
+ * - Filtered version of traversal; which does not need to exist at the DFS level as the visitor
66
+ * can handle filtering
67
+ * - Sorted traversal of BundleGraph entries, which does not have a clear use-case, but may
68
+ * not be safe to remove
69
+ *
70
+ * Only due to the latter we aren't replacing this.
71
+ */
72
+ getChildren: (nodeId: NodeId) => Array<NodeId>,
73
+ startNodeId?: ?NodeId,
74
+ |};
75
+
31
76
  export default class Graph<TNode, TEdgeType: number = 1> {
32
77
  nodes: Array<TNode | null>;
33
78
  adjacencyList: AdjacencyList<TEdgeType>;
@@ -49,7 +94,7 @@ export default class Graph<TNode, TEdgeType: number = 1> {
49
94
  }
50
95
 
51
96
  static deserialize(
52
- opts: SerializedGraph<TNode, TEdgeType>,
97
+ opts: GraphOpts<TNode, TEdgeType>,
53
98
  ): Graph<TNode, TEdgeType> {
54
99
  return new this({
55
100
  nodes: opts.nodes,
@@ -449,15 +494,16 @@ export default class Graph<TNode, TEdgeType: number = 1> {
449
494
  return;
450
495
  }
451
496
 
497
+ /**
498
+ * Iterative implementation of DFS that supports all use-cases.
499
+ *
500
+ * This replaces `dfs` and will replace `dfsFast`.
501
+ */
452
502
  dfs<TContext>({
453
503
  visit,
454
504
  startNodeId,
455
505
  getChildren,
456
- }: {|
457
- visit: GraphVisitor<NodeId, TContext>,
458
- getChildren(nodeId: NodeId): Array<NodeId>,
459
- startNodeId?: ?NodeId,
460
- |}): ?TContext {
506
+ }: DFSParams<TContext>): ?TContext {
461
507
  let traversalStartNode = nullthrows(
462
508
  startNodeId ?? this.rootNodeId,
463
509
  'A start node is required to traverse',
@@ -486,65 +532,74 @@ export default class Graph<TNode, TEdgeType: number = 1> {
486
532
  },
487
533
  };
488
534
 
489
- let walk = (nodeId, context: ?TContext) => {
490
- if (!this.hasNode(nodeId)) return;
491
- visited.add(nodeId);
535
+ const queue: DFSCommand<TContext>[] = [
536
+ {nodeId: traversalStartNode, context: null},
537
+ ];
538
+ const enter = typeof visit === 'function' ? visit : visit.enter;
539
+ while (queue.length !== 0) {
540
+ const command = queue.pop();
492
541
 
493
- skipped = false;
494
- let enter = typeof visit === 'function' ? visit : visit.enter;
495
- if (enter) {
496
- let newContext = enter(nodeId, context, actions);
542
+ if (command.exit != null) {
543
+ let {nodeId, context, exit} = command;
544
+ let newContext = exit(nodeId, command.context, actions);
497
545
  if (typeof newContext !== 'undefined') {
498
546
  // $FlowFixMe[reassign-const]
499
547
  context = newContext;
500
548
  }
501
- }
502
549
 
503
- if (skipped) {
504
- return;
505
- }
550
+ if (skipped) {
551
+ continue;
552
+ }
506
553
 
507
- if (stopped) {
508
- return context;
509
- }
554
+ if (stopped) {
555
+ this._visited = visited;
556
+ return context;
557
+ }
558
+ } else {
559
+ let {nodeId, context} = command;
560
+ if (!this.hasNode(nodeId) || visited.has(nodeId)) continue;
561
+ visited.add(nodeId);
562
+
563
+ skipped = false;
564
+ if (enter) {
565
+ let newContext = enter(nodeId, context, actions);
566
+ if (typeof newContext !== 'undefined') {
567
+ // $FlowFixMe[reassign-const]
568
+ context = newContext;
569
+ }
570
+ }
510
571
 
511
- for (let child of getChildren(nodeId)) {
512
- if (visited.has(child)) {
572
+ if (skipped) {
513
573
  continue;
514
574
  }
515
575
 
516
- visited.add(child);
517
- let result = walk(child, context);
518
576
  if (stopped) {
519
- return result;
577
+ this._visited = visited;
578
+ return context;
520
579
  }
521
- }
522
580
 
523
- if (
524
- typeof visit !== 'function' &&
525
- visit.exit &&
526
- // Make sure the graph still has the node: it may have been removed between enter and exit
527
- this.hasNode(nodeId)
528
- ) {
529
- let newContext = visit.exit(nodeId, context, actions);
530
- if (typeof newContext !== 'undefined') {
531
- // $FlowFixMe[reassign-const]
532
- context = newContext;
581
+ if (typeof visit !== 'function' && visit.exit) {
582
+ queue.push({
583
+ nodeId,
584
+ exit: visit.exit,
585
+ context,
586
+ });
533
587
  }
534
- }
535
588
 
536
- if (skipped) {
537
- return;
538
- }
589
+ // TODO turn into generator function
590
+ const children = getChildren(nodeId);
591
+ for (let i = children.length - 1; i > -1; i -= 1) {
592
+ const child = children[i];
593
+ if (visited.has(child)) {
594
+ continue;
595
+ }
539
596
 
540
- if (stopped) {
541
- return context;
597
+ queue.push({nodeId: child, context});
598
+ }
542
599
  }
543
- };
600
+ }
544
601
 
545
- let result = walk(traversalStartNode);
546
602
  this._visited = visited;
547
- return result;
548
603
  }
549
604
 
550
605
  bfs(visit: (nodeId: NodeId) => ?boolean): ?NodeId {
package/src/types.js CHANGED
@@ -9,7 +9,7 @@ export function fromNodeId(x: NodeId): number {
9
9
  return x;
10
10
  }
11
11
 
12
- export type ContentKey = string | number;
12
+ export type ContentKey = string;
13
13
 
14
14
  export type Edge<TEdgeType: number> = {|
15
15
  from: NodeId,
@@ -292,7 +292,8 @@ describe('AdjacencyList', () => {
292
292
  let work = new Promise(resolve => worker.on('message', resolve));
293
293
  worker.postMessage(originalSerialized);
294
294
  let received = AdjacencyList.deserialize(await work);
295
- await worker.terminate();
295
+ // eslint-disable-next-line no-unused-vars
296
+ const _terminatePromise = worker.terminate();
296
297
 
297
298
  assert.deepEqual(received.serialize().nodes, graph.serialize().nodes);
298
299
  assert.deepEqual(received.serialize().edges, graph.serialize().edges);
@@ -2,9 +2,10 @@
2
2
 
3
3
  import assert from 'assert';
4
4
  import sinon from 'sinon';
5
+ import type {TraversalActions} from '@parcel/types-internal';
5
6
 
6
7
  import Graph from '../src/Graph';
7
- import {toNodeId} from '../src/types';
8
+ import {toNodeId, type NodeId} from '../src/types';
8
9
 
9
10
  describe('Graph', () => {
10
11
  it('constructor should initialize an empty graph', () => {
@@ -340,4 +341,219 @@ describe('Graph', () => {
340
341
  assert.deepEqual(graph.nodes.filter(Boolean), ['root']);
341
342
  assert.deepStrictEqual(Array.from(graph.getAllEdges()), []);
342
343
  });
344
+
345
+ describe('dfs(...)', () => {
346
+ it(`throws if the graph is empty`, () => {
347
+ const graph = new Graph();
348
+ const visit = sinon.stub();
349
+ const getChildren = sinon.stub();
350
+ assert.throws(() => {
351
+ graph.dfs({
352
+ visit,
353
+ startNodeId: 0,
354
+ getChildren,
355
+ });
356
+ }, /Does not have node 0/);
357
+ });
358
+
359
+ it(`visits a single node`, () => {
360
+ const graph = new Graph();
361
+ graph.addNode('root');
362
+ const visit = sinon.stub();
363
+ const getChildren = () => [];
364
+ graph.dfs({
365
+ visit,
366
+ startNodeId: 0,
367
+ getChildren,
368
+ });
369
+
370
+ assert(visit.calledOnce);
371
+ });
372
+
373
+ it(`visits all connected nodes in DFS order`, () => {
374
+ const graph = new Graph();
375
+ graph.addNode('0');
376
+ graph.addNode('1');
377
+ graph.addNode('2');
378
+ graph.addNode('3');
379
+ graph.addNode('disconnected-1');
380
+ graph.addNode('disconnected-2');
381
+ graph.addEdge(0, 1);
382
+ graph.addEdge(0, 2);
383
+ graph.addEdge(1, 3);
384
+ graph.addEdge(2, 3);
385
+
386
+ const order = [];
387
+ const visit = (node: NodeId) => {
388
+ order.push(node);
389
+ };
390
+ const getChildren = (node: NodeId) => graph.getNodeIdsConnectedFrom(node);
391
+ graph.dfs({
392
+ visit,
393
+ startNodeId: 0,
394
+ getChildren,
395
+ });
396
+
397
+ assert.deepEqual(order, [0, 1, 3, 2]);
398
+ });
399
+
400
+ describe(`actions tests`, () => {
401
+ it(`skips children if skip is called on a node`, () => {
402
+ const graph = new Graph();
403
+ graph.addNode('0');
404
+ graph.addNode('1');
405
+ graph.addNode('2');
406
+ graph.addNode('3');
407
+ graph.addNode('disconnected-1');
408
+ graph.addNode('disconnected-2');
409
+ graph.addEdge(0, 1);
410
+ graph.addEdge(1, 2);
411
+ graph.addEdge(0, 3);
412
+
413
+ const order = [];
414
+ const visit = (
415
+ node: NodeId,
416
+ context: mixed | null,
417
+ actions: TraversalActions,
418
+ ) => {
419
+ if (node === 1) actions.skipChildren();
420
+ order.push(node);
421
+ };
422
+ const getChildren = (node: NodeId) =>
423
+ graph.getNodeIdsConnectedFrom(node);
424
+ graph.dfs({
425
+ visit,
426
+ startNodeId: 0,
427
+ getChildren,
428
+ });
429
+
430
+ assert.deepEqual(order, [0, 1, 3]);
431
+ });
432
+
433
+ it(`stops the traversal if stop is called`, () => {
434
+ const graph = new Graph();
435
+ graph.addNode('0');
436
+ graph.addNode('1');
437
+ graph.addNode('2');
438
+ graph.addNode('3');
439
+ graph.addNode('disconnected-1');
440
+ graph.addNode('disconnected-2');
441
+ graph.addEdge(0, 1);
442
+ graph.addEdge(1, 2);
443
+ graph.addEdge(1, 3);
444
+ graph.addEdge(0, 2);
445
+ graph.addEdge(2, 3);
446
+
447
+ const order = [];
448
+ const visit = (
449
+ node: NodeId,
450
+ context: mixed | null,
451
+ actions: TraversalActions,
452
+ ) => {
453
+ order.push(node);
454
+ if (node === 1) {
455
+ actions.stop();
456
+ return 'result';
457
+ }
458
+ return 'other';
459
+ };
460
+ const getChildren = (node: NodeId) =>
461
+ graph.getNodeIdsConnectedFrom(node);
462
+ const result = graph.dfs({
463
+ visit,
464
+ startNodeId: 0,
465
+ getChildren,
466
+ });
467
+
468
+ assert.deepEqual(order, [0, 1]);
469
+ assert.equal(result, 'result');
470
+ });
471
+ });
472
+
473
+ describe(`context tests`, () => {
474
+ it(`passes the context between visitors`, () => {
475
+ const graph = new Graph();
476
+ graph.addNode('0');
477
+ graph.addNode('1');
478
+ graph.addNode('2');
479
+ graph.addNode('3');
480
+ graph.addNode('disconnected-1');
481
+ graph.addNode('disconnected-2');
482
+ graph.addEdge(0, 1);
483
+ graph.addEdge(1, 2);
484
+ graph.addEdge(1, 3);
485
+ graph.addEdge(0, 2);
486
+ graph.addEdge(2, 3);
487
+
488
+ const contexts = [];
489
+ const visit = (node: NodeId, context: mixed | null) => {
490
+ contexts.push([node, context]);
491
+ return `node-${node}-created-context`;
492
+ };
493
+ const getChildren = (node: NodeId) =>
494
+ graph.getNodeIdsConnectedFrom(node);
495
+ const result = graph.dfs({
496
+ visit,
497
+ startNodeId: 0,
498
+ getChildren,
499
+ });
500
+
501
+ assert.deepEqual(contexts, [
502
+ [0, undefined],
503
+ [1, 'node-0-created-context'],
504
+ [2, 'node-1-created-context'],
505
+ [3, 'node-2-created-context'],
506
+ ]);
507
+ assert.equal(result, undefined);
508
+ });
509
+ });
510
+
511
+ describe(`exit visitor tests`, () => {
512
+ it(`calls the exit visitor`, () => {
513
+ const graph = new Graph();
514
+ graph.addNode('0');
515
+ graph.addNode('1');
516
+ graph.addNode('2');
517
+ graph.addNode('3');
518
+ graph.addNode('disconnected-1');
519
+ graph.addNode('disconnected-2');
520
+ graph.addEdge(0, 1);
521
+ graph.addEdge(1, 2);
522
+ graph.addEdge(1, 3);
523
+ graph.addEdge(0, 2);
524
+
525
+ const contexts = [];
526
+ const visit = (node: NodeId, context: mixed | null) => {
527
+ contexts.push([node, context]);
528
+ return `node-${node}-created-context`;
529
+ };
530
+ const visitExit = (node: NodeId, context: mixed | null) => {
531
+ contexts.push(['exit', node, context]);
532
+ return `node-exit-${node}-created-context`;
533
+ };
534
+ const getChildren = (node: NodeId) =>
535
+ graph.getNodeIdsConnectedFrom(node);
536
+ const result = graph.dfs({
537
+ visit: {
538
+ enter: visit,
539
+ exit: visitExit,
540
+ },
541
+ startNodeId: 0,
542
+ getChildren,
543
+ });
544
+
545
+ assert.deepEqual(contexts, [
546
+ [0, undefined],
547
+ [1, 'node-0-created-context'],
548
+ [2, 'node-1-created-context'],
549
+ ['exit', 2, 'node-2-created-context'],
550
+ [3, 'node-1-created-context'],
551
+ ['exit', 3, 'node-3-created-context'],
552
+ ['exit', 1, 'node-1-created-context'],
553
+ ['exit', 0, 'node-0-created-context'],
554
+ ]);
555
+ assert.equal(result, undefined);
556
+ });
557
+ });
558
+ });
343
559
  });
@@ -6,7 +6,7 @@ const {
6
6
  EdgeTypeMap,
7
7
  } = require('../../src/AdjacencyList');
8
8
 
9
- parentPort.once('message', (serialized) => {
9
+ parentPort.once('message', serialized => {
10
10
  let graph = AdjacencyList.deserialize(serialized);
11
11
  serialized.nodes.forEach((v, i) => {
12
12
  if (i < NodeTypeMap.HEADER_SIZE) return;