2 Copyright spdx-correct.js contributors
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
8 http://www.apache.org/licenses/LICENSE-2.0
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.
16 var parse = require('spdx-expression-parse')
17 var spdxLicenseIds = require('spdx-license-ids')
19 function valid (string) {
28 // Common transpositions of license identifier acronyms
29 var transpositions = [
39 [' International', ''],
46 ['GNU General Public License', 'GPL'],
47 ['Gnu public license', 'GPL'],
48 ['GNU Public License', 'GPL'],
49 ['GNU GENERAL PUBLIC LICENSE', 'GPL'],
51 ['Mozilla Public License', 'MPL'],
52 ['Universal Permissive License', 'UPL'],
60 // Simple corrections to nearly valid identifiers.
64 return argument.toUpperCase()
68 return argument.trim()
72 return argument.replace(/\./g, '')
76 return argument.replace(/\s+/g, '')
80 return argument.replace(/\s+/g, '-')
84 return argument.replace('v', '-')
88 return argument.replace(/,?\s*(\d)/, '-$1')
92 return argument.replace(/,?\s*(\d)/, '-$1.0')
94 // e.g. 'Apache Version 2.0'
97 .replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2')
99 // e.g. 'Apache Version 2'
100 function (argument) {
102 .replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2.0')
105 function (argument) {
106 return argument[0].toUpperCase() + argument.slice(1)
109 function (argument) {
110 return argument.replace('/', '-')
113 function (argument) {
115 .replace(/\s*V\s*(\d)/, '-$1')
116 .replace(/(\d)$/, '$1.0')
118 // e.g. 'GPL-2.0', 'GPL-3.0'
119 function (argument) {
120 if (argument.indexOf('3.0') !== -1) {
121 return argument + '-or-later'
123 return argument + '-only'
127 function (argument) {
128 return argument + 'only'
131 function (argument) {
132 return argument.replace(/(\d)$/, '-$1.0')
135 function (argument) {
136 return argument.replace(/(-| )?(\d)$/, '-$2-Clause')
138 // e.g. 'BSD clause 3'
139 function (argument) {
140 return argument.replace(/(-| )clause(-| )(\d)/, '-$3-Clause')
142 // e.g. 'New BSD license'
143 function (argument) {
144 return argument.replace(/\b(Modified|New|Revised)(-| )?BSD((-| )License)?/i, 'BSD-3-Clause')
146 // e.g. 'Simplified BSD license'
147 function (argument) {
148 return argument.replace(/\bSimplified(-| )?BSD((-| )License)?/i, 'BSD-2-Clause')
150 // e.g. 'Free BSD license'
151 function (argument) {
152 return argument.replace(/\b(Free|Net)(-| )?BSD((-| )License)?/i, 'BSD-2-Clause-$1BSD')
154 // e.g. 'Clear BSD license'
155 function (argument) {
156 return argument.replace(/\bClear(-| )?BSD((-| )License)?/i, 'BSD-3-Clause-Clear')
158 // e.g. 'Old BSD License'
159 function (argument) {
160 return argument.replace(/\b(Old|Original)(-| )?BSD((-| )License)?/i, 'BSD-4-Clause')
163 function (argument) {
164 return 'CC-' + argument
167 function (argument) {
168 return 'CC-' + argument + '-4.0'
170 // e.g. 'Attribution-NonCommercial'
171 function (argument) {
173 .replace('Attribution', 'BY')
174 .replace('NonCommercial', 'NC')
175 .replace('NoDerivatives', 'ND')
176 .replace(/ (\d)/, '-$1')
177 .replace(/ ?International/, '')
179 // e.g. 'Attribution-NonCommercial'
180 function (argument) {
183 .replace('Attribution', 'BY')
184 .replace('NonCommercial', 'NC')
185 .replace('NoDerivatives', 'ND')
186 .replace(/ (\d)/, '-$1')
187 .replace(/ ?International/, '') +
192 var licensesWithVersions = spdxLicenseIds
194 var match = /^(.*)-\d+\.\d+$/.exec(id)
196 ? [match[0], match[1]]
199 .reduce(function (objectMap, item) {
201 objectMap[key] = objectMap[key] || []
202 objectMap[key].push(item[0])
206 var licensesWithOneVersion = Object.keys(licensesWithVersions)
207 .map(function makeEntries (key) {
208 return [key, licensesWithVersions[key]]
210 .filter(function identifySoleVersions (item) {
212 // Licenses has just one valid version suffix.
213 item[1].length === 1 &&
215 // APL will be considered Apache, rather than APL-1.0
219 .map(function createLastResorts (item) {
220 return [item[0], item[1][0]]
223 licensesWithVersions = undefined
225 // If all else fails, guess that strings containing certain substrings
226 // meant to identify certain licenses.
228 ['UNLI', 'Unlicense'],
230 ['2 CLAUSE', 'BSD-2-Clause'],
231 ['2-CLAUSE', 'BSD-2-Clause'],
232 ['3 CLAUSE', 'BSD-3-Clause'],
233 ['3-CLAUSE', 'BSD-3-Clause'],
234 ['AFFERO', 'AGPL-3.0-or-later'],
235 ['AGPL', 'AGPL-3.0-or-later'],
236 ['APACHE', 'Apache-2.0'],
237 ['ARTISTIC', 'Artistic-2.0'],
238 ['Affero', 'AGPL-3.0-or-later'],
239 ['BEER', 'Beerware'],
240 ['BOOST', 'BSL-1.0'],
241 ['BSD', 'BSD-2-Clause'],
242 ['CDDL', 'CDDL-1.1'],
243 ['ECLIPSE', 'EPL-1.0'],
245 ['GNU', 'GPL-3.0-or-later'],
246 ['LGPL', 'LGPL-3.0-or-later'],
247 ['GPLV1', 'GPL-1.0-only'],
248 ['GPL-1', 'GPL-1.0-only'],
249 ['GPLV2', 'GPL-2.0-only'],
250 ['GPL-2', 'GPL-2.0-only'],
251 ['GPL', 'GPL-3.0-or-later'],
252 ['MIT +NO-FALSE-ATTRIBS', 'MITNFA'],
257 ].concat(licensesWithOneVersion)
262 var validTransformation = function (identifier) {
263 for (var i = 0; i < transforms.length; i++) {
264 var transformed = transforms[i](identifier).trim()
265 if (transformed !== identifier && valid(transformed)) {
272 var validLastResort = function (identifier) {
273 var upperCased = identifier.toUpperCase()
274 for (var i = 0; i < lastResorts.length; i++) {
275 var lastResort = lastResorts[i]
276 if (upperCased.indexOf(lastResort[SUBSTRING]) > -1) {
277 return lastResort[IDENTIFIER]
283 var anyCorrection = function (identifier, check) {
284 for (var i = 0; i < transpositions.length; i++) {
285 var transposition = transpositions[i]
286 var transposed = transposition[TRANSPOSED]
287 if (identifier.indexOf(transposed) > -1) {
288 var corrected = identifier.replace(
290 transposition[CORRECT]
292 var checked = check(corrected)
293 if (checked !== null) {
301 module.exports = function (identifier, options) {
302 options = options || {}
303 var upgrade = options.upgrade === undefined ? true : !!options.upgrade
304 function postprocess (value) {
305 return upgrade ? upgradeGPLs(value) : value
307 var validArugment = (
308 typeof identifier === 'string' &&
309 identifier.trim().length !== 0
311 if (!validArugment) {
312 throw Error('Invalid argument. Expected non-empty string.')
314 identifier = identifier.trim()
315 if (valid(identifier)) {
316 return postprocess(identifier)
318 var noPlus = identifier.replace(/\+$/, '').trim()
320 return postprocess(noPlus)
322 var transformed = validTransformation(identifier)
323 if (transformed !== null) {
324 return postprocess(transformed)
326 transformed = anyCorrection(identifier, function (argument) {
327 if (valid(argument)) {
330 return validTransformation(argument)
332 if (transformed !== null) {
333 return postprocess(transformed)
335 transformed = validLastResort(identifier)
336 if (transformed !== null) {
337 return postprocess(transformed)
339 transformed = anyCorrection(identifier, validLastResort)
340 if (transformed !== null) {
341 return postprocess(transformed)
346 function upgradeGPLs (value) {
348 'GPL-1.0', 'LGPL-1.0', 'AGPL-1.0',
349 'GPL-2.0', 'LGPL-2.0', 'AGPL-2.0',
351 ].indexOf(value) !== -1) {
352 return value + '-only'
354 'GPL-1.0+', 'GPL-2.0+', 'GPL-3.0+',
355 'LGPL-2.0+', 'LGPL-2.1+', 'LGPL-3.0+',
356 'AGPL-1.0+', 'AGPL-3.0+'
357 ].indexOf(value) !== -1) {
358 return value.replace(/\+$/, '-or-later')
359 } else if (['GPL-3.0', 'LGPL-3.0', 'AGPL-3.0'].indexOf(value) !== -1) {
360 return value + '-or-later'