re.js 8.0 KB

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