@parcel/graph 3.2.1-dev.3268 → 3.2.1-dev.3275
Sign up to get free protection for your applications and to get access to all the features.
- 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.3275+8b017703f",
|
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.3275+8b017703f",
|
23
24
|
"nullthrows": "^1.1.1"
|
24
25
|
},
|
25
|
-
"gitHead": "
|
26
|
+
"gitHead": "8b017703fdf4a90a643ce0190cdd7482060dc86b"
|
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;
|