Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/xregexp.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const 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
const replacementToken = /\$(?:{([\w$]+)}|<([\w$]+)>|(\d\d?|[\s\S]))/g;
const replacementToken = /\$(?:\{([\p{ID_Start}$_][\p{ID_Continue}$_\u200C\u200D]*)\}|<([\p{ID_Start}$_][\p{ID_Continue}$_\u200C\u200D]*)>|(\d\d?|[\s\S]))/gu;
// Check for correct `exec` handling of nonparticipating capturing groups
const correctExecNpcg = nativ.exec.call(/()??/, '')[1] === undefined;
// Check for ES6 `flags` prop support
Expand Down Expand Up @@ -1792,7 +1792,7 @@ XRegExp.addToken(
* and $ only. Also allows numbered backreferences as `\k<n>`.
*/
XRegExp.addToken(
/\\k<([\w$]+)>/,
/\\k<([\p{ID_Start}$_][\p{ID_Continue}$_\u200C\u200D]*)>/u,
function(match) {
// Groups with the same name is an error, else would need `lastIndexOf`
const index = isNaN(match[1]) ? (this.captureNames.indexOf(match[1]) + 1) : +match[1];
Expand Down Expand Up @@ -1844,7 +1844,7 @@ XRegExp.addToken(
* Python-style named capture as octals.
*/
XRegExp.addToken(
/\(\?P?<([\w$]+)>/,
/\(\?P?<([\p{ID_Start}$_][\p{ID_Continue}$_\u200C\u200D]*)>/u,
function(match) {
// Disallow bare integers as names because named backreferences are added to match arrays
// and therefore numeric properties may lead to incorrect lookups
Expand Down
80 changes: 2 additions & 78 deletions tests/spec/s-xregexp-methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -1251,84 +1251,8 @@ describe('XRegExp.replace()', function() {
expect(function() {XRegExp.replace('test', XRegExp('(?<test>t)', 'g'), ':$<x>:');}).toThrowError(SyntaxError);
});

});

describe('explicit numbered backreferences', function() {

it('should return the numbered backreference', function() {
expect(XRegExp.replace('test', /(.)./g, '${1}')).toBe('ts');
expect(XRegExp.replace('test', /(.)./g, '$<1>')).toBe('ts');

// Backreference to a nonparticipating capturing group
expect(XRegExp.replace('test', /t|(e)/g, '${1}')).toBe('es');
expect(XRegExp.replace('test', /t|(e)/g, '$<1>')).toBe('es');
});

it('should allow leading zeros', function() {
expect(XRegExp.replace('test', /(.)./g, '${01}')).toBe('ts');
expect(XRegExp.replace('test', /(.)./g, '$<01>')).toBe('ts');

expect(XRegExp.replace('test', /(.)./g, '${001}')).toBe('ts');
expect(XRegExp.replace('test', /(.)./g, '$<001>')).toBe('ts');
});

it('should return named backreferences by number', function() {
expect(XRegExp.replace('test', XRegExp('(?<name>.).', 'g'), '${1}')).toBe('ts');
expect(XRegExp.replace('test', XRegExp('(?<name>.).', 'g'), '$<1>')).toBe('ts');
});

it('should separate numbered backreferences from following literal digits', function() {
expect(XRegExp.replace('test', new RegExp('(.).', 'g'), '${1}0')).toBe('t0s0');
expect(XRegExp.replace('test', new RegExp('(.).', 'g'), '$<1>0')).toBe('t0s0');

expect(XRegExp.replace('test', new RegExp('(.).' + '()'.repeat(9), 'g'), '${1}0')).toBe('t0s0');
expect(XRegExp.replace('test', new RegExp('(.).' + '()'.repeat(9), 'g'), '$<1>0')).toBe('t0s0');
});

it('should throw an exception for backreferences to unknown group numbers', function() {
expect(function() {XRegExp.replace('test', /t/, '${1}');}).toThrowError(SyntaxError);
expect(function() {XRegExp.replace('test', /t/, '$<1>');}).toThrowError(SyntaxError);

expect(function() {XRegExp.replace('test', /(t)/, '${2}');}).toThrowError(SyntaxError);
expect(function() {XRegExp.replace('test', /(t)/, '$<2>');}).toThrowError(SyntaxError);
});

it('should allow ${0} to refer to the entire match', function() {
expect(XRegExp.replace('test', /../g, '${0}:')).toBe('te:st:');
expect(XRegExp.replace('test', /../g, '$<0>:')).toBe('te:st:');

expect(XRegExp.replace('test', /../g, '${00}:')).toBe('te:st:');
expect(XRegExp.replace('test', /../g, '$<00>:')).toBe('te:st:');

expect(XRegExp.replace('test', /../g, '${000}:')).toBe('te:st:');
expect(XRegExp.replace('test', /../g, '$<000>:')).toBe('te:st:');
});

it('should support backreferences 100 and greater, if the browser does natively', function() {
// IE < 9 doesn't allow backreferences greater than \99 *within* a regex, but
// XRegExp still allows backreferences to groups 100+ within replacement text
try {
// Regex with 1,000 capturing groups. This fails in Firefox 4-6 (but not v3.6
// or v7+) with `InternalError: regular expression too complex`
var lottaGroups = new RegExp([
'^(a)\\1', '()'.repeat(8),
'(b)\\10', '()'.repeat(89),
'(c)', '()'.repeat(899),
'(d)$'
].join(''));

expect(XRegExp.replace('aabbcd', lottaGroups, '${0} ${01} ${001} ${0001} ${1} ${10} ${100} ${1000}')).toBe('aabbcd a a a a b c d');
expect(XRegExp.replace('aabbcd', lottaGroups, '$<0> $<01> $<001> $<0001> $<1> $<10> $<100> $<1000>')).toBe('aabbcd a a a a b c d');
expect(XRegExp.replace('aabbcd', lottaGroups, '$<0> ${01} $<001> ${0001} $<1> ${10} $<100> ${1000}')).toBe('aabbcd a a a a b c d');
// For comparison...
expect(XRegExp.replace('aabbcd', lottaGroups, '$0 $01 $001 $0001 $1 $10 $100 $1000')).toBe('aabbcd a aabbcd1 aabbcd01 a b b0 b00');
} catch (err) {
// Keep the assertion count consistent cross-browser
expect(true).toBe(true);
expect(true).toBe(true);
expect(true).toBe(true);
expect(true).toBe(true);
}
it('should not allow leading digits', function() {
expect(function() {XRegExp.replace('test', /(.)./g, '${01}');}).toThrowError(SyntaxError);
});

});
Expand Down
62 changes: 10 additions & 52 deletions tests/spec/s-xregexp.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,27 +393,24 @@ describe('XRegExp()', function() {
// Named capture *functionality* is tested by the specs for named backreference syntax,
// XRegExp.exec, XRegExp.replace, etc.

it('should allow the characters A-Z, a-z, 0-9, $, and _ to be used in capture names', function() {
it('should allow the characters A-Z, a-z, 0-9, $, _, and RegExpIdentifierName characters to be used in capture names', function() {
expect(XRegExp('(?<Az>x)').test('x')).toBe(true);
expect(XRegExp('(?<_09>x)').test('x')).toBe(true);
expect(XRegExp('(?<$>x)').test('x')).toBe(true);
expect(function() {XRegExp('(?<naïve>)');}).not.toThrow();
expect(function() {XRegExp('(?<Русский>)');}).not.toThrow();
expect(function() {XRegExp('(?<日本語>)');}).not.toThrow();
});

it('should throw an exception if characters other than A-Z, a-z, 0-9, $, and _ are used in capture names', function() {
expect(function() {XRegExp('(?<?>)');}).toThrowError(SyntaxError);
expect(function() {XRegExp('(?<.>)');}).toThrowError(SyntaxError);
expect(function() {XRegExp('(?<<>)');}).toThrowError(SyntaxError);
expect(function() {XRegExp('(?<->)');}).toThrowError(SyntaxError);
// Native named capture uses different allowed chars that XRegExp should be updated to handle
//expect(function() {XRegExp('(?<naïve>)');}).toThrowError(SyntaxError);
//expect(function() {XRegExp('(?<Русский>)');}).toThrowError(SyntaxError);
//expect(function() {XRegExp('(?<日本語>)');}).toThrowError(SyntaxError);
});

it('should allow capture names to start with digits', function() {
expect(XRegExp('(?<0a>x)').test('x')).toBe(true);
expect(XRegExp('(?<1_1>x)').test('x')).toBe(true);
expect(XRegExp('(?<234$>x)').test('x')).toBe(true);
it('should not allow capture names to start with digits', function() {
expect(function() {XRegExp('(?<0a>x)');}).toThrowError(SyntaxError);
});

it('should throw an exception if bare integers are used as capture names', function() {
Expand Down Expand Up @@ -488,6 +485,10 @@ describe('XRegExp()', function() {
expect(function() {XRegExp('\\k<`>');}).toThrowError(SyntaxError);
});

it('should not allow leading digits', function() {
expect(function() {XRegExp('(.)\\k<01>');}).toThrowError(SyntaxError);
});

it('should separate backreferences from following literal digits', function() {
expect(XRegExp('(?<$1>A1)(2)(3)(4)(5)(6)(7)(8)(9)(B10)\\k<$1>0').test('A123456789B10A10')).toBe(true);
expect(XRegExp('(?<$1>A)\\k<$1>2').test('AA2')).toBe(true);
Expand All @@ -506,49 +507,6 @@ describe('XRegExp()', function() {

});

describe('explicit numbered backreferences', function() {

it('should match the numbered backreference', function() {
expect(XRegExp('(.)\\k<1>').test('aa')).toBe(true);
expect(XRegExp('(.)\\k<1>').test('ab')).toBe(false);
expect(XRegExp('(.)\\k<1>\\k<1>').test('aaa')).toBe(true);
});

it('should allow leading zeros', function() {
expect(XRegExp('(.)\\k<01>').test('aa')).toBe(true);
expect(XRegExp('(.)\\k<001>').test('aa')).toBe(true);
});

it('should match named backreferences by number', function() {
expect(XRegExp('(?<A>.)\\k<1>').test('aa')).toBe(true);
expect(XRegExp('(?<A>.)\\k<1>').test('ab')).toBe(false);
expect(XRegExp('(?<A>.)\\k<1>\\k<1>').test('aaa')).toBe(true);
});

it('should separate numbered backreferences from following literal digits', function() {
expect(XRegExp('(A1)(2)(3)(4)(5)(6)(7)(8)(9)(B10)\\k<1>0').test('A123456789B10A10')).toBe(true);
expect(XRegExp('(A)\\k<1>2').test('AA2')).toBe(true);
});

it('should throw an exception for backreferences to unknown groups', function() {
expect(function() {XRegExp('\\k<1>');}).toThrowError(SyntaxError);
expect(function() {XRegExp('()\\k<2>');}).toThrowError(SyntaxError);
});

it('should throw an exception for backreferences to capturing groups not opened to the left', function() {
expect(function() {XRegExp('\\k<1>()');}).toThrowError(SyntaxError);
expect(function() {XRegExp('()\\k<2>()');}).toThrowError(SyntaxError);
expect(function() {XRegExp('(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)\\k<11>(11)');}).toThrowError(SyntaxError);
expect(function() {XRegExp('(\\k<1>)');}).not.toThrow();
});

it('should not allow \\k<0> to refer to the entire match', function() {
expect(function() {XRegExp('\\k<0>');}).toThrowError(SyntaxError);
expect(function() {XRegExp('\\k<00>');}).toThrowError(SyntaxError);
});

});

describe('strict error handling', function() {

it('should throw an exception for octals except \\0 not followed by 0-9', function() {
Expand Down