1const { 2 MAX_SAFE_COMPONENT_LENGTH, 3 MAX_SAFE_BUILD_LENGTH, 4 MAX_LENGTH, 5} = require('./constants') 6const debug = require('./debug') 7exports = module.exports = {} 8 9// The actual regexps go on exports.re 10const re = exports.re = [] 11const safeRe = exports.safeRe = [] 12const src = exports.src = [] 13const t = exports.t = {} 14let R = 0 15 16const LETTERDASHNUMBER = '[a-zA-Z0-9-]' 17 18// Replace some greedy regex tokens to prevent regex dos issues. These regex are 19// used internally via the safeRe object since all inputs in this library get 20// normalized first to trim and collapse all extra whitespace. The original 21// regexes are exported for userland consumption and lower level usage. A 22// future breaking change could export the safer regex only with a note that 23// all input should have extra whitespace removed. 24const safeRegexReplacements = [ 25 ['\\s', 1], 26 ['\\d', MAX_LENGTH], 27 [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH], 28] 29 30const makeSafeRegex = (value) => { 31 for (const [token, max] of safeRegexReplacements) { 32 value = value 33 .split(`${token}*`).join(`${token}{0,${max}}`) 34 .split(`${token}+`).join(`${token}{1,${max}}`) 35 } 36 return value 37} 38 39const createToken = (name, value, isGlobal) => { 40 const safe = makeSafeRegex(value) 41 const index = R++ 42 debug(name, index, value) 43 t[name] = index 44 src[index] = value 45 re[index] = new RegExp(value, isGlobal ? 'g' : undefined) 46 safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined) 47} 48 49// The following Regular Expressions can be used for tokenizing, 50// validating, and parsing SemVer version strings. 51 52// ## Numeric Identifier 53// A single `0`, or a non-zero digit followed by zero or more digits. 54 55createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*') 56createToken('NUMERICIDENTIFIERLOOSE', '\\d+') 57 58// ## Non-numeric Identifier 59// Zero or more digits, followed by a letter or hyphen, and then zero or 60// more letters, digits, or hyphens. 61 62createToken('NONNUMERICIDENTIFIER', `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`) 63 64// ## Main Version 65// Three dot-separated numeric identifiers. 66 67createToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\.` + 68 `(${src[t.NUMERICIDENTIFIER]})\\.` + 69 `(${src[t.NUMERICIDENTIFIER]})`) 70 71createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + 72 `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + 73 `(${src[t.NUMERICIDENTIFIERLOOSE]})`) 74 75// ## Pre-release Version Identifier 76// A numeric identifier, or a non-numeric identifier. 77 78createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NUMERICIDENTIFIER] 79}|${src[t.NONNUMERICIDENTIFIER]})`) 80 81createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NUMERICIDENTIFIERLOOSE] 82}|${src[t.NONNUMERICIDENTIFIER]})`) 83 84// ## Pre-release Version 85// Hyphen, followed by one or more dot-separated pre-release version 86// identifiers. 87 88createToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER] 89}(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`) 90 91createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE] 92}(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`) 93 94// ## Build Metadata Identifier 95// Any combination of digits, letters, or hyphens. 96 97createToken('BUILDIDENTIFIER', `${LETTERDASHNUMBER}+`) 98 99// ## Build Metadata 100// Plus sign, followed by one or more period-separated build metadata 101// identifiers. 102 103createToken('BUILD', `(?:\\+(${src[t.BUILDIDENTIFIER] 104}(?:\\.${src[t.BUILDIDENTIFIER]})*))`) 105 106// ## Full Version String 107// A main version, followed optionally by a pre-release version and 108// build metadata. 109 110// Note that the only major, minor, patch, and pre-release sections of 111// the version string are capturing groups. The build metadata is not a 112// capturing group, because it should not ever be used in version 113// comparison. 114 115createToken('FULLPLAIN', `v?${src[t.MAINVERSION] 116}${src[t.PRERELEASE]}?${ 117 src[t.BUILD]}?`) 118 119createToken('FULL', `^${src[t.FULLPLAIN]}$`) 120 121// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. 122// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty 123// common in the npm registry. 124createToken('LOOSEPLAIN', `[v=\\s]*${src[t.MAINVERSIONLOOSE] 125}${src[t.PRERELEASELOOSE]}?${ 126 src[t.BUILD]}?`) 127 128createToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`) 129 130createToken('GTLT', '((?:<|>)?=?)') 131 132// Something like "2.*" or "1.2.x". 133// Note that "x.x" is a valid xRange identifer, meaning "any version" 134// Only the first item is strictly required. 135createToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`) 136createToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`) 137 138createToken('XRANGEPLAIN', `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})` + 139 `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + 140 `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + 141 `(?:${src[t.PRERELEASE]})?${ 142 src[t.BUILD]}?` + 143 `)?)?`) 144 145createToken('XRANGEPLAINLOOSE', `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` + 146 `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + 147 `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + 148 `(?:${src[t.PRERELEASELOOSE]})?${ 149 src[t.BUILD]}?` + 150 `)?)?`) 151 152createToken('XRANGE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`) 153createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`) 154 155// Coercion. 156// Extract anything that could conceivably be a part of a valid semver 157createToken('COERCE', `${'(^|[^\\d])' + 158 '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` + 159 `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + 160 `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + 161 `(?:$|[^\\d])`) 162createToken('COERCERTL', src[t.COERCE], true) 163 164// Tilde ranges. 165// Meaning is "reasonably at or greater than" 166createToken('LONETILDE', '(?:~>?)') 167 168createToken('TILDETRIM', `(\\s*)${src[t.LONETILDE]}\\s+`, true) 169exports.tildeTrimReplace = '$1~' 170 171createToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`) 172createToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`) 173 174// Caret ranges. 175// Meaning is "at least and backwards compatible with" 176createToken('LONECARET', '(?:\\^)') 177 178createToken('CARETTRIM', `(\\s*)${src[t.LONECARET]}\\s+`, true) 179exports.caretTrimReplace = '$1^' 180 181createToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`) 182createToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`) 183 184// A simple gt/lt/eq thing, or just "" to indicate "any version" 185createToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`) 186createToken('COMPARATOR', `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`) 187 188// An expression to strip any whitespace between the gtlt and the thing 189// it modifies, so that `> 1.2.3` ==> `>1.2.3` 190createToken('COMPARATORTRIM', `(\\s*)${src[t.GTLT] 191}\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true) 192exports.comparatorTrimReplace = '$1$2$3' 193 194// Something like `1.2.3 - 1.2.4` 195// Note that these all use the loose form, because they'll be 196// checked against either the strict or loose comparator form 197// later. 198createToken('HYPHENRANGE', `^\\s*(${src[t.XRANGEPLAIN]})` + 199 `\\s+-\\s+` + 200 `(${src[t.XRANGEPLAIN]})` + 201 `\\s*$`) 202 203createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` + 204 `\\s+-\\s+` + 205 `(${src[t.XRANGEPLAINLOOSE]})` + 206 `\\s*$`) 207 208// Star ranges basically just allow anything at all. 209createToken('STAR', '(<|>)?=?\\s*\\*') 210// >=0.0.0 is like a star 211createToken('GTE0', '^\\s*>=\\s*0\\.0\\.0\\s*$') 212createToken('GTE0PRE', '^\\s*>=\\s*0\\.0\\.0-0\\s*$') 213