Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/helpers/sortableSet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*!
This file is part of CycloneDX JavaScript Library.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

SPDX-License-Identifier: Apache-2.0
Copyright (c) OWASP Foundation. All Rights Reserved.
*/

export interface Comparable {
compare: (other: any) => number
}

export abstract class SortableSet<T extends Comparable> extends Set<T> {
sorted (): T[] {
return Array.from(this).sort((a, b) => a.compare(b))
}
}
8 changes: 3 additions & 5 deletions src/models/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { OrganizationalEntity } from './organizationalEntity'
import { ExternalReferenceRepository } from './externalReference'
import { LicenseRepository } from './license'
import { SWID } from './swid'
import { Comparable, SortableSet } from '../helpers/sortableSet'

interface OptionalProperties {
bomRef?: BomRef['value']
Expand All @@ -47,7 +48,7 @@ interface OptionalProperties {
cpe?: Component['cpe']
}

export class Component {
export class Component implements Comparable {
type: ComponentType
name: string
author?: string
Expand Down Expand Up @@ -129,8 +130,5 @@ export class Component {
}
}

export class ComponentRepository extends Set<Component> {
static compareItems (a: Component, b: Component): number {
return a.compare(b)
}
export class ComponentRepository extends SortableSet<Component> {
}
8 changes: 3 additions & 5 deletions src/models/externalReference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ Copyright (c) OWASP Foundation. All Rights Reserved.
*/

import { ExternalReferenceType } from '../enums'
import { Comparable, SortableSet } from '../helpers/sortableSet'

interface OptionalProperties {
comment?: ExternalReference['comment']
}

export class ExternalReference {
export class ExternalReference implements Comparable {
url: URL | string
type: ExternalReferenceType
comment?: string
Expand All @@ -41,8 +42,5 @@ export class ExternalReference {
}
}

export class ExternalReferenceRepository extends Set<ExternalReference> {
static compareItems (a: ExternalReference, b: ExternalReference): number {
return a.compare(b)
}
export class ExternalReferenceRepository extends SortableSet<ExternalReference> {
}
10 changes: 7 additions & 3 deletions src/models/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ export type Hash = readonly [
]

export class HashRepository extends Map<Hash[0], Hash[1]> {
static compareItems (a: Hash, b: Hash): number {
#compareItems ([a1, c1]: Hash, [a2, c2]: Hash): number {
/* eslint-disable-next-line @typescript-eslint/strict-boolean-expressions -- run compares in weighted order */
return a[0].localeCompare(b[0]) ||
a[1].localeCompare(b[1])
return a1.localeCompare(a2) ||
c1.localeCompare(c2)
}

sorted (): Hash[] {
return Array.from(this.entries()).sort(this.#compareItems)
}
}
6 changes: 5 additions & 1 deletion src/models/license.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,15 @@ export type DisjunctiveLicense = NamedLicense | SpdxLicense
export type License = DisjunctiveLicense | LicenseExpression

export class LicenseRepository extends Set<License> {
static compareItems (a: License, b: License): number {
#compareItems (a: License, b: License): number {
if (a.constructor === b.constructor) {
// @ts-expect-error -- classes are from same type -> they are comparable
return a.compare(b)
}
return a.constructor.name.localeCompare(b.constructor.name)
}

sorted (): License[] {
return Array.from(this).sort(this.#compareItems)
}
}
9 changes: 4 additions & 5 deletions src/models/organizationalContact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ SPDX-License-Identifier: Apache-2.0
Copyright (c) OWASP Foundation. All Rights Reserved.
*/

import { Comparable, SortableSet } from '../helpers/sortableSet'

interface OptionalProperties {
name?: OrganizationalContact['name']
email?: OrganizationalContact['email']
phone?: OrganizationalContact['phone']
}

export class OrganizationalContact {
export class OrganizationalContact implements Comparable {
name?: string
email?: string
phone?: string
Expand All @@ -42,8 +44,5 @@ export class OrganizationalContact {
}
}

export class OrganizationalContactRepository extends Set<OrganizationalContact> {
static compareItems (a: OrganizationalContact, b: OrganizationalContact): number {
return a.compare(b)
}
export class OrganizationalContactRepository extends SortableSet<OrganizationalContact> {
}
8 changes: 3 additions & 5 deletions src/models/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved.

import { HashRepository } from './hash'
import { ExternalReferenceRepository } from './externalReference'
import { Comparable, SortableSet } from '../helpers/sortableSet'

interface OptionalProperties {
vendor?: Tool['vendor']
Expand All @@ -28,7 +29,7 @@ interface OptionalProperties {
externalReferences?: Tool['externalReferences']
}

export class Tool {
export class Tool implements Comparable {
vendor?: string
name?: string
version?: string
Expand All @@ -51,8 +52,5 @@ export class Tool {
}
}

export class ToolRepository extends Set<Tool> {
static compareItems (a: Tool, b: Tool): number {
return a.compare(b)
}
export class ToolRepository extends SortableSet<Tool> {
}
98 changes: 49 additions & 49 deletions src/serialize/json/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const schemaUrl: ReadonlyMap<SpecVersion, string> = new Map([
interface Normalizer {
normalize: (data: object, options: NormalizerOptions) => object | undefined

normalizeIter?: (data: Iterable<object>, options: NormalizerOptions) => object[]
normalizeRepository?: (data: Iterable<object>, options: NormalizerOptions) => object[]
}

abstract class Base implements Normalizer {
Expand All @@ -120,7 +120,7 @@ export class BomNormalizer extends Base {
serialNumber: data.serialNumber,
metadata: this._factory.makeForMetadata().normalize(data.metadata, options),
components: data.components.size > 0
? this._factory.makeForComponent().normalizeIter(data.components, options)
? this._factory.makeForComponent().normalizeRepository(data.components, options)
// spec < 1.4 requires `component` to be array
: [],
dependencies: this._factory.spec.supportsDependencyGraph
Expand All @@ -136,10 +136,10 @@ export class MetadataNormalizer extends Base {
return {
timestamp: data.timestamp?.toISOString(),
tools: data.tools.size > 0
? this._factory.makeForTool().normalizeIter(data.tools, options)
? this._factory.makeForTool().normalizeRepository(data.tools, options)
: undefined,
authors: data.authors.size > 0
? this._factory.makeForOrganizationalContact().normalizeIter(data.authors, options)
? this._factory.makeForOrganizationalContact().normalizeRepository(data.authors, options)
: undefined,
component: data.component === undefined
? undefined
Expand All @@ -161,20 +161,20 @@ export class ToolNormalizer extends Base {
name: data.name || undefined,
version: data.version || undefined,
hashes: data.hashes.size > 0
? this._factory.makeForHash().normalizeIter(data.hashes, options)
? this._factory.makeForHash().normalizeRepository(data.hashes, options)
: undefined,
externalReferences: this._factory.spec.supportsToolReferences && data.externalReferences.size > 0
? this._factory.makeForExternalReference().normalizeIter(data.externalReferences, options)
? this._factory.makeForExternalReference().normalizeRepository(data.externalReferences, options)
: undefined
}
}

normalizeIter (data: Iterable<Models.Tool>, options: NormalizerOptions): Normalized.Tool[] {
const tools = Array.from(data)
if (options.sortLists ?? false) {
tools.sort(Models.ToolRepository.compareItems)
}
return tools.map(t => this.normalize(t, options))
normalizeRepository (data: Models.ToolRepository, options: NormalizerOptions): Normalized.Tool[] {
return (
options.sortLists ?? false
? data.sorted()
: Array.from(data)
).map(t => this.normalize(t, options))
}
}

Expand All @@ -189,13 +189,13 @@ export class HashNormalizer extends Base {
: undefined
}

normalizeIter (data: Iterable<Models.Hash>, options: NormalizerOptions): Normalized.Hash[] {
const hashes = Array.from(data)
if (options.sortLists ?? false) {
hashes.sort(Models.HashRepository.compareItems)
}
return hashes.map(h => this.normalize(h, options))
.filter(isNotUndefined)
normalizeRepository (data: Models.HashRepository, options: NormalizerOptions): Normalized.Hash[] {
return (
options.sortLists ?? false
? data.sorted()
: Array.from(data)
).map(h => this.normalize(h, options)
).filter(isNotUndefined)
}
}

Expand All @@ -210,12 +210,12 @@ export class OrganizationalContactNormalizer extends Base {
}
}

normalizeIter (data: Iterable<Models.OrganizationalContact>, options: NormalizerOptions): Normalized.OrganizationalContact[] {
const contacts = Array.from(data)
if (options.sortLists ?? false) {
contacts.sort(Models.OrganizationalContactRepository.compareItems)
}
return contacts.map(c => this.normalize(c, options))
normalizeRepository (data: Models.OrganizationalContactRepository, options: NormalizerOptions): Normalized.OrganizationalContact[] {
return (
options.sortLists ?? false
? data.sorted()
: Array.from(data)
).map(c => this.normalize(c, options))
}
}

Expand All @@ -230,7 +230,7 @@ export class OrganizationalEntityNormalizer extends Base {
? urls
: undefined,
contact: data.contact.size > 0
? this._factory.makeForOrganizationalContact().normalizeIter(data.contact, options)
? this._factory.makeForOrganizationalContact().normalizeRepository(data.contact, options)
: undefined
}
}
Expand All @@ -254,10 +254,10 @@ export class ComponentNormalizer extends Base {
description: data.description || undefined,
scope: data.scope,
hashes: data.hashes.size > 0
? this._factory.makeForHash().normalizeIter(data.hashes, options)
? this._factory.makeForHash().normalizeRepository(data.hashes, options)
: undefined,
licenses: data.licenses.size > 0
? this._factory.makeForLicense().normalizeIter(data.licenses, options)
? this._factory.makeForLicense().normalizeRepository(data.licenses, options)
: undefined,
copyright: data.copyright || undefined,
cpe: data.cpe || undefined,
Expand All @@ -266,19 +266,19 @@ export class ComponentNormalizer extends Base {
? undefined
: this._factory.makeForSWID().normalize(data.swid, options),
externalReferences: data.externalReferences.size > 0
? this._factory.makeForExternalReference().normalizeIter(data.externalReferences, options)
? this._factory.makeForExternalReference().normalizeRepository(data.externalReferences, options)
: undefined
}
: undefined
}

normalizeIter (data: Iterable<Models.Component>, options: NormalizerOptions): Normalized.Component[] {
const components = Array.from(data)
if (options.sortLists ?? false) {
components.sort(Models.ComponentRepository.compareItems)
}
return components.map(c => this.normalize(c, options))
.filter(isNotUndefined)
normalizeRepository (data: Models.ComponentRepository, options: NormalizerOptions): Normalized.Component[] {
return (
options.sortLists ?? false
? data.sorted()
: Array.from(data)
).map(c => this.normalize(c, options)
).filter(isNotUndefined)
}
}

Expand Down Expand Up @@ -327,12 +327,12 @@ export class LicenseNormalizer extends Base {
}
}

normalizeIter (data: Iterable<Models.License>, options: NormalizerOptions): Normalized.License[] {
const licenses = Array.from(data)
if (options.sortLists ?? false) {
licenses.sort(Models.LicenseRepository.compareItems)
}
return licenses.map(c => this.normalize(c, options))
normalizeRepository (data: Models.LicenseRepository, options: NormalizerOptions): Normalized.License[] {
return (
options.sortLists ?? false
? data.sorted()
: Array.from(data)
).map(c => this.normalize(c, options))
}
}

Expand Down Expand Up @@ -366,13 +366,13 @@ export class ExternalReferenceNormalizer extends Base {
: undefined
}

normalizeIter (data: Iterable<Models.ExternalReference>, options: NormalizerOptions): Normalized.ExternalReference[] {
const refs = Array.from(data)
if (options.sortLists ?? false) {
refs.sort(Models.ExternalReferenceRepository.compareItems)
}
return refs.map(r => this.normalize(r, options))
.filter(isNotUndefined)
normalizeRepository (data: Models.ExternalReferenceRepository, options: NormalizerOptions): Normalized.ExternalReference[] {
return (
options.sortLists ?? false
? data.sorted()
: Array.from(data)
).map(r => this.normalize(r, options)
).filter(isNotUndefined)
}
}

Expand Down
Loading