UNPKG

9.66 kBJavaScriptView Raw
1/*
2Copyright spdx-correct.js contributors
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16var parse = require('spdx-expression-parse')
17var spdxLicenseIds = require('spdx-license-ids')
18
19function valid (string) {
20 try {
21 parse(string)
22 return true
23 } catch (error) {
24 return false
25 }
26}
27
28// Common transpositions of license identifier acronyms
29var transpositions = [
30 ['APGL', 'AGPL'],
31 ['Gpl', 'GPL'],
32 ['GLP', 'GPL'],
33 ['APL', 'Apache'],
34 ['ISD', 'ISC'],
35 ['GLP', 'GPL'],
36 ['IST', 'ISC'],
37 ['Claude', 'Clause'],
38 [' or later', '+'],
39 [' International', ''],
40 ['GNU', 'GPL'],
41 ['GUN', 'GPL'],
42 ['+', ''],
43 ['GNU GPL', 'GPL'],
44 ['GNU/GPL', 'GPL'],
45 ['GNU GLP', 'GPL'],
46 ['GNU General Public License', 'GPL'],
47 ['Gnu public license', 'GPL'],
48 ['GNU Public License', 'GPL'],
49 ['GNU GENERAL PUBLIC LICENSE', 'GPL'],
50 ['MTI', 'MIT'],
51 ['Mozilla Public License', 'MPL'],
52 ['Universal Permissive License', 'UPL'],
53 ['WTH', 'WTF'],
54 ['-License', '']
55]
56
57var TRANSPOSED = 0
58var CORRECT = 1
59
60// Simple corrections to nearly valid identifiers.
61var transforms = [
62 // e.g. 'mit'
63 function (argument) {
64 return argument.toUpperCase()
65 },
66 // e.g. 'MIT '
67 function (argument) {
68 return argument.trim()
69 },
70 // e.g. 'M.I.T.'
71 function (argument) {
72 return argument.replace(/\./g, '')
73 },
74 // e.g. 'Apache- 2.0'
75 function (argument) {
76 return argument.replace(/\s+/g, '')
77 },
78 // e.g. 'CC BY 4.0''
79 function (argument) {
80 return argument.replace(/\s+/g, '-')
81 },
82 // e.g. 'LGPLv2.1'
83 function (argument) {
84 return argument.replace('v', '-')
85 },
86 // e.g. 'Apache 2.0'
87 function (argument) {
88 return argument.replace(/,?\s*(\d)/, '-$1')
89 },
90 // e.g. 'GPL 2'
91 function (argument) {
92 return argument.replace(/,?\s*(\d)/, '-$1.0')
93 },
94 // e.g. 'Apache Version 2.0'
95 function (argument) {
96 return argument
97 .replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2')
98 },
99 // e.g. 'Apache Version 2'
100 function (argument) {
101 return argument
102 .replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2.0')
103 },
104 // e.g. 'ZLIB'
105 function (argument) {
106 return argument[0].toUpperCase() + argument.slice(1)
107 },
108 // e.g. 'MPL/2.0'
109 function (argument) {
110 return argument.replace('/', '-')
111 },
112 // e.g. 'Apache 2'
113 function (argument) {
114 return argument
115 .replace(/\s*V\s*(\d)/, '-$1')
116 .replace(/(\d)$/, '$1.0')
117 },
118 // e.g. 'GPL-2.0', 'GPL-3.0'
119 function (argument) {
120 if (argument.indexOf('3.0') !== -1) {
121 return argument + '-or-later'
122 } else {
123 return argument + '-only'
124 }
125 },
126 // e.g. 'GPL-2.0-'
127 function (argument) {
128 return argument + 'only'
129 },
130 // e.g. 'GPL2'
131 function (argument) {
132 return argument.replace(/(\d)$/, '-$1.0')
133 },
134 // e.g. 'BSD 3'
135 function (argument) {
136 return argument.replace(/(-| )?(\d)$/, '-$2-Clause')
137 },
138 // e.g. 'BSD clause 3'
139 function (argument) {
140 return argument.replace(/(-| )clause(-| )(\d)/, '-$3-Clause')
141 },
142 // e.g. 'New BSD license'
143 function (argument) {
144 return argument.replace(/\b(Modified|New|Revised)(-| )?BSD((-| )License)?/i, 'BSD-3-Clause')
145 },
146 // e.g. 'Simplified BSD license'
147 function (argument) {
148 return argument.replace(/\bSimplified(-| )?BSD((-| )License)?/i, 'BSD-2-Clause')
149 },
150 // e.g. 'Free BSD license'
151 function (argument) {
152 return argument.replace(/\b(Free|Net)(-| )?BSD((-| )License)?/i, 'BSD-2-Clause-$1BSD')
153 },
154 // e.g. 'Clear BSD license'
155 function (argument) {
156 return argument.replace(/\bClear(-| )?BSD((-| )License)?/i, 'BSD-3-Clause-Clear')
157 },
158 // e.g. 'Old BSD License'
159 function (argument) {
160 return argument.replace(/\b(Old|Original)(-| )?BSD((-| )License)?/i, 'BSD-4-Clause')
161 },
162 // e.g. 'BY-NC-4.0'
163 function (argument) {
164 return 'CC-' + argument
165 },
166 // e.g. 'BY-NC'
167 function (argument) {
168 return 'CC-' + argument + '-4.0'
169 },
170 // e.g. 'Attribution-NonCommercial'
171 function (argument) {
172 return argument
173 .replace('Attribution', 'BY')
174 .replace('NonCommercial', 'NC')
175 .replace('NoDerivatives', 'ND')
176 .replace(/ (\d)/, '-$1')
177 .replace(/ ?International/, '')
178 },
179 // e.g. 'Attribution-NonCommercial'
180 function (argument) {
181 return 'CC-' +
182 argument
183 .replace('Attribution', 'BY')
184 .replace('NonCommercial', 'NC')
185 .replace('NoDerivatives', 'ND')
186 .replace(/ (\d)/, '-$1')
187 .replace(/ ?International/, '') +
188 '-4.0'
189 }
190]
191
192var licensesWithVersions = spdxLicenseIds
193 .map(function (id) {
194 var match = /^(.*)-\d+\.\d+$/.exec(id)
195 return match
196 ? [match[0], match[1]]
197 : [id, null]
198 })
199 .reduce(function (objectMap, item) {
200 var key = item[1]
201 objectMap[key] = objectMap[key] || []
202 objectMap[key].push(item[0])
203 return objectMap
204 }, {})
205
206var licensesWithOneVersion = Object.keys(licensesWithVersions)
207 .map(function makeEntries (key) {
208 return [key, licensesWithVersions[key]]
209 })
210 .filter(function identifySoleVersions (item) {
211 return (
212 // Licenses has just one valid version suffix.
213 item[1].length === 1 &&
214 item[0] !== null &&
215 // APL will be considered Apache, rather than APL-1.0
216 item[0] !== 'APL'
217 )
218 })
219 .map(function createLastResorts (item) {
220 return [item[0], item[1][0]]
221 })
222
223licensesWithVersions = undefined
224
225// If all else fails, guess that strings containing certain substrings
226// meant to identify certain licenses.
227var lastResorts = [
228 ['UNLI', 'Unlicense'],
229 ['WTF', 'WTFPL'],
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'],
244 ['FUCK', 'WTFPL'],
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'],
253 ['MIT', 'MIT'],
254 ['MPL', 'MPL-2.0'],
255 ['X11', 'X11'],
256 ['ZLIB', 'Zlib']
257].concat(licensesWithOneVersion)
258
259var SUBSTRING = 0
260var IDENTIFIER = 1
261
262var 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)) {
266 return transformed
267 }
268 }
269 return null
270}
271
272var 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]
278 }
279 }
280 return null
281}
282
283var 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(
289 transposed,
290 transposition[CORRECT]
291 )
292 var checked = check(corrected)
293 if (checked !== null) {
294 return checked
295 }
296 }
297 }
298 return null
299}
300
301module.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
306 }
307 var validArugment = (
308 typeof identifier === 'string' &&
309 identifier.trim().length !== 0
310 )
311 if (!validArugment) {
312 throw Error('Invalid argument. Expected non-empty string.')
313 }
314 identifier = identifier.trim()
315 if (valid(identifier)) {
316 return postprocess(identifier)
317 }
318 var noPlus = identifier.replace(/\+$/, '').trim()
319 if (valid(noPlus)) {
320 return postprocess(noPlus)
321 }
322 var transformed = validTransformation(identifier)
323 if (transformed !== null) {
324 return postprocess(transformed)
325 }
326 transformed = anyCorrection(identifier, function (argument) {
327 if (valid(argument)) {
328 return argument
329 }
330 return validTransformation(argument)
331 })
332 if (transformed !== null) {
333 return postprocess(transformed)
334 }
335 transformed = validLastResort(identifier)
336 if (transformed !== null) {
337 return postprocess(transformed)
338 }
339 transformed = anyCorrection(identifier, validLastResort)
340 if (transformed !== null) {
341 return postprocess(transformed)
342 }
343 return null
344}
345
346function upgradeGPLs (value) {
347 if ([
348 'GPL-1.0', 'LGPL-1.0', 'AGPL-1.0',
349 'GPL-2.0', 'LGPL-2.0', 'AGPL-2.0',
350 'LGPL-2.1'
351 ].indexOf(value) !== -1) {
352 return value + '-only'
353 } else if ([
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'
361 } else {
362 return value
363 }
364}