Skip to content

Commit 2cdd303

Browse files
authored
bring sorted() to model repositories (#66)
Signed-off-by: Jan Kowalleck <[email protected]>
1 parent 43cefb4 commit 2cdd303

File tree

9 files changed

+152
-123
lines changed

9 files changed

+152
-123
lines changed

src/helpers/sortableSet.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*!
2+
This file is part of CycloneDX JavaScript Library.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache-2.0
17+
Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
20+
export interface Comparable {
21+
compare: (other: any) => number
22+
}
23+
24+
export abstract class SortableSet<T extends Comparable> extends Set<T> {
25+
sorted (): T[] {
26+
return Array.from(this).sort((a, b) => a.compare(b))
27+
}
28+
}

src/models/component.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { OrganizationalEntity } from './organizationalEntity'
2727
import { ExternalReferenceRepository } from './externalReference'
2828
import { LicenseRepository } from './license'
2929
import { SWID } from './swid'
30+
import { Comparable, SortableSet } from '../helpers/sortableSet'
3031

3132
interface OptionalProperties {
3233
bomRef?: BomRef['value']
@@ -47,7 +48,7 @@ interface OptionalProperties {
4748
cpe?: Component['cpe']
4849
}
4950

50-
export class Component {
51+
export class Component implements Comparable {
5152
type: ComponentType
5253
name: string
5354
author?: string
@@ -129,8 +130,5 @@ export class Component {
129130
}
130131
}
131132

132-
export class ComponentRepository extends Set<Component> {
133-
static compareItems (a: Component, b: Component): number {
134-
return a.compare(b)
135-
}
133+
export class ComponentRepository extends SortableSet<Component> {
136134
}

src/models/externalReference.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@ Copyright (c) OWASP Foundation. All Rights Reserved.
1818
*/
1919

2020
import { ExternalReferenceType } from '../enums'
21+
import { Comparable, SortableSet } from '../helpers/sortableSet'
2122

2223
interface OptionalProperties {
2324
comment?: ExternalReference['comment']
2425
}
2526

26-
export class ExternalReference {
27+
export class ExternalReference implements Comparable {
2728
url: URL | string
2829
type: ExternalReferenceType
2930
comment?: string
@@ -41,8 +42,5 @@ export class ExternalReference {
4142
}
4243
}
4344

44-
export class ExternalReferenceRepository extends Set<ExternalReference> {
45-
static compareItems (a: ExternalReference, b: ExternalReference): number {
46-
return a.compare(b)
47-
}
45+
export class ExternalReferenceRepository extends SortableSet<ExternalReference> {
4846
}

src/models/hash.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,13 @@ export type Hash = readonly [
3030
]
3131

3232
export class HashRepository extends Map<Hash[0], Hash[1]> {
33-
static compareItems (a: Hash, b: Hash): number {
33+
#compareItems ([a1, c1]: Hash, [a2, c2]: Hash): number {
3434
/* eslint-disable-next-line @typescript-eslint/strict-boolean-expressions -- run compares in weighted order */
35-
return a[0].localeCompare(b[0]) ||
36-
a[1].localeCompare(b[1])
35+
return a1.localeCompare(a2) ||
36+
c1.localeCompare(c2)
37+
}
38+
39+
sorted (): Hash[] {
40+
return Array.from(this.entries()).sort(this.#compareItems)
3741
}
3842
}

src/models/license.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,15 @@ export type DisjunctiveLicense = NamedLicense | SpdxLicense
123123
export type License = DisjunctiveLicense | LicenseExpression
124124

125125
export class LicenseRepository extends Set<License> {
126-
static compareItems (a: License, b: License): number {
126+
#compareItems (a: License, b: License): number {
127127
if (a.constructor === b.constructor) {
128128
// @ts-expect-error -- classes are from same type -> they are comparable
129129
return a.compare(b)
130130
}
131131
return a.constructor.name.localeCompare(b.constructor.name)
132132
}
133+
134+
sorted (): License[] {
135+
return Array.from(this).sort(this.#compareItems)
136+
}
133137
}

src/models/organizationalContact.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ SPDX-License-Identifier: Apache-2.0
1717
Copyright (c) OWASP Foundation. All Rights Reserved.
1818
*/
1919

20+
import { Comparable, SortableSet } from '../helpers/sortableSet'
21+
2022
interface OptionalProperties {
2123
name?: OrganizationalContact['name']
2224
email?: OrganizationalContact['email']
2325
phone?: OrganizationalContact['phone']
2426
}
2527

26-
export class OrganizationalContact {
28+
export class OrganizationalContact implements Comparable {
2729
name?: string
2830
email?: string
2931
phone?: string
@@ -42,8 +44,5 @@ export class OrganizationalContact {
4244
}
4345
}
4446

45-
export class OrganizationalContactRepository extends Set<OrganizationalContact> {
46-
static compareItems (a: OrganizationalContact, b: OrganizationalContact): number {
47-
return a.compare(b)
48-
}
47+
export class OrganizationalContactRepository extends SortableSet<OrganizationalContact> {
4948
}

src/models/tool.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved.
1919

2020
import { HashRepository } from './hash'
2121
import { ExternalReferenceRepository } from './externalReference'
22+
import { Comparable, SortableSet } from '../helpers/sortableSet'
2223

2324
interface OptionalProperties {
2425
vendor?: Tool['vendor']
@@ -28,7 +29,7 @@ interface OptionalProperties {
2829
externalReferences?: Tool['externalReferences']
2930
}
3031

31-
export class Tool {
32+
export class Tool implements Comparable {
3233
vendor?: string
3334
name?: string
3435
version?: string
@@ -51,8 +52,5 @@ export class Tool {
5152
}
5253
}
5354

54-
export class ToolRepository extends Set<Tool> {
55-
static compareItems (a: Tool, b: Tool): number {
56-
return a.compare(b)
57-
}
55+
export class ToolRepository extends SortableSet<Tool> {
5856
}

src/serialize/json/normalize.ts

+49-49
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ const schemaUrl: ReadonlyMap<SpecVersion, string> = new Map([
9393
interface Normalizer {
9494
normalize: (data: object, options: NormalizerOptions) => object | undefined
9595

96-
normalizeIter?: (data: Iterable<object>, options: NormalizerOptions) => object[]
96+
normalizeRepository?: (data: Iterable<object>, options: NormalizerOptions) => object[]
9797
}
9898

9999
abstract class Base implements Normalizer {
@@ -120,7 +120,7 @@ export class BomNormalizer extends Base {
120120
serialNumber: data.serialNumber,
121121
metadata: this._factory.makeForMetadata().normalize(data.metadata, options),
122122
components: data.components.size > 0
123-
? this._factory.makeForComponent().normalizeIter(data.components, options)
123+
? this._factory.makeForComponent().normalizeRepository(data.components, options)
124124
// spec < 1.4 requires `component` to be array
125125
: [],
126126
dependencies: this._factory.spec.supportsDependencyGraph
@@ -136,10 +136,10 @@ export class MetadataNormalizer extends Base {
136136
return {
137137
timestamp: data.timestamp?.toISOString(),
138138
tools: data.tools.size > 0
139-
? this._factory.makeForTool().normalizeIter(data.tools, options)
139+
? this._factory.makeForTool().normalizeRepository(data.tools, options)
140140
: undefined,
141141
authors: data.authors.size > 0
142-
? this._factory.makeForOrganizationalContact().normalizeIter(data.authors, options)
142+
? this._factory.makeForOrganizationalContact().normalizeRepository(data.authors, options)
143143
: undefined,
144144
component: data.component === undefined
145145
? undefined
@@ -161,20 +161,20 @@ export class ToolNormalizer extends Base {
161161
name: data.name || undefined,
162162
version: data.version || undefined,
163163
hashes: data.hashes.size > 0
164-
? this._factory.makeForHash().normalizeIter(data.hashes, options)
164+
? this._factory.makeForHash().normalizeRepository(data.hashes, options)
165165
: undefined,
166166
externalReferences: this._factory.spec.supportsToolReferences && data.externalReferences.size > 0
167-
? this._factory.makeForExternalReference().normalizeIter(data.externalReferences, options)
167+
? this._factory.makeForExternalReference().normalizeRepository(data.externalReferences, options)
168168
: undefined
169169
}
170170
}
171171

172-
normalizeIter (data: Iterable<Models.Tool>, options: NormalizerOptions): Normalized.Tool[] {
173-
const tools = Array.from(data)
174-
if (options.sortLists ?? false) {
175-
tools.sort(Models.ToolRepository.compareItems)
176-
}
177-
return tools.map(t => this.normalize(t, options))
172+
normalizeRepository (data: Models.ToolRepository, options: NormalizerOptions): Normalized.Tool[] {
173+
return (
174+
options.sortLists ?? false
175+
? data.sorted()
176+
: Array.from(data)
177+
).map(t => this.normalize(t, options))
178178
}
179179
}
180180

@@ -189,13 +189,13 @@ export class HashNormalizer extends Base {
189189
: undefined
190190
}
191191

192-
normalizeIter (data: Iterable<Models.Hash>, options: NormalizerOptions): Normalized.Hash[] {
193-
const hashes = Array.from(data)
194-
if (options.sortLists ?? false) {
195-
hashes.sort(Models.HashRepository.compareItems)
196-
}
197-
return hashes.map(h => this.normalize(h, options))
198-
.filter(isNotUndefined)
192+
normalizeRepository (data: Models.HashRepository, options: NormalizerOptions): Normalized.Hash[] {
193+
return (
194+
options.sortLists ?? false
195+
? data.sorted()
196+
: Array.from(data)
197+
).map(h => this.normalize(h, options)
198+
).filter(isNotUndefined)
199199
}
200200
}
201201

@@ -210,12 +210,12 @@ export class OrganizationalContactNormalizer extends Base {
210210
}
211211
}
212212

213-
normalizeIter (data: Iterable<Models.OrganizationalContact>, options: NormalizerOptions): Normalized.OrganizationalContact[] {
214-
const contacts = Array.from(data)
215-
if (options.sortLists ?? false) {
216-
contacts.sort(Models.OrganizationalContactRepository.compareItems)
217-
}
218-
return contacts.map(c => this.normalize(c, options))
213+
normalizeRepository (data: Models.OrganizationalContactRepository, options: NormalizerOptions): Normalized.OrganizationalContact[] {
214+
return (
215+
options.sortLists ?? false
216+
? data.sorted()
217+
: Array.from(data)
218+
).map(c => this.normalize(c, options))
219219
}
220220
}
221221

@@ -230,7 +230,7 @@ export class OrganizationalEntityNormalizer extends Base {
230230
? urls
231231
: undefined,
232232
contact: data.contact.size > 0
233-
? this._factory.makeForOrganizationalContact().normalizeIter(data.contact, options)
233+
? this._factory.makeForOrganizationalContact().normalizeRepository(data.contact, options)
234234
: undefined
235235
}
236236
}
@@ -254,10 +254,10 @@ export class ComponentNormalizer extends Base {
254254
description: data.description || undefined,
255255
scope: data.scope,
256256
hashes: data.hashes.size > 0
257-
? this._factory.makeForHash().normalizeIter(data.hashes, options)
257+
? this._factory.makeForHash().normalizeRepository(data.hashes, options)
258258
: undefined,
259259
licenses: data.licenses.size > 0
260-
? this._factory.makeForLicense().normalizeIter(data.licenses, options)
260+
? this._factory.makeForLicense().normalizeRepository(data.licenses, options)
261261
: undefined,
262262
copyright: data.copyright || undefined,
263263
cpe: data.cpe || undefined,
@@ -266,19 +266,19 @@ export class ComponentNormalizer extends Base {
266266
? undefined
267267
: this._factory.makeForSWID().normalize(data.swid, options),
268268
externalReferences: data.externalReferences.size > 0
269-
? this._factory.makeForExternalReference().normalizeIter(data.externalReferences, options)
269+
? this._factory.makeForExternalReference().normalizeRepository(data.externalReferences, options)
270270
: undefined
271271
}
272272
: undefined
273273
}
274274

275-
normalizeIter (data: Iterable<Models.Component>, options: NormalizerOptions): Normalized.Component[] {
276-
const components = Array.from(data)
277-
if (options.sortLists ?? false) {
278-
components.sort(Models.ComponentRepository.compareItems)
279-
}
280-
return components.map(c => this.normalize(c, options))
281-
.filter(isNotUndefined)
275+
normalizeRepository (data: Models.ComponentRepository, options: NormalizerOptions): Normalized.Component[] {
276+
return (
277+
options.sortLists ?? false
278+
? data.sorted()
279+
: Array.from(data)
280+
).map(c => this.normalize(c, options)
281+
).filter(isNotUndefined)
282282
}
283283
}
284284

@@ -327,12 +327,12 @@ export class LicenseNormalizer extends Base {
327327
}
328328
}
329329

330-
normalizeIter (data: Iterable<Models.License>, options: NormalizerOptions): Normalized.License[] {
331-
const licenses = Array.from(data)
332-
if (options.sortLists ?? false) {
333-
licenses.sort(Models.LicenseRepository.compareItems)
334-
}
335-
return licenses.map(c => this.normalize(c, options))
330+
normalizeRepository (data: Models.LicenseRepository, options: NormalizerOptions): Normalized.License[] {
331+
return (
332+
options.sortLists ?? false
333+
? data.sorted()
334+
: Array.from(data)
335+
).map(c => this.normalize(c, options))
336336
}
337337
}
338338

@@ -366,13 +366,13 @@ export class ExternalReferenceNormalizer extends Base {
366366
: undefined
367367
}
368368

369-
normalizeIter (data: Iterable<Models.ExternalReference>, options: NormalizerOptions): Normalized.ExternalReference[] {
370-
const refs = Array.from(data)
371-
if (options.sortLists ?? false) {
372-
refs.sort(Models.ExternalReferenceRepository.compareItems)
373-
}
374-
return refs.map(r => this.normalize(r, options))
375-
.filter(isNotUndefined)
369+
normalizeRepository (data: Models.ExternalReferenceRepository, options: NormalizerOptions): Normalized.ExternalReference[] {
370+
return (
371+
options.sortLists ?? false
372+
? data.sorted()
373+
: Array.from(data)
374+
).map(r => this.normalize(r, options)
375+
).filter(isNotUndefined)
376376
}
377377
}
378378

0 commit comments

Comments
 (0)