• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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