index.js 5.69 KB
var licenseIDs = require('spdx-license-ids');

function valid(string) {
  return licenseIDs.indexOf(string) > -1;
}

// 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-'
  function(argument) {
    return argument.slice(0, argument.length - 1);
  },
  // 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';
  }
];

// 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'],
  ['AGPL', 'AGPL-3.0'],
  ['APACHE', 'Apache-2.0'],
  ['ARTISTIC', 'Artistic-2.0'],
  ['Affero', 'AGPL-3.0'],
  ['BEER', 'Beerware'],
  ['BOOST', 'BSL-1.0'],
  ['BSD', 'BSD-2-Clause'],
  ['ECLIPSE', 'EPL-1.0'],
  ['FUCK', 'WTFPL'],
  ['GNU', 'GPL-3.0'],
  ['LGPL', 'LGPL-3.0'],
  ['GPL', 'GPL-3.0'],
  ['MIT', 'MIT'],
  ['MPL', 'MPL-2.0'],
  ['X11', 'X11'],
  ['ZLIB', 'Zlib']
];

var SUBSTRING = 0;
var IDENTIFIER = 1;

var validTransformation = function(identifier) {
  for (var i = 0; i < transforms.length; i++) {
    var transformed = transforms[i](identifier);
    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) {
  identifier = identifier.replace(/\+$/, '');
  if (valid(identifier)) {
    return identifier;
  }
  var transformed = validTransformation(identifier);
  if (transformed !== null) {
    return transformed;
  }
  transformed = anyCorrection(identifier, function(argument) {
    if (valid(argument)) {
      return argument;
    }
    return validTransformation(argument);
  });
  if (transformed !== null) {
    return transformed;
  }
  transformed = validLastResort(identifier);
  if (transformed !== null) {
    return transformed;
  }
  transformed = anyCorrection(identifier, validLastResort);
  if (transformed !== null) {
    return transformed;
  }
  return null;
};