213 lines
7.5 KiB
JavaScript
213 lines
7.5 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('COERCE', `${'(^|[^\\d])' +
|
||
|
'(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` +
|
||
|
`(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` +
|
||
|
`(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` +
|
||
|
`(?:$|[^\\d])`)
|
||
|
createToken('COERCERTL', src[t.COERCE], 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*$')
|