Skip to content

Commit

Permalink
Update files in lib/
Browse files Browse the repository at this point in the history
Just keeping it in sync after slevithan/xregexp#182 was merged.
  • Loading branch information
speecyy committed Apr 25, 2017
1 parent b40d571 commit 2d61f4b
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 40 deletions.
41 changes: 41 additions & 0 deletions lib/addons/build.js
Expand Up @@ -58,6 +58,47 @@ module.exports = function (XRegExp) {
XRegExp(value, flags);
}

function interpolate(substitution) {
return substitution instanceof RegExp ? substitution : XRegExp.escape(substitution);
}

function reduceToSubpatternsObject(subpatterns, interpolated, subpatternIndex) {
subpatterns['subpattern' + subpatternIndex] = interpolated;
return subpatterns;
}

function embedSubpatternAfter(raw, subpatternIndex, rawLiterals) {
var hasSubpattern = subpatternIndex < rawLiterals.length - 1;
return raw + (hasSubpattern ? '{{subpattern' + subpatternIndex + '}}' : '');
}

/**
* Provides a tag function for building regexes using template literals [1]. See GitHub issue
* 103 for discussion [2].
*
* [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals
* [2]: https://github.com/slevithan/xregexp/issues/103
* @example
*
* var h12 = /1[0-2]|0?[1-9]/;
* var h24 = /2[0-3]|[01][0-9]/;
* var hours = XRegExp.tag('x')`${h12} : | ${h24}`
* var minutes = /^[0-5][0-9]$/;
* // Note that explicitly naming the 'minutes' group is required for it to appear in the
* // `XRegExp.exec()` return value object.
* var time = XRegExp.tag('x')`^ ${hours} (?<minutes>${minutes}) $`
* time.test('10:59'); // -> true
* XRegExp.exec('10:59', time).minutes; // -> '59'
*/
XRegExp.tag = function (flags) {
return function (literals /*, ...substitutions */) {
var substitutions = [].slice.call(arguments, 1);
var subpatterns = substitutions.map(interpolate).reduce(reduceToSubpatternsObject, {});
var pattern = literals.raw.map(embedSubpatternAfter).join('');
return XRegExp.build(pattern, subpatterns, flags);
};
};

/**
* Builds regexes using named subpatterns, for readability and pattern reuse. Backreferences in
* the outer pattern and provided subpatterns are automatically renumbered to work correctly.
Expand Down
86 changes: 46 additions & 40 deletions lib/xregexp.js
Expand Up @@ -52,7 +52,7 @@ var nativeTokens = {
'class': /\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\dA-Fa-f]{2}|u(?:[\dA-Fa-f]{4}|{[\dA-Fa-f]+})|c[A-Za-z]|[\s\S])|[\s\S]/
};
// Any backreference or dollar-prefixed character in replacement strings
var replacementToken = /\$(?:{([\w$]+)}|(\d\d?|[\s\S]))/g;
var replacementToken = /\$(?:{([\w$]+)}|<([\w$]+)>|(\d\d?|[\s\S]))/g;
// Check for correct `exec` handling of nonparticipating capturing groups
var correctExecNpcg = nativ.exec.call(/()??/, '')[1] === undefined;
// Check for ES6 `flags` prop support
Expand Down Expand Up @@ -243,7 +243,7 @@ function getContextualTokenSeparator(match, scope, flags) {
// No need to separate tokens if at the beginning or end of a group
match.input.charAt(match.index - 1) === '(' || match.input.charAt(match.index + match[0].length) === ')' ||
// Avoid separating tokens when the following token is a quantifier
isPatternNext(match.input, match.index + match[0].length, flags, '[?*+]|{\\d+(?:,\\d*)?}')) {
isQuantifierNext(match.input, match.index + match[0].length, flags)) {
return '';
}
// Keep tokens separated. This avoids e.g. inadvertedly changing `\1 1` or `\1(?#)1` to `\11`.
Expand Down Expand Up @@ -311,25 +311,23 @@ function indexOf(array, value) {
}

/**
* Checks whether the next nonignorable token after the specified position matches the
* `needlePattern`
* Checks whether the next nonignorable token after the specified position is a quantifier.
*
* @private
* @param {String} pattern Pattern to search within.
* @param {Number} pos Index in `pattern` to search at.
* @param {String} flags Flags used by the pattern.
* @param {String} needlePattern Pattern to match the next token against.
* @returns {Boolean} Whether the next nonignorable token matches `needlePattern`
* @returns {Boolean} Whether the next nonignorable token is a quantifier.
*/
function isPatternNext(pattern, pos, flags, needlePattern) {
function isQuantifierNext(pattern, pos, flags) {
var inlineCommentPattern = '\\(\\?#[^)]*\\)';
var lineCommentPattern = '#[^#\\n]*';
var patternsToIgnore = flags.indexOf('x') > -1 ?
var quantifierPattern = '[?*+]|{\\d+(?:,\\d*)?}';
return nativ.test.call(flags.indexOf('x') > -1 ?
// Ignore any leading whitespace, line comments, and inline comments
['\\s', lineCommentPattern, inlineCommentPattern] :
/^(?:\s|#[^#\n]*|\(\?#[^)]*\))*(?:[?*+]|{\d+(?:,\d*)?})/ :
// Ignore any leading inline comments
[inlineCommentPattern];
return nativ.test.call(new RegExp('^(?:' + patternsToIgnore.join('|') + ')*(?:' + needlePattern + ')'), pattern.slice(pos));
/^(?:\(\?#[^)]*\))*(?:[?*+]|{\d+(?:,\d*)?})/, pattern.slice(pos));
}

/**
Expand Down Expand Up @@ -639,7 +637,7 @@ function XRegExp(pattern, flags) {
}

// Add `RegExp.prototype` to the prototype chain
XRegExp.prototype = new RegExp();
XRegExp.prototype = /(?:)/;

// ==--------------------------==
// Public properties
Expand Down Expand Up @@ -1116,9 +1114,9 @@ XRegExp.matchChain = function (str, chain) {
* Returns a new string with one or all matches of a pattern replaced. The pattern can be a string
* or regex, and the replacement can be a string or a function to be called for each match. To
* perform a global search and replace, use the optional `scope` argument or include flag g if using
* a regex. Replacement strings can use `${n}` for named and numbered backreferences. Replacement
* functions can use named backreferences via `arguments[0].name`. Also fixes browser bugs compared
* to the native `String.prototype.replace` and can be used reliably cross-browser.
* a regex. Replacement strings can use `${n}` or `$<n>` for named and numbered backreferences.
* Replacement functions can use named backreferences via `arguments[0].name`. Also fixes browser
* bugs compared to the native `String.prototype.replace` and can be used reliably cross-browser.
*
* @memberOf XRegExp
* @param {String} str String to search.
Expand All @@ -1131,7 +1129,7 @@ XRegExp.matchChain = function (str, chain) {
* - $' - Inserts the string that follows the matched substring (right context).
* - $n, $nn - Where n/nn are digits referencing an existent capturing group, inserts
* backreference n/nn.
* - ${n} - Where n is a name or any number of digits that reference an existent capturing
* - ${n}, $<n> - Where n is a name or any number of digits that reference an existent capturing
* group, inserts backreference n.
* Replacement functions are invoked with three or more arguments:
* - The matched substring (corresponds to $& above). Named backreferences are accessible as
Expand All @@ -1149,6 +1147,9 @@ XRegExp.matchChain = function (str, chain) {
* XRegExp.replace('John Smith', name, '${last}, ${first}');
* // -> 'Smith, John'
*
* XRegExp.replace('John Smith', name, '$<last>, $<first>');
* // -> 'Smith, John'
*
* // Regex search, using named backreferences in replacement function
* XRegExp.replace('John Smith', name, function(match) {
* return match.last + ', ' + match.first;
Expand Down Expand Up @@ -1196,7 +1197,8 @@ XRegExp.replace = function (str, search, replacement, scope) {
* array of replacement details. Later replacements operate on the output of earlier replacements.
* Replacement details are accepted as an array with a regex or string to search for, the
* replacement string or function, and an optional scope of 'one' or 'all'. Uses the XRegExp
* replacement text syntax, which supports named backreference properties via `${name}`.
* replacement text syntax, which supports named backreference properties via `${name}` or
* `$<name>`.
*
* @memberOf XRegExp
* @param {String} str String to search.
Expand Down Expand Up @@ -1499,13 +1501,13 @@ fixed.match = function (regex) {
};

/**
* Adds support for `${n}` tokens for named and numbered backreferences in replacement text, and
* provides named backreferences to replacement functions as `arguments[0].name`. Also fixes browser
* bugs in replacement text syntax when performing a replacement using a nonregex search value, and
* the value of a replacement regex's `lastIndex` property during replacement iterations and upon
* completion. Calling `XRegExp.install('natives')` uses this to override the native method. Note
* that this doesn't support SpiderMonkey's proprietary third (`flags`) argument. Use via
* `XRegExp.replace` without overriding natives.
* Adds support for `${n}` (or `$<n>`) tokens for named and numbered backreferences in replacement
* text, and provides named backreferences to replacement functions as `arguments[0].name`. Also
* fixes browser bugs in replacement text syntax when performing a replacement using a nonregex
* search value, and the value of a replacement regex's `lastIndex` property during replacement
* iterations and upon completion. Calling `XRegExp.install('natives')` uses this to override the
* native method. Note that this doesn't support SpiderMonkey's proprietary third (`flags`)
* argument. Use via `XRegExp.replace` without overriding natives.
*
* @memberOf String
* @param {RegExp|String} search Search pattern to be replaced.
Expand Down Expand Up @@ -1560,11 +1562,14 @@ fixed.replace = function (search, replacement) {
result = nativ.replace.call(this == null ? this : String(this), search, function () {
// Keep this function's `arguments` available through closure
var args = arguments;
return nativ.replace.call(String(replacement), replacementToken, function ($0, $1, $2) {
return nativ.replace.call(String(replacement), replacementToken, replacer);

function replacer($0, bracketed, angled, dollarToken) {
bracketed = bracketed || angled;
var n;
// Named or numbered backreference with curly braces
if ($1) {
// XRegExp behavior for `${n}`:
if (bracketed) {
// XRegExp behavior for `${n}` or `$<n>`:
// 1. Backreference to numbered capture, if `n` is an integer. Use `0` for the
// entire match. Any number of leading zeros may be used.
// 2. Backreference to named capture `n`, if it exists and is not an integer
Expand All @@ -1573,40 +1578,41 @@ fixed.replace = function (search, replacement) {
// integer as the name.
// 3. If the name or number does not refer to an existing capturing group, it's
// an error.
n = +$1; // Type-convert; drop leading zeros
n = +bracketed; // Type-convert; drop leading zeros
if (n <= args.length - 3) {
return args[n] || '';
}
// Groups with the same name is an error, else would need `lastIndexOf`
n = captureNames ? indexOf(captureNames, $1) : -1;
n = captureNames ? indexOf(captureNames, bracketed) : -1;
if (n < 0) {
throw new SyntaxError('Backreference to undefined group ' + $0);
}
return args[n + 1] || '';
}
// Else, special variable or numbered backreference without curly braces
if ($2 === '$') {
if (dollarToken === '$') {
// $$
return '$';
}
if ($2 === '&' || +$2 === 0) {
if (dollarToken === '&' || +dollarToken === 0) {
// $&, $0 (not followed by 1-9), $00
return args[0];
}
if ($2 === '`') {
if (dollarToken === '`') {
// $` (left context)
return args[args.length - 1].slice(0, args[args.length - 2]);
}
if ($2 === "'") {
if (dollarToken === "'") {
// $' (right context)
return args[args.length - 1].slice(args[args.length - 2] + args[0].length);
}
// Else, numbered backreference without curly braces
$2 = +$2; // Type-convert; drop leading zero
dollarToken = +dollarToken; // Type-convert; drop leading zero
// XRegExp behavior for `$n` and `$nn`:
// - Backrefs end after 1 or 2 digits. Use `${..}` for more digits.
// - Backrefs end after 1 or 2 digits. Use `${..}` or `$<..>` for more digits.
// - `$1` is an error if no capturing groups.
// - `$10` is an error if less than 10 capturing groups. Use `${1}0` instead.
// - `$10` is an error if less than 10 capturing groups. Use `${1}0` or `$<1>0`
// instead.
// - `$01` is `$1` if at least one capturing group, else it's an error.
// - `$0` (not followed by 1-9) and `$00` are the entire match.
// Native behavior, for comparison:
Expand All @@ -1615,15 +1621,15 @@ fixed.replace = function (search, replacement) {
// - `$10` is `$1` followed by a literal `0` if less than 10 capturing groups.
// - `$01` is `$1` if at least one capturing group, else it's a literal `$01`.
// - `$0` is a literal `$0`.
if (!isNaN($2)) {
if ($2 > args.length - 3) {
if (!isNaN(dollarToken)) {
if (dollarToken > args.length - 3) {
throw new SyntaxError('Backreference to undefined group ' + $0);
}
return args[$2] || '';
return args[dollarToken] || '';
}
// `$` followed by an unsupported char is an error, unlike native JS
throw new SyntaxError('Invalid token ' + $0);
});
}
});
}

Expand Down

0 comments on commit 2d61f4b

Please sign in to comment.