| 'use strict' |
| |
| 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 safeSrc = exports.safeSrc = [] |
| 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 |
| safeSrc[index] = safe |
| 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. |
| // Non-numberic identifiers include numberic identifiers but can be longer. |
| // Therefore non-numberic identifiers must go first. |
| |
| createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NONNUMERICIDENTIFIER] |
| }|${src[t.NUMERICIDENTIFIER]})`) |
| |
| createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NONNUMERICIDENTIFIER] |
| }|${src[t.NUMERICIDENTIFIERLOOSE]})`) |
| |
| // ## 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*$') |