You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

241 lines
10 KiB

  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. /*!
  6. * XRegExp.build 4.0.0
  7. * <xregexp.com>
  8. * Steven Levithan (c) 2012-2017 MIT License
  9. */
  10. exports.default = function (XRegExp) {
  11. var REGEX_DATA = 'xregexp';
  12. var subParts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*\]/g;
  13. var parts = XRegExp.union([/\({{([\w$]+)}}\)|{{([\w$]+)}}/, subParts], 'g', {
  14. conjunction: 'or'
  15. });
  16. /**
  17. * Strips a leading `^` and trailing unescaped `$`, if both are present.
  18. *
  19. * @private
  20. * @param {String} pattern Pattern to process.
  21. * @returns {String} Pattern with edge anchors removed.
  22. */
  23. function deanchor(pattern) {
  24. // Allow any number of empty noncapturing groups before/after anchors, because regexes
  25. // built/generated by XRegExp sometimes include them
  26. var leadingAnchor = /^(?:\(\?:\))*\^/;
  27. var trailingAnchor = /\$(?:\(\?:\))*$/;
  28. if (leadingAnchor.test(pattern) && trailingAnchor.test(pattern) &&
  29. // Ensure that the trailing `$` isn't escaped
  30. trailingAnchor.test(pattern.replace(/\\[\s\S]/g, ''))) {
  31. return pattern.replace(leadingAnchor, '').replace(trailingAnchor, '');
  32. }
  33. return pattern;
  34. }
  35. /**
  36. * Converts the provided value to an XRegExp. Native RegExp flags are not preserved.
  37. *
  38. * @private
  39. * @param {String|RegExp} value Value to convert.
  40. * @param {Boolean} [addFlagX] Whether to apply the `x` flag in cases when `value` is not
  41. * already a regex generated by XRegExp
  42. * @returns {RegExp} XRegExp object with XRegExp syntax applied.
  43. */
  44. function asXRegExp(value, addFlagX) {
  45. var flags = addFlagX ? 'x' : '';
  46. return XRegExp.isRegExp(value) ? value[REGEX_DATA] && value[REGEX_DATA].captureNames ?
  47. // Don't recompile, to preserve capture names
  48. value :
  49. // Recompile as XRegExp
  50. XRegExp(value.source, flags) :
  51. // Compile string as XRegExp
  52. XRegExp(value, flags);
  53. }
  54. function interpolate(substitution) {
  55. return substitution instanceof RegExp ? substitution : XRegExp.escape(substitution);
  56. }
  57. function reduceToSubpatternsObject(subpatterns, interpolated, subpatternIndex) {
  58. subpatterns['subpattern' + subpatternIndex] = interpolated;
  59. return subpatterns;
  60. }
  61. function embedSubpatternAfter(raw, subpatternIndex, rawLiterals) {
  62. var hasSubpattern = subpatternIndex < rawLiterals.length - 1;
  63. return raw + (hasSubpattern ? '{{subpattern' + subpatternIndex + '}}' : '');
  64. }
  65. /**
  66. * Provides tagged template literals that create regexes with XRegExp syntax and flags. The
  67. * provided pattern is handled as a raw string, so backslashes don't need to be escaped.
  68. *
  69. * Interpolation of strings and regexes shares the features of `XRegExp.build`. Interpolated
  70. * patterns are treated as atomic units when quantified, interpolated strings have their special
  71. * characters escaped, a leading `^` and trailing unescaped `$` are stripped from interpolated
  72. * regexes if both are present, and any backreferences within an interpolated regex are
  73. * rewritten to work within the overall pattern.
  74. *
  75. * @memberOf XRegExp
  76. * @param {String} [flags] Any combination of XRegExp flags.
  77. * @returns {Function} Handler for template literals that construct regexes with XRegExp syntax.
  78. * @example
  79. *
  80. * const h12 = /1[0-2]|0?[1-9]/;
  81. * const h24 = /2[0-3]|[01][0-9]/;
  82. * const hours = XRegExp.tag('x')`${h12} : | ${h24}`;
  83. * const minutes = /^[0-5][0-9]$/;
  84. * // Note that explicitly naming the 'minutes' group is required for named backreferences
  85. * const time = XRegExp.tag('x')`^ ${hours} (?<minutes>${minutes}) $`;
  86. * time.test('10:59'); // -> true
  87. * XRegExp.exec('10:59', time).minutes; // -> '59'
  88. */
  89. XRegExp.tag = function (flags) {
  90. return function (literals) {
  91. for (var _len = arguments.length, substitutions = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  92. substitutions[_key - 1] = arguments[_key];
  93. }
  94. var subpatterns = substitutions.map(interpolate).reduce(reduceToSubpatternsObject, {});
  95. var pattern = literals.raw.map(embedSubpatternAfter).join('');
  96. return XRegExp.build(pattern, subpatterns, flags);
  97. };
  98. };
  99. /**
  100. * Builds regexes using named subpatterns, for readability and pattern reuse. Backreferences in
  101. * the outer pattern and provided subpatterns are automatically renumbered to work correctly.
  102. * Native flags used by provided subpatterns are ignored in favor of the `flags` argument.
  103. *
  104. * @memberOf XRegExp
  105. * @param {String} pattern XRegExp pattern using `{{name}}` for embedded subpatterns. Allows
  106. * `({{name}})` as shorthand for `(?<name>{{name}})`. Patterns cannot be embedded within
  107. * character classes.
  108. * @param {Object} subs Lookup object for named subpatterns. Values can be strings or regexes. A
  109. * leading `^` and trailing unescaped `$` are stripped from subpatterns, if both are present.
  110. * @param {String} [flags] Any combination of XRegExp flags.
  111. * @returns {RegExp} Regex with interpolated subpatterns.
  112. * @example
  113. *
  114. * const time = XRegExp.build('(?x)^ {{hours}} ({{minutes}}) $', {
  115. * hours: XRegExp.build('{{h12}} : | {{h24}}', {
  116. * h12: /1[0-2]|0?[1-9]/,
  117. * h24: /2[0-3]|[01][0-9]/
  118. * }, 'x'),
  119. * minutes: /^[0-5][0-9]$/
  120. * });
  121. * time.test('10:59'); // -> true
  122. * XRegExp.exec('10:59', time).minutes; // -> '59'
  123. */
  124. XRegExp.build = function (pattern, subs, flags) {
  125. flags = flags || '';
  126. // Used with `asXRegExp` calls for `pattern` and subpatterns in `subs`, to work around how
  127. // some browsers convert `RegExp('\n')` to a regex that contains the literal characters `\`
  128. // and `n`. See more details at <https://github.com/slevithan/xregexp/pull/163>.
  129. var addFlagX = flags.indexOf('x') !== -1;
  130. var inlineFlags = /^\(\?([\w$]+)\)/.exec(pattern);
  131. // Add flags within a leading mode modifier to the overall pattern's flags
  132. if (inlineFlags) {
  133. flags = XRegExp._clipDuplicates(flags + inlineFlags[1]);
  134. }
  135. var data = {};
  136. for (var p in subs) {
  137. if (subs.hasOwnProperty(p)) {
  138. // Passing to XRegExp enables extended syntax and ensures independent validity,
  139. // lest an unescaped `(`, `)`, `[`, or trailing `\` breaks the `(?:)` wrapper. For
  140. // subpatterns provided as native regexes, it dies on octals and adds the property
  141. // used to hold extended regex instance data, for simplicity.
  142. var sub = asXRegExp(subs[p], addFlagX);
  143. data[p] = {
  144. // Deanchoring allows embedding independently useful anchored regexes. If you
  145. // really need to keep your anchors, double them (i.e., `^^...$$`).
  146. pattern: deanchor(sub.source),
  147. names: sub[REGEX_DATA].captureNames || []
  148. };
  149. }
  150. }
  151. // Passing to XRegExp dies on octals and ensures the outer pattern is independently valid;
  152. // helps keep this simple. Named captures will be put back.
  153. var patternAsRegex = asXRegExp(pattern, addFlagX);
  154. // 'Caps' is short for 'captures'
  155. var numCaps = 0;
  156. var numPriorCaps = void 0;
  157. var numOuterCaps = 0;
  158. var outerCapsMap = [0];
  159. var outerCapNames = patternAsRegex[REGEX_DATA].captureNames || [];
  160. var output = patternAsRegex.source.replace(parts, function ($0, $1, $2, $3, $4) {
  161. var subName = $1 || $2;
  162. var capName = void 0;
  163. var intro = void 0;
  164. var localCapIndex = void 0;
  165. // Named subpattern
  166. if (subName) {
  167. if (!data.hasOwnProperty(subName)) {
  168. throw new ReferenceError('Undefined property ' + $0);
  169. }
  170. // Named subpattern was wrapped in a capturing group
  171. if ($1) {
  172. capName = outerCapNames[numOuterCaps];
  173. outerCapsMap[++numOuterCaps] = ++numCaps;
  174. // If it's a named group, preserve the name. Otherwise, use the subpattern name
  175. // as the capture name
  176. intro = '(?<' + (capName || subName) + '>';
  177. } else {
  178. intro = '(?:';
  179. }
  180. numPriorCaps = numCaps;
  181. var rewrittenSubpattern = data[subName].pattern.replace(subParts, function (match, paren, backref) {
  182. // Capturing group
  183. if (paren) {
  184. capName = data[subName].names[numCaps - numPriorCaps];
  185. ++numCaps;
  186. // If the current capture has a name, preserve the name
  187. if (capName) {
  188. return '(?<' + capName + '>';
  189. }
  190. // Backreference
  191. } else if (backref) {
  192. localCapIndex = +backref - 1;
  193. // Rewrite the backreference
  194. return data[subName].names[localCapIndex] ?
  195. // Need to preserve the backreference name in case using flag `n`
  196. '\\k<' + data[subName].names[localCapIndex] + '>' : '\\' + (+backref + numPriorCaps);
  197. }
  198. return match;
  199. });
  200. return '' + intro + rewrittenSubpattern + ')';
  201. }
  202. // Capturing group
  203. if ($3) {
  204. capName = outerCapNames[numOuterCaps];
  205. outerCapsMap[++numOuterCaps] = ++numCaps;
  206. // If the current capture has a name, preserve the name
  207. if (capName) {
  208. return '(?<' + capName + '>';
  209. }
  210. // Backreference
  211. } else if ($4) {
  212. localCapIndex = +$4 - 1;
  213. // Rewrite the backreference
  214. return outerCapNames[localCapIndex] ?
  215. // Need to preserve the backreference name in case using flag `n`
  216. '\\k<' + outerCapNames[localCapIndex] + '>' : '\\' + outerCapsMap[+$4];
  217. }
  218. return $0;
  219. });
  220. return XRegExp(output, flags);
  221. };
  222. };
  223. module.exports = exports['default'];