Skip to content

Commit 9ce0088

Browse files
committed
Cache children for already visited nodes
1 parent 5f7eb71 commit 9ce0088

File tree

2 files changed

+123
-5
lines changed

2 files changed

+123
-5
lines changed

src/lib/tree.spec.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { describe, it, expect } from 'vitest';
2+
import type { Bom } from '$lib/cyclonedx/models';
3+
import { type TreeItem, TreeItemImpl } from '$lib/models/tree';
4+
import { createTreeDataFromBom } from '$lib/tree';
5+
6+
describe('createTreeDataFromBom', () => {
7+
it('creates a tree structure from a basic BOM', () => {
8+
const bom: Bom = {
9+
metadata: {
10+
component: {
11+
'bom-ref': 'subject-ref',
12+
name: 'subject-name',
13+
type: 'application'
14+
}
15+
},
16+
components: [
17+
{
18+
'bom-ref': 'a-ref',
19+
type: 'library',
20+
name: 'a-name'
21+
},
22+
{
23+
'bom-ref': 'b-ref',
24+
type: 'library',
25+
name: 'b-name'
26+
},
27+
{
28+
'bom-ref': 'c-ref',
29+
type: 'library',
30+
name: 'c-name'
31+
},
32+
{
33+
'bom-ref': 'd-ref',
34+
type: 'library',
35+
name: 'd-name'
36+
}
37+
],
38+
dependencies: [
39+
{
40+
ref: 'subject-ref',
41+
dependsOn: ['a-ref', 'b-ref']
42+
},
43+
{
44+
ref: 'a-ref',
45+
dependsOn: ['c-ref', 'd-ref']
46+
},
47+
{
48+
ref: 'b-ref',
49+
dependsOn: []
50+
},
51+
{
52+
ref: 'c-ref',
53+
dependsOn: ['d-ref']
54+
}
55+
]
56+
};
57+
58+
const expected: TreeItem[] = [
59+
new TreeItemImpl('a-name', 'a-ref', [
60+
new TreeItemImpl('c-name', 'c-ref', [new TreeItemImpl('d-name', 'd-ref')]),
61+
new TreeItemImpl('d-name', 'd-ref')
62+
]),
63+
new TreeItemImpl('b-name', 'b-ref')
64+
];
65+
66+
expect(createTreeDataFromBom(bom)).toStrictEqual(expected);
67+
});
68+
it('can handle cycles in the BOM dependencies', () => {
69+
const bom: Bom = {
70+
metadata: {
71+
component: {
72+
'bom-ref': 'subject-ref',
73+
name: 'subject-name',
74+
type: 'application'
75+
}
76+
},
77+
components: [
78+
{
79+
'bom-ref': 'a-ref',
80+
type: 'library',
81+
name: 'a-name'
82+
},
83+
{
84+
'bom-ref': 'b-ref',
85+
type: 'library',
86+
name: 'b-name'
87+
}
88+
],
89+
dependencies: [
90+
{
91+
ref: 'subject-ref',
92+
dependsOn: ['a-ref']
93+
},
94+
{
95+
ref: 'a-ref',
96+
dependsOn: ['b-ref']
97+
},
98+
{
99+
ref: 'b-ref',
100+
dependsOn: ['a-ref']
101+
}
102+
]
103+
};
104+
105+
const expected: TreeItem[] = [
106+
new TreeItemImpl('a-name', 'a-ref', [
107+
new TreeItemImpl('b-name', 'b-ref', [new TreeItemImpl('a-name', 'a-ref')])
108+
])
109+
];
110+
111+
expect(createTreeDataFromBom(bom)).toStrictEqual(expected);
112+
});
113+
});

src/lib/tree.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export function createTreeDataFromBom(bom: Bom): TreeItem[] {
1919

2020
const subject = bom.metadata?.component;
2121

22+
const childrenCache = new Map<string, TreeItem[] | undefined>();
23+
2224
function getChildTreeItems(componentRef: string, visited: Set<string>): TreeItem[] {
2325
// If we've already visited this component, return an empty array to prevent cycles
2426
if (visited.has(componentRef)) {
@@ -32,11 +34,14 @@ export function createTreeDataFromBom(bom: Bom): TreeItem[] {
3234
return (dependencyMap.get(componentRef) ?? [])
3335
.map((child) => {
3436
if (componentRefToName.has(child)) {
35-
return new TreeItemImpl(
36-
componentRefToName.get(child)!,
37-
child,
38-
getChildTreeItems(child, new Set(visited))
39-
);
37+
let children;
38+
if (childrenCache.has(child)) {
39+
children = childrenCache.get(child);
40+
} else {
41+
children = getChildTreeItems(child, new Set(visited));
42+
childrenCache.set(child, children);
43+
}
44+
return new TreeItemImpl(componentRefToName.get(child)!, child, children);
4045
} else {
4146
return null;
4247
}

0 commit comments

Comments
 (0)