// Copyright 2024 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --js-regexp-duplicate-named-groups
// Duplicate names are only valid in alterations. Test that previous behaviour
// is still correct (early syntax error).
assertEarlyError('/(?.)(?.)/');
assertEarlyError('/((?.)(?.))/');
assertEarlyError('/(?.)((?.))/');
assertEarlyError('/((?.))(?.)/');
assertEarlyError('/(?.)((?.)|(?.))/');
assertEarlyError('/(?.)((?.)|(?.))/');
assertEarlyError('/((?.)|(?.))(?.)/');
assertEarlyError('/((?.)|(?.))(?.)/');
assertEarlyError('/((?.)|((?.)|(?.))(?.)/');
assertEarlyError('/((?.)|((?.)|(?.))(?.)/');
assertEarlyError(
'/x(?.)((((?.)|(?.))|(?.)|(?.))|(?.))|(?.)y/');
assertEarlyError(
'/x((?.)(((?.)|(?.))|(?.)|(?.))|(?.))|(?.)y/');
assertEarlyError(
'/x(((?.)((?.)|(?.))|(?.)|(?.))|(?.))|(?.)y/');
assertEarlyError(
'/x((((?.)(?.)|(?.))|(?.)|(?.))|(?.))|(?.)y/');
assertEarlyError(
'/x(?.)|((?.)|(?.)|((?.)|((?.)|(?.)(?.))))y/');
assertEarlyError(
'/x(?.)|((?.)|(?.)|((?.)|((?.)|(?.))(?.)|((?.)|(?.)|((?.)|((?.)|(?.)))(?.))y/');
assertEarlyError(
'/x(?.)|((?.)|(?.)|((?.)|((?.)|(?.))))(?.)y/');
// Cases with valid duplicate names.
new RegExp('(?.)|(?.)');
new RegExp('(?.)|(?.)|(?.)|(?.)');
new RegExp('(?.)|(.)|(?.)');
new RegExp('(?.(?.))|(?.)');
new RegExp('(?.(?.))|(?.)|(?.)');
new RegExp('((?.)|((?.)|(?.))(?.))');
new RegExp('((?.)|(?.))|(?.)(?.)');
new RegExp('((?.)|(?.))|(?.)(?.)');
new RegExp('(?:(?.)|(?.))|(?.)');
new RegExp('(?.)|(?:(?.)|(?.))');
new RegExp('x((((?.)|(?.))|(?.)|(?.))|(?.))|(?.)y');
new RegExp('x(?.)|((?.)|(?.)|((?.)|((?.)|(?.))))y');
// Test different functions with duplicate names in alteration.
assertEquals(
['xxyy', undefined, 'y'], /(?:(?:(?x)|(?y))\k){2}/.exec('xxyy'));
assertEquals(
['zzyyxx', 'x', undefined, undefined, undefined, undefined],
/(?:(?:(?x)|(?y)|(a)|(?b)|(?z))\k){3}/.exec('xzzyyxxy'));
assertEquals(
['xxyy', undefined, 'y'], 'xxyy'.match(/(?:(?:(?x)|(?y))\k){2}/));
assertEquals(
['zzyyxx', 'x', undefined, undefined, undefined, undefined],
'xzzyyxxy'.match(/(?:(?:(?x)|(?y)|(a)|(?b)|(?z))\k){3}/));
assertTrue(/(?:(?:(?x)|(?y))\k){2}/.test('xxyy'));
assertTrue(
/(?:(?:(?x)|(?y)|(a)|(?b)|(?z))\k){3}/.test('xzzyyxxy'));
assertFalse(/(?:(?:(?x)|(?y))\k){2}/.test('xyxy'));
assertFalse(
/(?:(?:(?x)|(?y)|(a)|(?b)|(?z))\k){3}/.test('xyzxyzxyz'));
assertEquals(3, 'abcxyz'.search(/(?x)|(?y)/));
assertEquals(3, 'abcxyz'.search(/(?y)|(?x)/));
assertEquals(1, 'aybcxyz'.search(/(?x)|(?y)/));
assertEquals(1, 'aybcxyz'.search(/(?y)|(?x)/));
assertEquals('2xyy', 'xxyy'.replace(/(?:(?:(?x)|(?y))\k)/, '2$'));
assertEquals(
'x2zyyxxy',
'xzzyyxxy'.replace(
/(?:(?:(?x)|(?y)|(a)|(?b)|(?z))\k)/, '2$'));
assertEquals(
'2x(x,)yy', 'xxyy'.replace(/(?:(?:(?x)|(?y))\k)/, '2$($1,$2)'));
assertEquals(
'x2z(,,,,z)yyxxy',
'xzzyyxxy'.replace(
/(?:(?:(?x)|(?y)|(a)|(?b)|(?z))\k)/,
'2$($1,$2,$3,$4,$5)'));
assertEquals('2x2y', 'xxyy'.replace(/(?:(?:(?x)|(?y))\k)/g, '2$'));
assertEquals(
'x2z2y2xy',
'xzzyyxxy'.replace(
/(?:(?:(?x)|(?y)|(a)|(?b)|(?z))\k)/g, '2$'));
assertEquals(
'2x&2y', 'xx&yy'.replaceAll(/(?:(?:(?x)|(?y))\k)/g, '2$'));
assertEquals(
'x&2z&2y&2x&y',
'x&zz&yy&xx&y'.replaceAll(
/(?:(?:(?x)|(?y)|(a)|(?b)|(?z))\k)/g, '2$'));
assertEquals(
['', 'x', undefined, '', undefined, 'y', ''],
'xxyy'.split(/(?:(?:(?x)|(?y))\k)/));
assertEquals(
[
'x', undefined, undefined, undefined, undefined, 'z', '', undefined, 'y',
undefined, undefined, undefined, '', 'x', undefined, undefined, undefined,
undefined, 'y'
],
'xzzyyxxy'.split(/(?:(?:(?x)|(?y)|(a)|(?b)|(?z))\k)/));
function assertMatchAll(matches, expected) {
let i = 0;
for (match of matches) {
assertEquals(expected[i], match);
i++;
}
}
assertMatchAll(
'xyx'.matchAll(/(?x)|(?y)/g),
[['x', 'x', undefined], ['y', undefined, 'y'], ['x', 'x', undefined]]);
assertMatchAll(
'xyx'.matchAll(/(?y)|(?x)/g),
[['x', undefined, 'x'], ['y', 'y', undefined], ['x', undefined, 'x']]);
// Property enumeration order of the groups object is based on source order, not
// match order.
assertEquals(
['b', 'a'], Object.keys(/(?x)(?x)|(?y)(?y)/.exec('xx').groups));
assertEquals(
['b', 'a'], Object.keys(/(?x)(?x)|(?y)(?y)/.exec('yy').groups));
// Test match indices with duplicate groups.
assertEquals([2, 3], 'abxy'.match(/(?x)|(?y)/d).indices.groups.a);
assertEquals([2, 3], 'bayx'.match(/(?x)|(?y)/d).indices.groups.a);
// Replace with function as replacement.
function testReplaceWithCallback(global) {
let replace_callback_cnt = 0;
function checkReplace(match, c1, c2, offset, string, groups) {
replace_callback_cnt++;
if (offset == 0) {
// First callback for match 'xx'.
assertEquals('xx', match);
assertEquals('x', c1);
assertEquals(undefined, c2);
assertEquals('xxyy', string);
} else {
// Second callback for match 'yy'.
assertTrue(global);
assertEquals(2, offset);
assertEquals('yy', match);
assertEquals(undefined, c1);
assertEquals('y', c2);
assertEquals('xxyy', string);
}
return '2' + groups.a;
}
let re = new RegExp('(?:(?:(?x)|(?y))\\k)', global ? 'g' : '');
let expected = global ? '2x2y' : '2xyy';
assertEquals(expected, 'xxyy'.replace(re, checkReplace));
assertEquals(global ? 2 : 1, replace_callback_cnt);
}
testReplaceWithCallback(false);
testReplaceWithCallback(true);