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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;