Skip to content

Commit 28e6e37

Browse files
committed
feat: hasCycle now accepts options sourceNodes and shouldFollow to target specific sub-graph or cycles.
1 parent 013f68b commit 28e6e37

2 files changed

Lines changed: 40 additions & 5 deletions

File tree

src/utils/hasCycle.spec.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import { Graph } from '../Graph.js';
33
import { hasCycle } from './hasCycle.js';
44

55
describe('hasCycle', () => {
6-
it('Should detect cycle.', function () {
6+
it('should detect cycle.', function () {
77
const graph = new Graph();
88
graph.addEdge('a', 'b');
99
graph.addEdge('b', 'a');
1010
expect(hasCycle(graph)).toBe(true);
1111
});
1212

13-
it('Should detect cycle (long).', function () {
13+
it('should detect cycle (long).', function () {
1414
const graph = new Graph();
1515
graph.addEdge('a', 'b');
1616
graph.addEdge('b', 'c');
@@ -19,15 +19,42 @@ describe('hasCycle', () => {
1919
expect(hasCycle(graph)).toBe(true);
2020
});
2121

22-
it('Should detect cycle (loop).', function () {
22+
it('should detect cycle (loop).', function () {
2323
const graph = new Graph();
2424
graph.addEdge('a', 'a');
2525
expect(hasCycle(graph)).toBe(true);
2626
});
2727

28-
it('Should not detect cycle.', function () {
28+
it('should not detect cycle.', function () {
2929
const graph = new Graph();
3030
graph.addEdge('a', 'b');
3131
expect(hasCycle(graph)).toBe(false);
3232
});
33+
34+
it('should ignore the cycle in another sub-graph.', function () {
35+
const graph = new Graph();
36+
graph.addEdge('a', 'b');
37+
graph.addEdge('b', 'c');
38+
graph.addEdge('c', 'd');
39+
graph.addEdge('d', 'a');
40+
41+
graph.addEdge('m', 'n');
42+
43+
expect(hasCycle(graph, { sourceNodes: ['m'] })).toBe(false);
44+
});
45+
46+
it('should not detect the cycle when the traversing is stopped by the shouldFollow option.', function () {
47+
const graph = new Graph<string, string>();
48+
graph.addEdge('a', 'b', undefined, 'foo');
49+
graph.addEdge('b', 'c', undefined, 'foo');
50+
graph.addEdge('c', 'd', undefined, 'foo');
51+
graph.addEdge('d', 'a', undefined, 'bar');
52+
53+
expect(
54+
hasCycle(graph, {
55+
shouldFollow: (source, target) =>
56+
graph.getEdgeProperties(source, target) === 'foo',
57+
}),
58+
).toBe(false);
59+
});
3360
});

src/utils/hasCycle.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
import { depthFirstSearch } from '../algorithms/depthFirstSearch/index.js';
2+
import { DepthFirstSearchOptions } from '../algorithms/depthFirstSearch/types.js';
23
import { CycleError } from '../CycleError.js';
34
import { Graph } from '../Graph.js';
45

56
/**
67
* Perform a depth first search to detect an eventual cycle.
8+
*
9+
* You can provide a `shouldFollow` function to constrain the traversing and
10+
* provide `sourceNodes` to explore a particular sub-graphs.
711
*/
8-
export function hasCycle(graph: Graph): boolean {
12+
export function hasCycle<Node, LinkProps>(
13+
graph: Graph<Node, LinkProps>,
14+
opts?: Pick<DepthFirstSearchOptions<Node, LinkProps>, 'shouldFollow' | 'sourceNodes'>,
15+
): boolean {
916
try {
1017
depthFirstSearch(graph, {
18+
...opts,
1119
includeSourceNodes: true,
1220
errorOnCycle: true,
1321
});

0 commit comments

Comments
 (0)