Skip to content

Commit 12c7010

Browse files
committed
Handle cycles in the dependency tree
1 parent d1d1307 commit 12c7010

File tree

4 files changed

+76
-68
lines changed

4 files changed

+76
-68
lines changed

src/lib/components/Bom.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import ComponentsTreeChart from '$lib/components/ComponentsTreeChart.svelte';
77
import ComponentModal from '$lib/components/ComponentModal.svelte';
88
import { Document } from 'carbon-icons-svelte';
9-
import { createTreeDataFromBom, type TreeItem } from '$lib/tree';
9+
import { createTreeDataFromBom } from '$lib/tree';
10+
import type { TreeItem } from '$lib/models/tree';
1011
1112
let { bom = null }: { bom: Bom | null } = $props();
1213

src/lib/components/ComponentsTreeChart.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<script lang="ts">
22
import { TreeChart, type TreeChartOptions } from '@carbon/charts-svelte';
33
import '@carbon/charts-svelte/styles.css';
4-
import type { TreeChartItem } from '$lib/tree';
54
import type { Bom } from '$lib/cyclonedx/models';
5+
import type { TreeChartItem } from '$lib/models/tree';
66
77
let { bom, nodes = [] }: { bom: Bom; nodes: TreeChartItem[] } = $props();
88

src/lib/models/tree.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Used for the tree chart
2+
import type { TreeNode } from 'carbon-components-svelte/src/TreeView/TreeView.svelte';
3+
4+
export interface TreeChartItem {
5+
name: string;
6+
value?: string;
7+
children?: TreeChartItem[];
8+
}
9+
10+
// The combined type that can be used for the TreeView and the TreeChart
11+
export type TreeItem = TreeChartItem & TreeNode;
12+
13+
export class TreeItemImpl implements TreeItem {
14+
private readonly _name: string;
15+
private readonly _ref: string;
16+
private readonly _children: TreeItem[] | undefined;
17+
18+
constructor(name: string, ref: string, children?: TreeItem[]) {
19+
this._name = name;
20+
this._ref = ref;
21+
this._children = children && children.length > 0 ? children : undefined;
22+
}
23+
24+
get name() {
25+
return this._name;
26+
}
27+
28+
get text() {
29+
return this._name;
30+
}
31+
32+
get id() {
33+
return this._ref;
34+
}
35+
36+
get value() {
37+
return this._ref;
38+
}
39+
40+
get children() {
41+
return this._children;
42+
}
43+
44+
get nodes() {
45+
return this._children;
46+
}
47+
48+
get icon() {
49+
return undefined;
50+
}
51+
52+
get disabled() {
53+
return undefined;
54+
}
55+
}

src/lib/tree.ts

Lines changed: 18 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,5 @@
11
import type { Bom } from '$lib/cyclonedx/models';
2-
import type { TreeNode } from 'carbon-components-svelte/src/TreeView/TreeView.svelte';
3-
4-
// Used for the tree chart
5-
export interface TreeChartItem {
6-
name: string;
7-
value?: string;
8-
children?: TreeChartItem[];
9-
}
10-
11-
// The combined type that can be used for the TreeView and the TreeChart
12-
export type TreeItem = TreeChartItem & TreeNode;
13-
14-
class TreeItemImpl implements TreeItem {
15-
private readonly _name: string;
16-
private readonly _ref: string;
17-
private readonly _children: TreeItem[] | undefined;
18-
19-
constructor(name: string, ref: string, children?: TreeItem[]) {
20-
this._name = name;
21-
this._ref = ref;
22-
this._children = children && children.length > 0 ? children : undefined;
23-
}
24-
25-
get name() {
26-
return this._name;
27-
}
28-
29-
get text() {
30-
return this._name;
31-
}
32-
33-
get id() {
34-
return this._ref;
35-
}
36-
37-
get value() {
38-
return this._ref;
39-
}
40-
41-
get children() {
42-
return this._children;
43-
}
44-
45-
get nodes() {
46-
return this._children;
47-
}
48-
49-
get icon() {
50-
return undefined;
51-
}
52-
53-
get disabled() {
54-
return undefined;
55-
}
56-
}
2+
import { type TreeItem, TreeItemImpl } from '$lib/models/tree';
573

584
export function createTreeDataFromBom(bom: Bom): TreeItem[] {
595
const componentRefToName = new Map<string, string>();
@@ -73,27 +19,33 @@ export function createTreeDataFromBom(bom: Bom): TreeItem[] {
7319

7420
const subject = bom.metadata?.component;
7521

76-
function getChildTreeItems(componentRef: string) {
22+
function getChildTreeItems(componentRef: string, visited: Set<string>): TreeItem[] {
23+
// If we've already visited this component, return an empty array to prevent cycles
24+
if (visited.has(componentRef)) {
25+
console.warn(`Cycle detected for ${componentRef}`);
26+
return [];
27+
}
28+
29+
// Mark the current component as visited
30+
visited.add(componentRef);
31+
7732
return (dependencyMap.get(componentRef) ?? [])
7833
.map((child) => {
7934
if (componentRefToName.has(child)) {
80-
return getTreeItem(child, componentRefToName.get(child)!);
35+
return new TreeItemImpl(
36+
componentRefToName.get(child)!,
37+
child,
38+
getChildTreeItems(child, new Set(visited))
39+
);
8140
} else {
8241
return null;
8342
}
8443
})
85-
.filter((child) => child !== null);
44+
.filter((child) => child !== null) as TreeItem[];
8645
}
8746

88-
const getTreeItem: (componentRef: string, componentName: string) => TreeItem = (
89-
componentRef,
90-
componentName
91-
) => {
92-
return new TreeItemImpl(componentName, componentRef, getChildTreeItems(componentRef));
93-
};
94-
9547
if (subject && subject['bom-ref']) {
96-
data.push(...getChildTreeItems(subject['bom-ref']));
48+
data.push(...getChildTreeItems(subject['bom-ref'], new Set<string>()));
9749
} else {
9850
console.error(`No subject found in ${bom.metadata}`);
9951
}

0 commit comments

Comments
 (0)