Skip to content

Commit 9c88abc

Browse files
authored
fix: normalize expression over license (#623)
Signed-off-by: Jan Kowalleck <[email protected]>
1 parent 1a6b484 commit 9c88abc

16 files changed

+914
-54
lines changed

HISTORY.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ All notable changes to this project will be documented in this file.
44

55
## unreleased
66

7+
* Fixed
8+
* `Serialize.{JSON,XML}.Normalize.LicenseNormalizer.normalizeIterable()` now omits invalid license combinations ([#602] via [#])
9+
If there is any `Models.LicenseExpression`, then this is the only license normalized; otherwise all licenses are normalized.
710
* Docs
811
* Fixed link to CycloneDX-specification in README (via [#617])
912

13+
[#602]: https://github.com/CycloneDX/cyclonedx-javascript-library/issues/602
1014
[#617]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/617
15+
[#]:
1116

1217
## 1.13.2 - 2023-03-29
1318

src/serialize/json/normalize.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -378,13 +378,26 @@ export class LicenseNormalizer extends BaseJsonNormalizer<Models.License> {
378378
}
379379
}
380380

381-
/** @since 1.5.1 */
381+
/**
382+
* If there is any {@link Models.LicenseExpression | LicenseExpression} in the set, then this is the only item that is normalized.
383+
*
384+
* @since 1.5.1
385+
*/
382386
normalizeIterable (data: SortableIterable<Models.License>, options: NormalizerOptions): Normalized.License[] {
383-
return (
384-
options.sortLists ?? false
385-
? data.sorted()
386-
: Array.from(data)
387-
).map(c => this.normalize(c, options))
387+
const licenses = options.sortLists ?? false
388+
? data.sorted()
389+
: Array.from(data)
390+
391+
if (licenses.length > 1) {
392+
const expressions = licenses.filter(l => l instanceof Models.LicenseExpression) as Models.LicenseExpression[]
393+
if (expressions.length > 0) {
394+
// could have thrown {@link RangeError} when there is more than one only {@link Models.LicenseExpression | LicenseExpression}.
395+
// but let's be graceful and just normalize to the most relevant choice: any expression
396+
return [this.#normalizeLicenseExpression(expressions[0])]
397+
}
398+
}
399+
400+
return licenses.map(l => this.normalize(l, options))
388401
}
389402

390403
/** @deprecated use {@link normalizeIterable} instead of {@link normalizeRepository} */

src/serialize/xml/normalize.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -482,13 +482,26 @@ export class LicenseNormalizer extends BaseXmlNormalizer<Models.License> {
482482
return makeTextElement(data.expression, 'expression')
483483
}
484484

485-
/** @since 1.5.1 */
485+
/**
486+
* If there is any {@link Models.LicenseExpression | LicenseExpression} in the set, then this is the only item that is normalized.
487+
*
488+
* @since 1.5.1
489+
*/
486490
normalizeIterable (data: SortableIterable<Models.License>, options: NormalizerOptions): SimpleXml.Element[] {
487-
return (
488-
options.sortLists ?? false
489-
? data.sorted()
490-
: Array.from(data)
491-
).map(c => this.normalize(c, options))
491+
const licenses = options.sortLists ?? false
492+
? data.sorted()
493+
: Array.from(data)
494+
495+
if (licenses.length > 1) {
496+
const expressions = licenses.filter(l => l instanceof Models.LicenseExpression) as Models.LicenseExpression[]
497+
if (expressions.length > 0) {
498+
// could have thrown {@link RangeError} when there is more than one only {@link Models.LicenseExpression | LicenseExpression}.
499+
// but let's be graceful and just normalize to the most relevant choice: any expression
500+
return [this.#normalizeLicenseExpression(expressions[0])]
501+
}
502+
}
503+
504+
return licenses.map(l => this.normalize(l, options))
492505
}
493506

494507
/** @deprecated use {@link normalizeIterable} instead of {@link normalizeRepository} */

tests/_data/models.js

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,10 @@ const { Enums, Models } = require('../../')
2323

2424
/* eslint-disable jsdoc/valid-types */
2525

26-
/**
27-
* @typedef {import('../../src/models/bom').Bom} Bom
28-
*/
29-
3026
/* eslint-enable jsdoc/valid-types */
3127

3228
/**
33-
* @returns {Bom}
29+
* @returns {Models.Bom}
3430
*/
3531
module.exports.createComplexStructure = function () {
3632
const bom = new Models.Bom({
@@ -127,7 +123,6 @@ module.exports.createComplexStructure = function () {
127123
license.url = new URL('https://spdx.org/licenses/MIT.html')
128124
return license
129125
})(new Models.SpdxLicense('MIT')))
130-
component.licenses.add(new Models.LicenseExpression('(MIT or Apache-2.0)'))
131126
component.publisher = 'the publisher'
132127
component.purl = new PackageURL('npm', 'acme', 'dummy-component', '1337-beta', undefined, undefined)
133128
component.scope = Enums.ComponentScope.Required
@@ -201,5 +196,44 @@ module.exports.createComplexStructure = function () {
201196
])
202197
}))
203198

199+
bom.components.add(
200+
new Models.Component(
201+
Enums.ComponentType.Library, 'component-with-licenses', {
202+
bomRef: 'component-with-licenses',
203+
licenses: new Models.LicenseRepository([
204+
new Models.NamedLicense('something'),
205+
new Models.SpdxLicense('MIT'),
206+
new Models.SpdxLicense('Apache-2.0')
207+
// no expression
208+
])
209+
}
210+
)
211+
)
212+
bom.components.add(
213+
new Models.Component(
214+
Enums.ComponentType.Library, 'component-with-licenseExpression', {
215+
bomRef: 'component-with-licenseExpression',
216+
licenses: new Models.LicenseRepository([
217+
new Models.LicenseExpression('(MIT OR Apache-2.0)')
218+
// no named nor SPDX
219+
])
220+
}
221+
)
222+
)
223+
bom.components.add(
224+
/* scenario: prefer any expression over other licenses */
225+
new Models.Component(
226+
Enums.ComponentType.Library, 'component-with-licenses-and-expression', {
227+
bomRef: 'component-with-licenses-and-expression',
228+
licenses: new Models.LicenseRepository([
229+
new Models.NamedLicense('something'),
230+
new Models.SpdxLicense('MIT'),
231+
new Models.SpdxLicense('Apache-2.0'),
232+
new Models.LicenseExpression('(MIT OR Apache-2.0)')
233+
])
234+
}
235+
)
236+
)
237+
204238
return bom
205239
}

tests/_data/normalizeResults/json_sortedLists_spec1.2.json

Lines changed: 54 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/_data/normalizeResults/json_sortedLists_spec1.3.json

Lines changed: 54 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/_data/normalizeResults/json_sortedLists_spec1.4.json

Lines changed: 51 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)