You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
364 lines
9.4 KiB
364 lines
9.4 KiB
/* |
|
Copyright spdx-correct.js contributors |
|
|
|
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. |
|
*/ |
|
var parse = require('spdx-expression-parse') |
|
var spdxLicenseIds = require('spdx-license-ids') |
|
|
|
function valid (string) { |
|
try { |
|
parse(string) |
|
return true |
|
} catch (error) { |
|
return false |
|
} |
|
} |
|
|
|
// Common transpositions of license identifier acronyms |
|
var transpositions = [ |
|
['APGL', 'AGPL'], |
|
['Gpl', 'GPL'], |
|
['GLP', 'GPL'], |
|
['APL', 'Apache'], |
|
['ISD', 'ISC'], |
|
['GLP', 'GPL'], |
|
['IST', 'ISC'], |
|
['Claude', 'Clause'], |
|
[' or later', '+'], |
|
[' International', ''], |
|
['GNU', 'GPL'], |
|
['GUN', 'GPL'], |
|
['+', ''], |
|
['GNU GPL', 'GPL'], |
|
['GNU/GPL', 'GPL'], |
|
['GNU GLP', 'GPL'], |
|
['GNU General Public License', 'GPL'], |
|
['Gnu public license', 'GPL'], |
|
['GNU Public License', 'GPL'], |
|
['GNU GENERAL PUBLIC LICENSE', 'GPL'], |
|
['MTI', 'MIT'], |
|
['Mozilla Public License', 'MPL'], |
|
['Universal Permissive License', 'UPL'], |
|
['WTH', 'WTF'], |
|
['-License', ''] |
|
] |
|
|
|
var TRANSPOSED = 0 |
|
var CORRECT = 1 |
|
|
|
// Simple corrections to nearly valid identifiers. |
|
var transforms = [ |
|
// e.g. 'mit' |
|
function (argument) { |
|
return argument.toUpperCase() |
|
}, |
|
// e.g. 'MIT ' |
|
function (argument) { |
|
return argument.trim() |
|
}, |
|
// e.g. 'M.I.T.' |
|
function (argument) { |
|
return argument.replace(/\./g, '') |
|
}, |
|
// e.g. 'Apache- 2.0' |
|
function (argument) { |
|
return argument.replace(/\s+/g, '') |
|
}, |
|
// e.g. 'CC BY 4.0'' |
|
function (argument) { |
|
return argument.replace(/\s+/g, '-') |
|
}, |
|
// e.g. 'LGPLv2.1' |
|
function (argument) { |
|
return argument.replace('v', '-') |
|
}, |
|
// e.g. 'Apache 2.0' |
|
function (argument) { |
|
return argument.replace(/,?\s*(\d)/, '-$1') |
|
}, |
|
// e.g. 'GPL 2' |
|
function (argument) { |
|
return argument.replace(/,?\s*(\d)/, '-$1.0') |
|
}, |
|
// e.g. 'Apache Version 2.0' |
|
function (argument) { |
|
return argument |
|
.replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2') |
|
}, |
|
// e.g. 'Apache Version 2' |
|
function (argument) { |
|
return argument |
|
.replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2.0') |
|
}, |
|
// e.g. 'ZLIB' |
|
function (argument) { |
|
return argument[0].toUpperCase() + argument.slice(1) |
|
}, |
|
// e.g. 'MPL/2.0' |
|
function (argument) { |
|
return argument.replace('/', '-') |
|
}, |
|
// e.g. 'Apache 2' |
|
function (argument) { |
|
return argument |
|
.replace(/\s*V\s*(\d)/, '-$1') |
|
.replace(/(\d)$/, '$1.0') |
|
}, |
|
// e.g. 'GPL-2.0', 'GPL-3.0' |
|
function (argument) { |
|
if (argument.indexOf('3.0') !== -1) { |
|
return argument + '-or-later' |
|
} else { |
|
return argument + '-only' |
|
} |
|
}, |
|
// e.g. 'GPL-2.0-' |
|
function (argument) { |
|
return argument + 'only' |
|
}, |
|
// e.g. 'GPL2' |
|
function (argument) { |
|
return argument.replace(/(\d)$/, '-$1.0') |
|
}, |
|
// e.g. 'BSD 3' |
|
function (argument) { |
|
return argument.replace(/(-| )?(\d)$/, '-$2-Clause') |
|
}, |
|
// e.g. 'BSD clause 3' |
|
function (argument) { |
|
return argument.replace(/(-| )clause(-| )(\d)/, '-$3-Clause') |
|
}, |
|
// e.g. 'New BSD license' |
|
function (argument) { |
|
return argument.replace(/\b(Modified|New|Revised)(-| )?BSD((-| )License)?/i, 'BSD-3-Clause') |
|
}, |
|
// e.g. 'Simplified BSD license' |
|
function (argument) { |
|
return argument.replace(/\bSimplified(-| )?BSD((-| )License)?/i, 'BSD-2-Clause') |
|
}, |
|
// e.g. 'Free BSD license' |
|
function (argument) { |
|
return argument.replace(/\b(Free|Net)(-| )?BSD((-| )License)?/i, 'BSD-2-Clause-$1BSD') |
|
}, |
|
// e.g. 'Clear BSD license' |
|
function (argument) { |
|
return argument.replace(/\bClear(-| )?BSD((-| )License)?/i, 'BSD-3-Clause-Clear') |
|
}, |
|
// e.g. 'Old BSD License' |
|
function (argument) { |
|
return argument.replace(/\b(Old|Original)(-| )?BSD((-| )License)?/i, 'BSD-4-Clause') |
|
}, |
|
// e.g. 'BY-NC-4.0' |
|
function (argument) { |
|
return 'CC-' + argument |
|
}, |
|
// e.g. 'BY-NC' |
|
function (argument) { |
|
return 'CC-' + argument + '-4.0' |
|
}, |
|
// e.g. 'Attribution-NonCommercial' |
|
function (argument) { |
|
return argument |
|
.replace('Attribution', 'BY') |
|
.replace('NonCommercial', 'NC') |
|
.replace('NoDerivatives', 'ND') |
|
.replace(/ (\d)/, '-$1') |
|
.replace(/ ?International/, '') |
|
}, |
|
// e.g. 'Attribution-NonCommercial' |
|
function (argument) { |
|
return 'CC-' + |
|
argument |
|
.replace('Attribution', 'BY') |
|
.replace('NonCommercial', 'NC') |
|
.replace('NoDerivatives', 'ND') |
|
.replace(/ (\d)/, '-$1') |
|
.replace(/ ?International/, '') + |
|
'-4.0' |
|
} |
|
] |
|
|
|
var licensesWithVersions = spdxLicenseIds |
|
.map(function (id) { |
|
var match = /^(.*)-\d+\.\d+$/.exec(id) |
|
return match |
|
? [match[0], match[1]] |
|
: [id, null] |
|
}) |
|
.reduce(function (objectMap, item) { |
|
var key = item[1] |
|
objectMap[key] = objectMap[key] || [] |
|
objectMap[key].push(item[0]) |
|
return objectMap |
|
}, {}) |
|
|
|
var licensesWithOneVersion = Object.keys(licensesWithVersions) |
|
.map(function makeEntries (key) { |
|
return [key, licensesWithVersions[key]] |
|
}) |
|
.filter(function identifySoleVersions (item) { |
|
return ( |
|
// Licenses has just one valid version suffix. |
|
item[1].length === 1 && |
|
item[0] !== null && |
|
// APL will be considered Apache, rather than APL-1.0 |
|
item[0] !== 'APL' |
|
) |
|
}) |
|
.map(function createLastResorts (item) { |
|
return [item[0], item[1][0]] |
|
}) |
|
|
|
licensesWithVersions = undefined |
|
|
|
// If all else fails, guess that strings containing certain substrings |
|
// meant to identify certain licenses. |
|
var lastResorts = [ |
|
['UNLI', 'Unlicense'], |
|
['WTF', 'WTFPL'], |
|
['2 CLAUSE', 'BSD-2-Clause'], |
|
['2-CLAUSE', 'BSD-2-Clause'], |
|
['3 CLAUSE', 'BSD-3-Clause'], |
|
['3-CLAUSE', 'BSD-3-Clause'], |
|
['AFFERO', 'AGPL-3.0-or-later'], |
|
['AGPL', 'AGPL-3.0-or-later'], |
|
['APACHE', 'Apache-2.0'], |
|
['ARTISTIC', 'Artistic-2.0'], |
|
['Affero', 'AGPL-3.0-or-later'], |
|
['BEER', 'Beerware'], |
|
['BOOST', 'BSL-1.0'], |
|
['BSD', 'BSD-2-Clause'], |
|
['CDDL', 'CDDL-1.1'], |
|
['ECLIPSE', 'EPL-1.0'], |
|
['FUCK', 'WTFPL'], |
|
['GNU', 'GPL-3.0-or-later'], |
|
['LGPL', 'LGPL-3.0-or-later'], |
|
['GPLV1', 'GPL-1.0-only'], |
|
['GPL-1', 'GPL-1.0-only'], |
|
['GPLV2', 'GPL-2.0-only'], |
|
['GPL-2', 'GPL-2.0-only'], |
|
['GPL', 'GPL-3.0-or-later'], |
|
['MIT +NO-FALSE-ATTRIBS', 'MITNFA'], |
|
['MIT', 'MIT'], |
|
['MPL', 'MPL-2.0'], |
|
['X11', 'X11'], |
|
['ZLIB', 'Zlib'] |
|
].concat(licensesWithOneVersion) |
|
|
|
var SUBSTRING = 0 |
|
var IDENTIFIER = 1 |
|
|
|
var validTransformation = function (identifier) { |
|
for (var i = 0; i < transforms.length; i++) { |
|
var transformed = transforms[i](identifier).trim() |
|
if (transformed !== identifier && valid(transformed)) { |
|
return transformed |
|
} |
|
} |
|
return null |
|
} |
|
|
|
var validLastResort = function (identifier) { |
|
var upperCased = identifier.toUpperCase() |
|
for (var i = 0; i < lastResorts.length; i++) { |
|
var lastResort = lastResorts[i] |
|
if (upperCased.indexOf(lastResort[SUBSTRING]) > -1) { |
|
return lastResort[IDENTIFIER] |
|
} |
|
} |
|
return null |
|
} |
|
|
|
var anyCorrection = function (identifier, check) { |
|
for (var i = 0; i < transpositions.length; i++) { |
|
var transposition = transpositions[i] |
|
var transposed = transposition[TRANSPOSED] |
|
if (identifier.indexOf(transposed) > -1) { |
|
var corrected = identifier.replace( |
|
transposed, |
|
transposition[CORRECT] |
|
) |
|
var checked = check(corrected) |
|
if (checked !== null) { |
|
return checked |
|
} |
|
} |
|
} |
|
return null |
|
} |
|
|
|
module.exports = function (identifier, options) { |
|
options = options || {} |
|
var upgrade = options.upgrade === undefined ? true : !!options.upgrade |
|
function postprocess (value) { |
|
return upgrade ? upgradeGPLs(value) : value |
|
} |
|
var validArugment = ( |
|
typeof identifier === 'string' && |
|
identifier.trim().length !== 0 |
|
) |
|
if (!validArugment) { |
|
throw Error('Invalid argument. Expected non-empty string.') |
|
} |
|
identifier = identifier.trim() |
|
if (valid(identifier)) { |
|
return postprocess(identifier) |
|
} |
|
var noPlus = identifier.replace(/\+$/, '').trim() |
|
if (valid(noPlus)) { |
|
return postprocess(noPlus) |
|
} |
|
var transformed = validTransformation(identifier) |
|
if (transformed !== null) { |
|
return postprocess(transformed) |
|
} |
|
transformed = anyCorrection(identifier, function (argument) { |
|
if (valid(argument)) { |
|
return argument |
|
} |
|
return validTransformation(argument) |
|
}) |
|
if (transformed !== null) { |
|
return postprocess(transformed) |
|
} |
|
transformed = validLastResort(identifier) |
|
if (transformed !== null) { |
|
return postprocess(transformed) |
|
} |
|
transformed = anyCorrection(identifier, validLastResort) |
|
if (transformed !== null) { |
|
return postprocess(transformed) |
|
} |
|
return null |
|
} |
|
|
|
function upgradeGPLs (value) { |
|
if ([ |
|
'GPL-1.0', 'LGPL-1.0', 'AGPL-1.0', |
|
'GPL-2.0', 'LGPL-2.0', 'AGPL-2.0', |
|
'LGPL-2.1' |
|
].indexOf(value) !== -1) { |
|
return value + '-only' |
|
} else if ([ |
|
'GPL-1.0+', 'GPL-2.0+', 'GPL-3.0+', |
|
'LGPL-2.0+', 'LGPL-2.1+', 'LGPL-3.0+', |
|
'AGPL-1.0+', 'AGPL-3.0+' |
|
].indexOf(value) !== -1) { |
|
return value.replace(/\+$/, '-or-later') |
|
} else if (['GPL-3.0', 'LGPL-3.0', 'AGPL-3.0'].indexOf(value) !== -1) { |
|
return value + '-or-later' |
|
} else { |
|
return value |
|
} |
|
}
|
|
|