@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.
- package/lib/AdjacencyList.js +1 -1
- package/lib/ContentGraph.js +1 -1
- package/lib/Graph.js +77 -39
- package/package.json +4 -3
- package/src/AdjacencyList.js +2 -2
- package/src/ContentGraph.js +2 -3
- package/src/Graph.js +100 -45
- package/src/types.js +1 -1
- package/test/AdjacencyList.test.js +2 -1
- package/test/Graph.test.js +217 -1
- package/test/integration/adjacency-list-shared-array.js +1 -1
package/lib/AdjacencyList.js
CHANGED
|
@@ -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
|
|
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.
|
package/lib/ContentGraph.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
368
|
+
this._visited = visited;
|
|
369
|
+
return context;
|
|
357
370
|
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
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.
|
|
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": ">=
|
|
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": "
|
|
26
|
+
"gitHead": "3edb0d7419831e49cfa7ea8c52b4f7127a16ae52"
|
|
26
27
|
}
|
package/src/AdjacencyList.js
CHANGED
|
@@ -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
|
|
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
|
|
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/ContentGraph.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
-
|
|
504
|
-
|
|
505
|
-
|
|
550
|
+
if (skipped) {
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
506
553
|
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
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
|
-
|
|
577
|
+
this._visited = visited;
|
|
578
|
+
return context;
|
|
520
579
|
}
|
|
521
|
-
}
|
|
522
580
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
537
|
-
|
|
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
|
-
|
|
541
|
-
|
|
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
|
@@ -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
|
-
|
|
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);
|
package/test/Graph.test.js
CHANGED
|
@@ -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',
|
|
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;
|