218 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const {
 | |
|   MAX_SAFE_COMPONENT_LENGTH,
 | |
|   MAX_SAFE_BUILD_LENGTH,
 | |
|   MAX_LENGTH,
 | |
| } = require('./constants')
 | |
| const debug = require('./debug')
 | |
| exports = module.exports = {}
 | |
| 
 | |
| // The actual regexps go on exports.re
 | |
| const re = exports.re = []
 | |
| const safeRe = exports.safeRe = []
 | |
| const src = exports.src = []
 | |
| const t = exports.t = {}
 | |
| let R = 0
 | |
| 
 | |
| const LETTERDASHNUMBER = '[a-zA-Z0-9-]'
 | |
| 
 | |
| // Replace some greedy regex tokens to prevent regex dos issues. These regex are
 | |
| // used internally via the safeRe object since all inputs in this library get
 | |
| // normalized first to trim and collapse all extra whitespace. The original
 | |
| // regexes are exported for userland consumption and lower level usage. A
 | |
| // future breaking change could export the safer regex only with a note that
 | |
| // all input should have extra whitespace removed.
 | |
| const safeRegexReplacements = [
 | |
|   ['\\s', 1],
 | |
|   ['\\d', MAX_LENGTH],
 | |
|   [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH],
 | |
| ]
 | |
| 
 | |
| const makeSafeRegex = (value) => {
 | |
|   for (const [token, max] of safeRegexReplacements) {
 | |
|     value = value
 | |
|       .split(`${token}*`).join(`${token}{0,${max}}`)
 | |
|       .split(`${token}+`).join(`${token}{1,${max}}`)
 | |
|   }
 | |
|   return value
 | |
| }
 | |
| 
 | |
| const createToken = (name, value, isGlobal) => {
 | |
|   const safe = makeSafeRegex(value)
 | |
|   const index = R++
 | |
|   debug(name, index, value)
 | |
|   t[name] = index
 | |
|   src[index] = value
 | |
|   re[index] = new RegExp(value, isGlobal ? 'g' : undefined)
 | |
|   safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined)
 | |
| }
 | |
| 
 | |
| // The following Regular Expressions can be used for tokenizing,
 | |
| // validating, and parsing SemVer version strings.
 | |
| 
 | |
| // ## Numeric Identifier
 | |
| // A single `0`, or a non-zero digit followed by zero or more digits.
 | |
| 
 | |
| createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*')
 | |
| createToken('NUMERICIDENTIFIERLOOSE', '\\d+')
 | |
| 
 | |
| // ## Non-numeric Identifier
 | |
| // Zero or more digits, followed by a letter or hyphen, and then zero or
 | |
| // more letters, digits, or hyphens.
 | |
| 
 | |
| createToken('NONNUMERICIDENTIFIER', `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`)
 | |
| 
 | |
| // ## Main Version
 | |
| // Three dot-separated numeric identifiers.
 | |
| 
 | |
| createToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\.` +
 | |
|                    `(${src[t.NUMERICIDENTIFIER]})\\.` +
 | |
|                    `(${src[t.NUMERICIDENTIFIER]})`)
 | |
| 
 | |
| createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` +
 | |
|                         `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` +
 | |
|                         `(${src[t.NUMERICIDENTIFIERLOOSE]})`)
 | |
| 
 | |
| // ## Pre-release Version Identifier
 | |
| // A numeric identifier, or a non-numeric identifier.
 | |
| 
 | |
| createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NUMERICIDENTIFIER]
 | |
| }|${src[t.NONNUMERICIDENTIFIER]})`)
 | |
| 
 | |
| createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NUMERICIDENTIFIERLOOSE]
 | |
| }|${src[t.NONNUMERICIDENTIFIER]})`)
 | |
| 
 | |
| // ## Pre-release Version
 | |
| // Hyphen, followed by one or more dot-separated pre-release version
 | |
| // identifiers.
 | |
| 
 | |
| createToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER]
 | |
| }(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`)
 | |
| 
 | |
| createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE]
 | |
| }(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`)
 | |
| 
 | |
| // ## Build Metadata Identifier
 | |
| // Any combination of digits, letters, or hyphens.
 | |
| 
 | |
| createToken('BUILDIDENTIFIER', `${LETTERDASHNUMBER}+`)
 | |
| 
 | |
| // ## Build Metadata
 | |
| // Plus sign, followed by one or more period-separated build metadata
 | |
| // identifiers.
 | |
| 
 | |
| createToken('BUILD', `(?:\\+(${src[t.BUILDIDENTIFIER]
 | |
| }(?:\\.${src[t.BUILDIDENTIFIER]})*))`)
 | |
| 
 | |
| // ## Full Version String
 | |
| // A main version, followed optionally by a pre-release version and
 | |
| // build metadata.
 | |
| 
 | |
| // Note that the only major, minor, patch, and pre-release sections of
 | |
| // the version string are capturing groups.  The build metadata is not a
 | |
| // capturing group, because it should not ever be used in version
 | |
| // comparison.
 | |
| 
 | |
| createToken('FULLPLAIN', `v?${src[t.MAINVERSION]
 | |
| }${src[t.PRERELEASE]}?${
 | |
|   src[t.BUILD]}?`)
 | |
| 
 | |
| createToken('FULL', `^${src[t.FULLPLAIN]}$`)
 | |
| 
 | |
| // like full, but allows v1.2.3 and =1.2.3, which people do sometimes.
 | |
| // also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty
 | |
| // common in the npm registry.
 | |
| createToken('LOOSEPLAIN', `[v=\\s]*${src[t.MAINVERSIONLOOSE]
 | |
| }${src[t.PRERELEASELOOSE]}?${
 | |
|   src[t.BUILD]}?`)
 | |
| 
 | |
| createToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`)
 | |
| 
 | |
| createToken('GTLT', '((?:<|>)?=?)')
 | |
| 
 | |
| // Something like "2.*" or "1.2.x".
 | |
| // Note that "x.x" is a valid xRange identifer, meaning "any version"
 | |
| // Only the first item is strictly required.
 | |
| createToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`)
 | |
| createToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`)
 | |
| 
 | |
| createToken('XRANGEPLAIN', `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})` +
 | |
|                    `(?:\\.(${src[t.XRANGEIDENTIFIER]})` +
 | |
|                    `(?:\\.(${src[t.XRANGEIDENTIFIER]})` +
 | |
|                    `(?:${src[t.PRERELEASE]})?${
 | |
|                      src[t.BUILD]}?` +
 | |
|                    `)?)?`)
 | |
| 
 | |
| createToken('XRANGEPLAINLOOSE', `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` +
 | |
|                         `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` +
 | |
|                         `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` +
 | |
|                         `(?:${src[t.PRERELEASELOOSE]})?${
 | |
|                           src[t.BUILD]}?` +
 | |
|                         `)?)?`)
 | |
| 
 | |
| createToken('XRANGE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`)
 | |
| createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`)
 | |
| 
 | |
| // Coercion.
 | |
| // Extract anything that could conceivably be a part of a valid semver
 | |
| createToken('COERCEPLAIN', `${'(^|[^\\d])' +
 | |
|               '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` +
 | |
|               `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` +
 | |
|               `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?`)
 | |
| createToken('COERCE', `${src[t.COERCEPLAIN]}(?:$|[^\\d])`)
 | |
| createToken('COERCEFULL', src[t.COERCEPLAIN] +
 | |
|               `(?:${src[t.PRERELEASE]})?` +
 | |
|               `(?:${src[t.BUILD]})?` +
 | |
|               `(?:$|[^\\d])`)
 | |
| createToken('COERCERTL', src[t.COERCE], true)
 | |
| createToken('COERCERTLFULL', src[t.COERCEFULL], true)
 | |
| 
 | |
| // Tilde ranges.
 | |
| // Meaning is "reasonably at or greater than"
 | |
| createToken('LONETILDE', '(?:~>?)')
 | |
| 
 | |
| createToken('TILDETRIM', `(\\s*)${src[t.LONETILDE]}\\s+`, true)
 | |
| exports.tildeTrimReplace = '$1~'
 | |
| 
 | |
| createToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`)
 | |
| createToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`)
 | |
| 
 | |
| // Caret ranges.
 | |
| // Meaning is "at least and backwards compatible with"
 | |
| createToken('LONECARET', '(?:\\^)')
 | |
| 
 | |
| createToken('CARETTRIM', `(\\s*)${src[t.LONECARET]}\\s+`, true)
 | |
| exports.caretTrimReplace = '$1^'
 | |
| 
 | |
| createToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`)
 | |
| createToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`)
 | |
| 
 | |
| // A simple gt/lt/eq thing, or just "" to indicate "any version"
 | |
| createToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`)
 | |
| createToken('COMPARATOR', `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`)
 | |
| 
 | |
| // An expression to strip any whitespace between the gtlt and the thing
 | |
| // it modifies, so that `> 1.2.3` ==> `>1.2.3`
 | |
| createToken('COMPARATORTRIM', `(\\s*)${src[t.GTLT]
 | |
| }\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true)
 | |
| exports.comparatorTrimReplace = '$1$2$3'
 | |
| 
 | |
| // Something like `1.2.3 - 1.2.4`
 | |
| // Note that these all use the loose form, because they'll be
 | |
| // checked against either the strict or loose comparator form
 | |
| // later.
 | |
| createToken('HYPHENRANGE', `^\\s*(${src[t.XRANGEPLAIN]})` +
 | |
|                    `\\s+-\\s+` +
 | |
|                    `(${src[t.XRANGEPLAIN]})` +
 | |
|                    `\\s*$`)
 | |
| 
 | |
| createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` +
 | |
|                         `\\s+-\\s+` +
 | |
|                         `(${src[t.XRANGEPLAINLOOSE]})` +
 | |
|                         `\\s*$`)
 | |
| 
 | |
| // Star ranges basically just allow anything at all.
 | |
| createToken('STAR', '(<|>)?=?\\s*\\*')
 | |
| // >=0.0.0 is like a star
 | |
| createToken('GTE0', '^\\s*>=\\s*0\\.0\\.0\\s*$')
 | |
| createToken('GTE0PRE', '^\\s*>=\\s*0\\.0\\.0-0\\s*$')
 |