344 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			344 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | /* | ||
|  | 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'], | ||
|  |   ['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. '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 | ||
|  |   } | ||
|  | } |