// 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);