// Copyright 2017 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: --harmony-regexp-named-captures --allow-natives-syntax // Malformed named captures. assertThrows("/(?<>a)/u", SyntaxError); // Empty name. assertThrows("/(?a)/u", SyntaxError); // Name starting with digits. assertThrows("/(?<:a>a)/u", SyntaxError); // Name starting with invalid char. assertThrows("/(?a)/u", SyntaxError); // Name containing with invalid char. assertThrows("/(?a)(?a)/u", SyntaxError); // Duplicate name. assertThrows("/(?a)(?b)(?a)/u", SyntaxError); // Duplicate name. assertThrows("/\\k/u", SyntaxError); // Invalid reference. assertThrows("/\\k.)\\k/u", SyntaxError); // Lone \k. assertThrows("/(?.)\\k.)\\k/u", SyntaxError); // Invalid reference. assertThrows("/(?a)\\k/u", SyntaxError); // Invalid reference. assertThrows("/(?a)\\k/u", SyntaxError); // Invalid reference. assertThrows("/\\k(?a)/u", SyntaxError); // Invalid reference. assertThrows("/(?\\a)/u", SyntaxError); // Identity escape in capture. // Behavior in non-unicode mode. assertThrows("/(?<>a)/", SyntaxError); assertThrows("/(?a)/", SyntaxError); assertThrows("/(?<:a>a)/", SyntaxError); assertThrows("/(?a)/", SyntaxError); assertThrows("/(?a)(?a)/", SyntaxError); assertThrows("/(?a)(?b)(?a)/", SyntaxError); assertTrue(/\k/.test("k")); assertTrue(/\k<4>/.test("k<4>")); assertTrue(/\k.)\\k/", SyntaxError); assertThrows("/(?.)\\k.)\\k/", SyntaxError); assertThrows("/(?a)\\k/", SyntaxError); assertThrows("/(?a)\\k/", SyntaxError); assertThrows("/\\k(?a)/", SyntaxError); assertThrows("/\\ka)/", SyntaxError); assertTrue(/(?\a)/.test("a")); assertEquals(["k"], "xxxkxxx".match(/\k/)); assertEquals(["kxxx".match(/\k.)(?.)(?.)\k\k\k/.exec("abccba").groups); // A couple of corner cases around '\k' as named back-references vs. identity // escapes. assertTrue(/\k(?<=>)a/.test("ka")); assertTrue(/\k(?a")); assertTrue(/\k(x)/.test("kx")); assertTrue(/\k(?x)/.test("x")); assertThrows("/\\k(?x)/", SyntaxError); assertThrows("/\\k.)/", SyntaxError); assertThrows("/\\k(?.)/", SyntaxError); // Basic named groups. assertEquals(["a", "a"], "bab".match(/(?a)/u)); assertEquals(["a", "a"], "bab".match(/(?a)/u)); assertEquals(["a", "a"], "bab".match(/(?<_>a)/u)); assertEquals(["a", "a"], "bab".match(/(?<$>a)/u)); assertEquals(["bab", "a"], "bab".match(/.(?<$>a)./u)); assertEquals(["bab", "a", "b"], "bab".match(/.(?a)(.)/u)); assertEquals(["bab", "a", "b"], "bab".match(/.(?a)(?.)/u)); assertEquals(["bab", "ab"], "bab".match(/.(?\w\w)/u)); assertEquals(["bab", "bab"], "bab".match(/(?\w\w\w)/u)); assertEquals(["bab", "ba", "b"], "bab".match(/(?\w\w)(?\w)/u)); assertEquals(["a", "a"], "bab".match(/(?a)/)); assertEquals(["a", "a"], "bab".match(/(?a)/)); assertEquals(["a", "a"], "bab".match(/(?<_>a)/)); assertEquals(["a", "a"], "bab".match(/(?<$>a)/)); assertEquals(["bab", "a"], "bab".match(/.(?<$>a)./)); assertEquals(["bab", "a", "b"], "bab".match(/.(?a)(.)/)); assertEquals(["bab", "a", "b"], "bab".match(/.(?a)(?.)/)); assertEquals(["bab", "ab"], "bab".match(/.(?\w\w)/)); assertEquals(["bab", "bab"], "bab".match(/(?\w\w\w)/)); assertEquals(["bab", "ba", "b"], "bab".match(/(?\w\w)(?\w)/)); assertEquals("bab".match(/(a)/u), "bab".match(/(?a)/u)); assertEquals("bab".match(/(a)/u), "bab".match(/(?a)/u)); assertEquals("bab".match(/(a)/u), "bab".match(/(?<_>a)/u)); assertEquals("bab".match(/(a)/u), "bab".match(/(?<$>a)/u)); assertEquals("bab".match(/.(a)./u), "bab".match(/.(?<$>a)./u)); assertEquals("bab".match(/.(a)(.)/u), "bab".match(/.(?a)(.)/u)); assertEquals("bab".match(/.(a)(.)/u), "bab".match(/.(?a)(?.)/u)); assertEquals("bab".match(/.(\w\w)/u), "bab".match(/.(?\w\w)/u)); assertEquals("bab".match(/(\w\w\w)/u), "bab".match(/(?\w\w\w)/u)); assertEquals("bab".match(/(\w\w)(\w)/u), "bab".match(/(?\w\w)(?\w)/u)); assertEquals(["bab", "b"], "bab".match(/(?b).\1/u)); assertEquals(["baba", "b", "a"], "baba".match(/(.)(?a)\1\2/u)); assertEquals(["baba", "b", "a", "b", "a"], "baba".match(/(.)(?a)(?\1)(\2)/u)); assertEquals(["<)a/u)); assertEquals([">a", ">"], ">a".match(/(?>)a/u)); // Named references. assertEquals(["bab", "b"], "bab".match(/(?.).\k/u)); assertNull("baa".match(/(?.).\k/u)); // Nested groups. assertEquals(["bab", "bab", "ab", "b"], "bab".match(/(?.(?.(?.)))/u)); assertEquals({a: "bab", b: "ab", c: "b"}, "bab".match(/(?.(?.(?.)))/u).groups); // Reference inside group. assertEquals(["bab", "b"], "bab".match(/(?\k\w)../u)); assertEquals({a: "b"}, "bab".match(/(?\k\w)../u).groups); // Reference before group. assertEquals(["bab", "b"], "bab".match(/\k(?b)\w\k/u)); assertEquals({a: "b"}, "bab".match(/\k(?b)\w\k/u).groups); assertEquals(["bab", "b", "a"], "bab".match(/(?b)\k(?a)\k/u)); assertEquals({a: "a", b: "b"}, "bab".match(/(?b)\k(?a)\k/u).groups); assertEquals(["bab", "b"], "bab".match(/\k(?b)\w\k/)); assertEquals(["bab", "b", "a"], "bab".match(/(?b)\k(?a)\k/)); // Reference properties. assertEquals("a", /(?a)(?b)\k/u.exec("aba").groups.a); assertEquals("b", /(?a)(?b)\k/u.exec("aba").groups.b); assertEquals(undefined, /(?a)(?b)\k/u.exec("aba").groups.c); assertEquals(undefined, /(?a)(?b)\k|(?c)/u.exec("aba").groups.c); // Unicode names. assertEquals("a", /(?<π>a)/u.exec("bab").groups.π); assertEquals("a", /(?<\u{03C0}>a)/u.exec("bab").groups.π); assertEquals("a", /(?<π>a)/u.exec("bab").groups.\u03C0); assertEquals("a", /(?<\u{03C0}>a)/u.exec("bab").groups.\u03C0); assertEquals("a", /(?<$>a)/u.exec("bab").groups.$); assertEquals("a", /(?<_>a)/u.exec("bab").groups._); assertEquals("a", /(?<$𐒤>a)/u.exec("bab").groups.$𐒤); assertEquals("a", /(?<_\u200C>a)/u.exec("bab").groups._\u200C); assertEquals("a", /(?<_\u200D>a)/u.exec("bab").groups._\u200D); assertEquals("a", /(?<ಠ_ಠ>a)/u.exec("bab").groups.ಠ_ಠ); assertThrows('/(?<❤>a)/u', SyntaxError); assertThrows('/(?<𐒤>a)/u', SyntaxError); // ID_Continue but not ID_Start. assertEquals("a", /(?<π>a)/.exec("bab").groups.π); assertEquals("a", /(?<$>a)/.exec("bab").groups.$); assertEquals("a", /(?<_>a)/.exec("bab").groups._); assertThrows("/(?<$𐒤>a)/", SyntaxError); assertEquals("a", /(?<ಠ_ಠ>a)/.exec("bab").groups.ಠ_ಠ); assertThrows('/(?<❤>a)/', SyntaxError); assertThrows('/(?<𐒤>a)/', SyntaxError); // ID_Continue but not ID_Start. // Interaction with lookbehind assertions. assertEquals(["f", "c"], "abcdef".match(/(?<=(?\w){3})f/u)); assertEquals({a: "c"}, "abcdef".match(/(?<=(?\w){3})f/u).groups); assertEquals({a: "b"}, "abcdef".match(/(?<=(?\w){4})f/u).groups); assertEquals({a: "a"}, "abcdef".match(/(?<=(?\w)+)f/u).groups); assertNull("abcdef".match(/(?<=(?\w){6})f/u)); assertEquals(["f", ""], "abcdef".match(/((?<=\w{3}))f/u)); assertEquals(["f", ""], "abcdef".match(/(?(?<=\w{3}))f/u)); assertEquals(["f", undefined], "abcdef".match(/(?\d){3})f/u)); assertNull("abcdef".match(/(?\D){3})f/u)); assertEquals(["f", undefined], "abcdef".match(/(?\D){3})f|f/u)); assertEquals(["f", undefined], "abcdef".match(/(?(?.)|(?.)/u.exec("abcd").groups)); // The '__proto__' property on the groups object. assertEquals(undefined, /(?.)/u.exec("a").groups.__proto__); assertEquals("a", /(?<__proto__>a)/u.exec("a").groups.__proto__); // Backslash as ID_Start and ID_Continue (v8:5868). assertThrows("/(?<\\>.)/", SyntaxError); // '\' misclassified as ID_Start. assertThrows("/(?.)/", SyntaxError); // '\' misclassified as ID_Continue. // Backreference before the group (exercises the capture mini-parser). assertThrows("/\\1(?:.)/u", SyntaxError); assertThrows("/\\1(?<=a)./u", SyntaxError); assertThrows("/\\1(?.)/u.exec("abcd")); // Unicode escapes in capture names. assertTrue(/(?.)/u.test("a")); // \u Lead \u Trail assertThrows("/(?.)/u", SyntaxError); // \u Lead assertThrows("/(?.)/u", SyntaxError); // \u Trail assertTrue(/(?<\u0041>.)/u.test("a")); // \u NonSurrogate assertTrue(/(?<\u{0041}>.)/u.test("a")); // \u{ Non-surrogate } assertTrue(/(?.)/u.test("a")); // \u{ Surrogate, ID_Continue } assertThrows("/(?.)/u", SyntaxError); // \u{ Out-of-bounds } assertThrows("/(?.)/u", SyntaxError); // Lead assertThrows("/(?.)/u", SyntaxError); // Trail assertThrows("/(?.)/u", SyntaxError); // Lead assertThrows("/(?.)/u", SyntaxError); // Trail assertTrue(RegExp("(?<\\u{0041}>.)", "u").test("a")); // Non-surrogate assertTrue(RegExp("(?.)", "u").test("a")); // Surrogate,ID_Continue assertTrue(RegExp("(?<\u{0041}>.)", "u").test("a")); // Non-surrogate assertTrue(RegExp("(?.)", "u").test("a")); // Surrogate,ID_Continue assertTrue(RegExp("(?<\\u0041>.)", "u").test("a")); // Non-surrogate assertThrows("/(?.)/", SyntaxError); assertThrows("/(?.)/", SyntaxError); assertThrows("/(?.)/", SyntaxError); assertTrue(/(?<\u0041>.)/.test("a")); assertThrows("/(?<\\u{0041}>.)/", SyntaxError); assertThrows("/(?.)/", SyntaxError); assertThrows("/(?.)/", SyntaxError); assertThrows("/(?.)/", SyntaxError); // Lead assertThrows("/(?.)/", SyntaxError); // Trail; assertThrows("/(?.)/", SyntaxError); // Lead assertThrows("/(?.)/", SyntaxError); // Trail assertThrows("/(?<\\u{0041}>.)/", SyntaxError); // Non-surrogate assertThrows("/(?.)/", SyntaxError); // Surrogate, ID_Continue assertTrue(RegExp("(?<\u{0041}>.)").test("a")); // Non-surrogate assertThrows("(?.)", SyntaxError); // Surrogate, ID_Continue assertTrue(RegExp("(?<\\u0041>.)").test("a")); // Non-surrogate // @@replace with a callable replacement argument (no named captures). { let result = "abcd".replace(/(.)(.)/u, (match, fst, snd, offset, str) => { assertEquals("ab", match); assertEquals("a", fst); assertEquals("b", snd); assertEquals(0, offset); assertEquals("abcd", str); return `${snd}${fst}`; }); assertEquals("bacd", result); assertEquals("undefinedbcd", "abcd".replace(/(.)|(.)/u, (match, fst, snd, offset, str) => snd)); } // @@replace with a callable replacement argument (global, named captures). { let i = 0; let result = "abcd".replace(/(?.)(?.)/gu, (match, fst, snd, offset, str, groups) => { if (i == 0) { assertEquals("ab", match); assertEquals("a", groups.fst); assertEquals("b", groups.snd); assertEquals("a", fst); assertEquals("b", snd); assertEquals(0, offset); assertEquals("abcd", str); } else if (i == 1) { assertEquals("cd", match); assertEquals("c", groups.fst); assertEquals("d", groups.snd); assertEquals("c", fst); assertEquals("d", snd); assertEquals(2, offset); assertEquals("abcd", str); } else { assertUnreachable(); } i++; return `${groups.snd}${groups.fst}`; }); assertEquals("badc", result); assertEquals("undefinedundefinedundefinedundefined", "abcd".replace(/(?.)|(?.)/gu, (match, fst, snd, offset, str, groups) => groups.snd)); } // @@replace with a callable replacement argument (non-global, named captures). { let result = "abcd".replace(/(?.)(?.)/u, (match, fst, snd, offset, str, groups) => { assertEquals("ab", match); assertEquals("a", groups.fst); assertEquals("b", groups.snd); assertEquals("a", fst); assertEquals("b", snd); assertEquals(0, offset); assertEquals("abcd", str); return `${groups.snd}${groups.fst}`; }); assertEquals("bacd", result); assertEquals("undefinedbcd", "abcd".replace(/(?.)|(?.)/u, (match, fst, snd, offset, str, groups) => groups.snd)); } function toSlowMode(re) { re.exec = (str) => RegExp.prototype.exec.call(re, str); return re; } // @@replace with a callable replacement argument (slow, global, // named captures). { let i = 0; let re = toSlowMode(/(?.)(?.)/gu); let result = "abcd".replace(re, (match, fst, snd, offset, str, groups) => { if (i == 0) { assertEquals("ab", match); assertEquals("a", groups.fst); assertEquals("b", groups.snd); assertEquals("a", fst); assertEquals("b", snd); assertEquals(0, offset); assertEquals("abcd", str); } else if (i == 1) { assertEquals("cd", match); assertEquals("c", groups.fst); assertEquals("d", groups.snd); assertEquals("c", fst); assertEquals("d", snd); assertEquals(2, offset); assertEquals("abcd", str); } else { assertUnreachable(); } i++; return `${groups.snd}${groups.fst}`; }); assertEquals("badc", result); assertEquals("undefinedundefinedundefinedundefined", "abcd".replace(toSlowMode(/(?.)|(?.)/gu), (match, fst, snd, offset, str, groups) => groups.snd)); } // @@replace with a callable replacement argument (slow, non-global, // named captures). { let re = toSlowMode(/(?.)(?.)/u); let result = "abcd".replace(re, (match, fst, snd, offset, str, groups) => { assertEquals("ab", match); assertEquals("a", groups.fst); assertEquals("b", groups.snd); assertEquals("a", fst); assertEquals("b", snd); assertEquals(0, offset); assertEquals("abcd", str); return `${groups.snd}${groups.fst}`; }); assertEquals("bacd", result); assertEquals("undefinedbcd", "abcd".replace(toSlowMode(/(?.)|(?.)/u), (match, fst, snd, offset, str, groups) => groups.snd)); } // @@replace with a string replacement argument (no named captures). { let re = /(.)(.)|(x)/u; assertEquals("$$cd", "abcd".replace(re, "$$")); assertEquals("bacd", "abcd".replace(re, "$2$1")); assertEquals("cd", "abcd".replace(re, "$3")); assertEquals("$cd", "abcd".replace(re, "$<42$1>")); assertEquals("$cd", "abcd".replace(re, "$")); assertEquals("$cd", "abcd".replace(re, "$<$1>")); } // @@replace with a string replacement argument (global, named captures). { let re = /(?.)(?.)|(?x)/gu; assertEquals("badc", "abcd".replace(re, "$$")); assertEquals("badc", "abcd".replace(re, "$2$1")); assertEquals("", "abcd".replace(re, "$")); assertEquals("$")); assertEquals("", "abcd".replace(re, "$")); assertEquals("", "abcd".replace(re, "$<$1>")); } // @@replace with a string replacement argument (non-global, named captures). { let re = /(?.)(?.)|(?x)/u; assertEquals("bacd", "abcd".replace(re, "$$")); assertEquals("bacd", "abcd".replace(re, "$2$1")); assertEquals("cd", "abcd".replace(re, "$")); assertEquals("$")); assertEquals("cd", "abcd".replace(re, "$")); assertEquals("cd", "abcd".replace(re, "$<$1>")); } // @@replace with a string replacement argument (slow, global, named captures). { let re = toSlowMode(/(?.)(?.)|(?x)/gu); assertEquals("badc", "abcd".replace(re, "$$")); assertEquals("badc", "abcd".replace(re, "$2$1")); assertEquals("", "abcd".replace(re, "$")); assertEquals("$")); assertEquals("", "abcd".replace(re, "$")); assertEquals("", "abcd".replace(re, "$<$1>")); } // @@replace with a string replacement argument (slow, non-global, // named captures). { let re = toSlowMode(/(?.)(?.)|(?x)/u); assertEquals("bacd", "abcd".replace(re, "$$")); assertEquals("bacd", "abcd".replace(re, "$2$1")); assertEquals("cd", "abcd".replace(re, "$")); assertEquals("$")); assertEquals("cd", "abcd".replace(re, "$")); assertEquals("cd", "abcd".replace(re, "$<$1>")); } // Tests for 'groups' semantics on the regexp result object. // https://crbug.com/v8/7192 { const re = /./; const result = re.exec("a"); assertTrue(%SpeciesProtector()); assertEquals(result.__proto__, Array.prototype); assertTrue(result.hasOwnProperty('groups')); assertArrayEquals(["a"], result); assertEquals(0, result.index); assertEquals(undefined, result.groups); Array.prototype.groups = { a: "b" }; assertTrue(%SpeciesProtector()); assertEquals("$", "a".replace(re, "$")); Array.prototype.groups = undefined; } { const re = toSlowMode(/./); const result = re.exec("a"); assertTrue(%SpeciesProtector()); assertEquals(result.__proto__, Array.prototype); assertTrue(result.hasOwnProperty('groups')); assertArrayEquals(["a"], result); assertEquals(0, result.index); assertEquals(undefined, result.groups); Array.prototype.groups = { a: "b" }; assertTrue(%SpeciesProtector()); assertEquals("$", "a".replace(re, "$")); Array.prototype.groups = undefined; } { const re = /(?a).|(?x)/; const result = re.exec("ab"); assertTrue(%SpeciesProtector()); assertEquals(result.__proto__, Array.prototype); assertTrue(result.hasOwnProperty('groups')); assertArrayEquals(["ab", "a", undefined], result); assertEquals(0, result.index); assertEquals({a: "a", x: undefined}, result.groups); // a is a matched named capture, b is an unmatched named capture, and z // is not a named capture. Array.prototype.groups = { a: "b", x: "y", z: "z" }; assertTrue(%SpeciesProtector()); assertEquals("a", "ab".replace(re, "$")); assertEquals("", "ab".replace(re, "$")); assertEquals("", "ab".replace(re, "$")); Array.prototype.groups = undefined; } { const re = toSlowMode(/(?a).|(?x)/); const result = re.exec("ab"); assertTrue(%SpeciesProtector()); assertEquals(result.__proto__, Array.prototype); assertTrue(result.hasOwnProperty('groups')); assertArrayEquals(["ab", "a", undefined], result); assertEquals(0, result.index); assertEquals({a: "a", x: undefined}, result.groups); // a is a matched named capture, b is an unmatched named capture, and z // is not a named capture. Array.prototype.groups = { a: "b", x: "y", z: "z" }; assertTrue(%SpeciesProtector()); assertEquals("a", "ab".replace(re, "$")); assertEquals("", "ab".replace(re, "$")); assertEquals("", "ab".replace(re, "$")); Array.prototype.groups = undefined; } { class FakeRegExp extends RegExp { exec(subject) { const fake_result = [ "ab", "a" ]; fake_result.index = 0; // groups is not set, triggering prototype lookup. return fake_result; } }; const re = new FakeRegExp(); const result = re.exec("ab"); assertTrue(%SpeciesProtector()); assertEquals(result.__proto__, Array.prototype); assertFalse(result.hasOwnProperty('groups')); Array.prototype.groups = { a: "b" }; Array.prototype.groups.__proto__.b = "c"; assertTrue(%SpeciesProtector()); assertEquals("b", "ab".replace(re, "$")); assertEquals("c", "ab".replace(re, "$")); Array.prototype.groups = undefined; } { class FakeRegExp extends RegExp { exec(subject) { const fake_result = [ "ab", "a" ]; fake_result.index = 0; fake_result.groups = { a: "b" }; fake_result.groups.__proto__.b = "c"; return fake_result; } }; const re = new FakeRegExp(); const result = re.exec("ab"); assertTrue(%SpeciesProtector()); assertEquals(result.__proto__, Array.prototype); assertTrue(result.hasOwnProperty('groups')); assertEquals({ a: "b" }, result.groups); assertEquals("b", "ab".replace(re, "$")); assertEquals("c", "ab".replace(re, "$")); }