@parcel/graph 3.2.1-nightly.3141 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -21,7 +21,7 @@ function _nullthrows() {
21
21
  var _sharedBuffer = require("./shared-buffer");
22
22
  var _types = require("./types");
23
23
  var _Graph = require("./Graph");
24
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
24
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
25
25
  /** The address of the node in the nodes map. */
26
26
  /** The address of the edge in the edges map. */
27
27
  // eslint-disable-next-line no-unused-vars
@@ -89,7 +89,6 @@ const MAX_LINK_TRIES = 3;
89
89
  class AdjacencyList {
90
90
  #nodes /*: NodeTypeMap<TEdgeType | NullEdgeType> */;
91
91
  #edges /*: EdgeTypeMap<TEdgeType | NullEdgeType> */;
92
-
93
92
  #params /*: AdjacencyListParams */;
94
93
 
95
94
  /**
@@ -240,7 +239,7 @@ class AdjacencyList {
240
239
  *
241
240
  * Note that this method does not increment the node count
242
241
  * (that only happens in `addEdge`), it _may_ preemptively resize
243
- * the nodes array if it is at capacity, under the asumption that
242
+ * the nodes array if it is at capacity, under the assumption that
244
243
  * at least 1 edge to or from this new node will be added.
245
244
  *
246
245
  * Returns the id of the added node.
@@ -12,7 +12,7 @@ function _nullthrows() {
12
12
  };
13
13
  return data;
14
14
  }
15
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
16
  class ContentGraph extends _Graph.default {
17
17
  constructor(opts) {
18
18
  if (opts) {
package/lib/Graph.js CHANGED
@@ -15,8 +15,17 @@ function _nullthrows() {
15
15
  };
16
16
  return data;
17
17
  }
18
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
18
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
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/lib/index.js CHANGED
@@ -49,6 +49,6 @@ var _types = require("./types");
49
49
  var _Graph = _interopRequireWildcard(require("./Graph"));
50
50
  var _ContentGraph = _interopRequireDefault(require("./ContentGraph"));
51
51
  var _BitSet = require("./BitSet");
52
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
52
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
53
53
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
54
- function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
54
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parcel/graph",
3
- "version": "3.2.1-nightly.3141+4042e306a",
3
+ "version": "3.3.0",
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.13.0",
23
24
  "nullthrows": "^1.1.1"
24
25
  },
25
- "gitHead": "4042e306aef005f9bf407a189681b4049599719d"
26
+ "gitHead": "a53f8f3ba1025c7ea8653e9719e0a61ef9717079"
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.
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>;
@@ -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 {
@@ -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;