diff --git a/test/built-ins/RegExp/named-groups/functional-replace-global.js b/test/built-ins/RegExp/named-groups/functional-replace-global.js new file mode 100644 index 0000000000000000000000000000000000000000..2fb5d49c8211ca13e065837e2ffbf9ca1d64e26c --- /dev/null +++ b/test/built-ins/RegExp/named-groups/functional-replace-global.js @@ -0,0 +1,56 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: > + Function argument to String.prototype.replace gets groups as the last argument +esid: sec-regexp.prototype-@@replace +features: [regexp-named-groups] +info: > + RegExp.prototype [ @@replace ] ( string, replaceValue ) + 14. Repeat, for each result in results, + j. Let namedCaptures be ? Get(result, "groups"). + k. If functionalReplace is true, then + iv. If namedCaptures is not undefined, + 1. Append namedCaptures as the last element of replacerArgs. +---*/ + +let source = "(?<fst>.)(?<snd>.)"; +let alternateSource = "(?<fst>.)|(?<snd>.)"; + +for (let flags of ["g", "gu"]) { + let i = 0; + let re = new RegExp(source, flags); + let result = "abcd".replace(re, + (match, fst, snd, offset, str, groups) => { + if (i == 0) { + assert.sameValue("ab", match); + assert.sameValue("a", groups.fst); + assert.sameValue("b", groups.snd); + assert.sameValue("a", fst); + assert.sameValue("b", snd); + assert.sameValue(0, offset); + assert.sameValue("abcd", str); + } else if (i == 1) { + assert.sameValue("cd", match); + assert.sameValue("c", groups.fst); + assert.sameValue("d", groups.snd); + assert.sameValue("c", fst); + assert.sameValue("d", snd); + assert.sameValue(2, offset); + assert.sameValue("abcd", str); + } else { + assertUnreachable(); + } + i++; + return `${groups.snd}${groups.fst}`; + }); + assert.sameValue("badc", result); + assert.sameValue(i, 2); + + let re2 = new RegExp(alternateSource, flags); + assert.sameValue("undefinedundefinedundefinedundefined", + "abcd".replace(re2, + (match, fst, snd, offset, str, groups) => groups.snd)); +} + diff --git a/test/built-ins/RegExp/named-groups/functional-replace-non-global.js b/test/built-ins/RegExp/named-groups/functional-replace-non-global.js new file mode 100644 index 0000000000000000000000000000000000000000..5186a88fa2b255798176a5c9ea838ead7297cc3d --- /dev/null +++ b/test/built-ins/RegExp/named-groups/functional-replace-non-global.js @@ -0,0 +1,43 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: > + Function argument to String.prototype.replace gets groups as the last argument +esid: sec-regexp.prototype-@@replace +features: [regexp-named-groups] +info: > + RegExp.prototype [ @@replace ] ( string, replaceValue ) + 14. Repeat, for each result in results, + j. Let namedCaptures be ? Get(result, "groups"). + k. If functionalReplace is true, then + iv. If namedCaptures is not undefined, + 1. Append namedCaptures as the last element of replacerArgs. +---*/ + +let source = "(?<fst>.)(?<snd>.)"; +let alternateSource = "(?<fst>.)|(?<snd>.)"; + +for (let flags of ["", "u"]) { + let i = 0; + let re = new RegExp(source, flags); + let result = "abcd".replace(re, + (match, fst, snd, offset, str, groups) => { + assert.sameValue(i++, 0); + assert.sameValue("ab", match); + assert.sameValue("a", groups.fst); + assert.sameValue("b", groups.snd); + assert.sameValue("a", fst); + assert.sameValue("b", snd); + assert.sameValue(0, offset); + assert.sameValue("abcd", str); + return `${groups.snd}${groups.fst}`; + }); + assert.sameValue("bacd", result); + assert.sameValue(i, 1); + + let re2 = new RegExp(alternateSource, flags); + assert.sameValue("undefinedbcd", + "abcd".replace(re2, + (match, fst, snd, offset, str, groups) => groups.snd)); +} diff --git a/test/built-ins/RegExp/named-groups/groups-properties.js b/test/built-ins/RegExp/named-groups/groups-properties.js new file mode 100644 index 0000000000000000000000000000000000000000..6d7b46ec63549a88e0264bb57a6c69cb91994415 --- /dev/null +++ b/test/built-ins/RegExp/named-groups/groups-properties.js @@ -0,0 +1,39 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Properties of the groups object are created with CreateDataProperty +includes: [compareArray.js, propertyHelper.js] +esid: sec-regexpbuiltinexec +features: [regexp-named-groups] +info: > + Runtime Semantics: RegExpBuiltinExec ( R, S ) + 25. For each integer i such that i > 0 and i ≤ n + f. If the ith capture of R was defined with a GroupName, + i. Let s be the StringValue of the corresponding RegExpIdentifierName. + ii. Perform ! CreateDataProperty(groups, s, capturedValue). +---*/ + +// Properties created on result.groups in textual order. +assert(compareArray(["fst", "snd"], + Object.getOwnPropertyNames( + /(?<fst>.)|(?<snd>.)/u.exec("abcd").groups))); + +// Properties are created with Define, not Set +let counter = 0; +Object.defineProperty(Object.prototype, 'x', {set() { counter++; }}); +let match = /(?<x>.)/.exec('a'); +let groups = match.groups; +assert.sameValue(counter, 0); + +// Properties are writable, enumerable and configurable +// (from CreateDataProperty) +verifyWritable(groups, "x"); +verifyEnumerable(groups, "x"); +verifyConfigurable(groups, "x"); + +// The '__proto__' property on the groups object is not special, +// and does not affect the [[Prototype]] of the resulting groups object. +groups = /(?<__proto__>a)/u.exec("a").groups; +assert.sameValue("a", groups.__proto__); +assert.sameValue(null, Object.getPrototypeOf(groups)); diff --git a/test/built-ins/RegExp/named-groups/lookbehind.js b/test/built-ins/RegExp/named-groups/lookbehind.js new file mode 100644 index 0000000000000000000000000000000000000000..0009bc5008f727d344b0074c589beaea46339621 --- /dev/null +++ b/test/built-ins/RegExp/named-groups/lookbehind.js @@ -0,0 +1,46 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Named groups can be used in conjunction with lookbehind +esid: pending +features: [regexp-named-groups, regexp-lookbehind] +includes: [compareArray.js] +---*/ + +// Unicode mode +assert(compareArray(["f", "c"], "abcdef".match(/(?<=(?<a>\w){3})f/u))); +assert.sameValue("c", "abcdef".match(/(?<=(?<a>\w){3})f/u).groups.a); +assert.sameValue("b", "abcdef".match(/(?<=(?<a>\w){4})f/u).groups.a); +assert.sameValue("a", "abcdef".match(/(?<=(?<a>\w)+)f/u).groups.a); +assert.sameValue(null, "abcdef".match(/(?<=(?<a>\w){6})f/u)); + +assert(compareArray(["f", ""], "abcdef".match(/((?<=\w{3}))f/u))); +assert(compareArray(["f", ""], "abcdef".match(/(?<a>(?<=\w{3}))f/u))); + +assert(compareArray(["f", undefined], "abcdef".match(/(?<!(?<a>\d){3})f/u))); +assert.sameValue(null, "abcdef".match(/(?<!(?<a>\D){3})f/u)); + +assert(compareArray(["f", undefined], "abcdef".match(/(?<!(?<a>\D){3})f|f/u))); +assert(compareArray(["f", undefined], "abcdef".match(/(?<a>(?<!\D{3}))f|f/u))); + +// Non-Unicode mode +assert(compareArray(["f", "c"], "abcdef".match(/(?<=(?<a>\w){3})f/))); +assert.sameValue("c", "abcdef".match(/(?<=(?<a>\w){3})f/).groups.a); +assert.sameValue("b", "abcdef".match(/(?<=(?<a>\w){4})f/).groups.a); +assert.sameValue("a", "abcdef".match(/(?<=(?<a>\w)+)f/).groups.a); +assert.sameValue(null, "abcdef".match(/(?<=(?<a>\w){6})f/)); + +assert(compareArray(["f", ""], "abcdef".match(/((?<=\w{3}))f/))); +assert(compareArray(["f", ""], "abcdef".match(/(?<a>(?<=\w{3}))f/))); + +assert(compareArray(["f", undefined], "abcdef".match(/(?<!(?<a>\d){3})f/))); +assert.sameValue(null, "abcdef".match(/(?<!(?<a>\D){3})f/)); + +assert(compareArray(["f", undefined], "abcdef".match(/(?<!(?<a>\D){3})f|f/))); +assert(compareArray(["f", undefined], "abcdef".match(/(?<a>(?<!\D{3}))f|f/))); + +// Even within a lookbehind, properties are created in left to right order +assert(compareArray(["fst", "snd"], + Object.getOwnPropertyNames( + /(?<=(?<fst>.)|(?<snd>.))/u.exec("abcd").groups))); diff --git a/test/built-ins/RegExp/named-groups/non-unicode-malformed.js b/test/built-ins/RegExp/named-groups/non-unicode-malformed.js new file mode 100644 index 0000000000000000000000000000000000000000..6a6d7bd9ecfbf2c66d55ed4932d063ef226b9cc2 --- /dev/null +++ b/test/built-ins/RegExp/named-groups/non-unicode-malformed.js @@ -0,0 +1,44 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: > + Named groups in Unicode RegExps have some syntax errors and some + compatibility escape fallback behavior. +esid: pending +features: [regexp-named-groups, regexp-lookbehind] +includes: [compareArray.js] +---*/ + +assert.throws(SyntaxError, () => eval("/(?<>a)/")); +assert.throws(SyntaxError, () => eval("/(?<aa)/")); +assert.throws(SyntaxError, () => eval("/(?<42a>a)/")); +assert.throws(SyntaxError, () => eval("/(?<:a>a)/")); +assert.throws(SyntaxError, () => eval("/(?<a:>a)/")); +assert.throws(SyntaxError, () => eval("/(?<a>a)(?<a>a)/")); +assert.throws(SyntaxError, () => eval("/(?<a>a)(?<b>b)(?<a>a)/")); +assert(/\k<a>/.test("k<a>")); +assert(/\k<4>/.test("k<4>")); +assert(/\k<a/.test("k<a")); +assert(/\k/.test("k")); +assert.throws(SyntaxError, () => eval("/(?<a>.)\\k/")); +assert.throws(SyntaxError, () => eval("/(?<a>.)\\k<a/")); +assert.throws(SyntaxError, () => eval("/(?<a>.)\\k<b>/")); +assert.throws(SyntaxError, () => eval("/(?<a>a)\\k<ab>/")); +assert.throws(SyntaxError, () => eval("/(?<ab>a)\\k<a>/")); +assert.throws(SyntaxError, () => eval("/\\k<a>(?<ab>a)/")); +assert.throws(SyntaxError, () => eval("/\\k<a(?<a>a)/")); +assert(/(?<a>\a)/.test("a")); + +assert(compareArray(["k<a>"], "xxxk<a>xxx".match(/\k<a>/))); +assert(compareArray(["k<a"], "xxxk<a>xxx".match(/\k<a/))); + +// A couple of corner cases around '\k' as named back-references vs. identity +// escapes. +assert(/\k<a>(?<=>)a/.test("k<a>a")); +assert(/\k<a>(?<!a)a/.test("k<a>a")); +assert(/\k<a>(<a>x)/.test("k<a><a>x")); +assert(/\k<a>(?<a>x)/.test("x")); +assert.throws(SyntaxError, () => eval("/\\k<a>(?<b>x)/")); +assert.throws(SyntaxError, () => eval("/\\k<a(?<a>.)/")); +assert.throws(SyntaxError, () => eval("/\\k(?<a>.)/")); diff --git a/test/built-ins/RegExp/named-groups/non-unicode-match.js b/test/built-ins/RegExp/named-groups/non-unicode-match.js new file mode 100644 index 0000000000000000000000000000000000000000..4e29bb24a4638bf91fa3dfb636ec9877fe20ca27 --- /dev/null +++ b/test/built-ins/RegExp/named-groups/non-unicode-match.js @@ -0,0 +1,43 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Basic matching cases with non-Unicode groups +esid: pending +features: [regexp-named-groups] +includes: [compareArray.js] +---*/ + +assert(compareArray(["a", "a"], "bab".match(/(?<a>a)/))); +assert(compareArray(["a", "a"], "bab".match(/(?<a42>a)/))); +assert(compareArray(["a", "a"], "bab".match(/(?<_>a)/))); +assert(compareArray(["a", "a"], "bab".match(/(?<$>a)/))); +assert(compareArray(["bab", "a"], "bab".match(/.(?<$>a)./))); +assert(compareArray(["bab", "a", "b"], "bab".match(/.(?<a>a)(.)/))); +assert(compareArray(["bab", "a", "b"], "bab".match(/.(?<a>a)(?<b>.)/))); +assert(compareArray(["bab", "ab"], "bab".match(/.(?<a>\w\w)/))); +assert(compareArray(["bab", "bab"], "bab".match(/(?<a>\w\w\w)/))); +assert(compareArray(["bab", "ba", "b"], "bab".match(/(?<a>\w\w)(?<b>\w)/))); + +let {a, b, c} = /(?<a>.)(?<b>.)(?<c>.)\k<c>\k<b>\k<a>/.exec("abccba").groups; +assert.sameValue(a, "a"); +assert.sameValue(b, "b"); +assert.sameValue(c, "c"); + +assert(compareArray("bab".match(/(a)/), "bab".match(/(?<a>a)/))); +assert(compareArray("bab".match(/(a)/), "bab".match(/(?<a42>a)/))); +assert(compareArray("bab".match(/(a)/), "bab".match(/(?<_>a)/))); +assert(compareArray("bab".match(/(a)/), "bab".match(/(?<$>a)/))); +assert(compareArray("bab".match(/.(a)./), "bab".match(/.(?<$>a)./))); +assert(compareArray("bab".match(/.(a)(.)/), "bab".match(/.(?<a>a)(.)/))); +assert(compareArray("bab".match(/.(a)(.)/), "bab".match(/.(?<a>a)(?<b>.)/))); +assert(compareArray("bab".match(/.(\w\w)/), "bab".match(/.(?<a>\w\w)/))); +assert(compareArray("bab".match(/(\w\w\w)/), "bab".match(/(?<a>\w\w\w)/))); +assert(compareArray("bab".match(/(\w\w)(\w)/), "bab".match(/(?<a>\w\w)(?<b>\w)/))); + +assert(compareArray(["bab", "b"], "bab".match(/(?<b>b).\1/))); +assert(compareArray(["baba", "b", "a"], "baba".match(/(.)(?<a>a)\1\2/))); +assert(compareArray(["baba", "b", "a", "b", "a"], + "baba".match(/(.)(?<a>a)(?<b>\1)(\2)/))); +assert(compareArray(["<a", "<"], "<a".match(/(?<lt><)a/))); +assert(compareArray([">a", ">"], ">a".match(/(?<gt>>)a/))); diff --git a/test/built-ins/RegExp/named-groups/non-unicode-property-names.js b/test/built-ins/RegExp/named-groups/non-unicode-property-names.js new file mode 100644 index 0000000000000000000000000000000000000000..fea50314a429920f857367b933bc22be034fc3f6 --- /dev/null +++ b/test/built-ins/RegExp/named-groups/non-unicode-property-names.js @@ -0,0 +1,45 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Exotic named group names in non-Unicode RegExps +esid: pending +features: [regexp-named-groups] +includes: [compareArray.js] +---*/ + +assert.sameValue("a", /(?<Ï€>a)/.exec("bab").groups.Ï€); +assert.throws(SyntaxError, () => eval('/(?<\\u{03C0}>a)/'), "\\u{} escapes allowed only in Unicode mode"); +assert.sameValue("a", /(?<Ï€>a)/.exec("bab").groups.\u03C0); +assert.sameValue("a", /(?<$>a)/.exec("bab").groups.$); +assert.sameValue("a", /(?<_>a)/.exec("bab").groups._); +assert.throws(SyntaxError, () => eval('/(?<$ð’¤>a)/'), "Individual surrogates not in ID_Continue); +assert.sameValue("a", /(?<_\u200C>a)/.exec("bab").groups._\u200C); +assert.sameValue("a", /(?<_\u200D>a)/.exec("bab").groups._\u200D); +assert.sameValue("a", /(?<ಠ_ಠ>a)/.exec("bab").groups.ಠ_ಠ); +assert.throws(SyntaxError, () => eval('/(?<â¤>a)/')); +assert.throws(SyntaxError, () => eval('/(?<ð’¤>a)/'), "Individual surrogate not in ID_Start."); + +// Unicode escapes in capture names. +assert.throws(SyntaxError, () => eval("/(?<a\\uD801\uDCA4>.)/")); +assert.throws(SyntaxError, () => eval("/(?<a\\uD801>.)/")); +assert.throws(SyntaxError, () => eval("/(?<a\\uDCA4>.)/")); +assert(/(?<\u0041>.)/.test("a")); +assert.throws(SyntaxError, () => eval("/(?<a\\u{104A4}>.)/")); +assert.throws(SyntaxError, () => eval("/(?<a\\u{10FFFF}>.)/")); +assert.throws(SyntaxError, () => eval("/(?<a\uD801>.)/"), "Lea"); +assert.throws(SyntaxError, () => eval("/(?<a\uDCA4>.)/"), "Trai"); +assert(RegExp("(?<\u{0041}>.)").test("a"), "Non-surrogate"); +assert(RegExp("(?<a\u{104A4}>.)").test("a"), "Surrogate, ID_Continue"); + +// Bracketed escapes are not allowed; +// 4-char escapes must be the proper ID_Start/ID_Continue +assert.throws(SyntaxError, () => eval("/(?<a\\uD801>.)/"), "Lead"); +assert.throws(SyntaxError, () => eval("/(?<a\\uDCA4>.)/"), "Trail"); +assert.throws(SyntaxError, () => eval("/(?<\\u{0041}>.)/"), "Non-surrogate"); +assert.throws(SyntaxError, () => eval("/(?<a\\u{104A4}>.)/"), "Surrogate, ID_Continue"); +assert(RegExp("(?<\\u0041>.)").test("a"), "Non-surrogate"); + +// Backslash is not allowed as ID_Start and ID_Continue +assert.throws(SyntaxError, () => eval("/(?<\\>.)/"), "'\' misclassified as ID_Start"); +assert.throws(SyntaxError, () => eval("/(?<a\\>.)/"), "'\' misclassified as ID_Continue"); diff --git a/test/built-ins/RegExp/named-groups/non-unicode-references.js b/test/built-ins/RegExp/named-groups/non-unicode-references.js new file mode 100644 index 0000000000000000000000000000000000000000..bbf7a0fa5aa0ac762cea9f1eebe36cf4e98faa2b --- /dev/null +++ b/test/built-ins/RegExp/named-groups/non-unicode-references.js @@ -0,0 +1,34 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Named backreferences in non-Unicode RegExps +esid: pending +features: [regexp-named-groups] +includes: [compareArray.js] +---*/ + +// Named references. +assert(compareArray(["bab", "b"], "bab".match(/(?<b>.).\k<b>/))); +assert.sameValue(null, "baa".match(/(?<b>.).\k<b>/)); + +// Reference inside group. +assert(compareArray(["bab", "b"], "bab".match(/(?<a>\k<a>\w)../))); +assert.sameValue("b", "bab".match(/(?<a>\k<a>\w)../).groups.a); + +// Reference before group. +assert(compareArray(["bab", "b"], "bab".match(/\k<a>(?<a>b)\w\k<a>/))); +assert.sameValue("b", "bab".match(/\k<a>(?<a>b)\w\k<a>/).groups.a); +assert(compareArray(["bab", "b", "a"], "bab".match(/(?<b>b)\k<a>(?<a>a)\k<b>/))); +let {a, b} = "bab".match(/(?<b>b)\k<a>(?<a>a)\k<b>/).groups; +assert.sameValue(a, "a"); +assert.sameValue(b, "b"); + +assert(compareArray(["bab", "b"], "bab".match(/\k<a>(?<a>b)\w\k<a>/))); +assert(compareArray(["bab", "b", "a"], "bab".match(/(?<b>b)\k<a>(?<a>a)\k<b>/))); + +// Reference properties. +assert.sameValue("a", /(?<a>a)(?<b>b)\k<a>/.exec("aba").groups.a); +assert.sameValue("b", /(?<a>a)(?<b>b)\k<a>/.exec("aba").groups.b); +assert.sameValue(undefined, /(?<a>a)(?<b>b)\k<a>/.exec("aba").groups.c); +assert.sameValue(undefined, /(?<a>a)(?<b>b)\k<a>|(?<c>c)/.exec("aba").groups.c); diff --git a/test/built-ins/RegExp/named-groups/string-replace-get.js b/test/built-ins/RegExp/named-groups/string-replace-get.js new file mode 100644 index 0000000000000000000000000000000000000000..2234d995122e35e03ca0c9bee9ea54994ddfb764 --- /dev/null +++ b/test/built-ins/RegExp/named-groups/string-replace-get.js @@ -0,0 +1,29 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Named substitutions are found by getting the property from the groups object +esid: sec-getsubstitution +features: [regexp-named-groups] +info: > + Runtime Semantics: GetSubstitution( matched, str, position, captures, namedCaptures, replacement ) + + Table: Replacement Text Symbol Substitutions + + Unicode Characters: $< + Replacement text: + 2. Otherwise, + c. Let capture be ? Get(namedCaptures, groupName). + d. If capture is undefined, replace the text through > with the empty string. + e. Otherwise, replace the text through this following > with ? ToString(capture). +---*/ + +let source = "(?<fst>.)(?<snd>.)|(?<thd>x)"; +for (let flags of ["g", "gu"]) { + let re = new RegExp(source, flags); + assert.sameValue("badc", "abcd".replace(re, "$<snd>$<fst>")); +} +for (let flags of ["", "u"]) { + let re = new RegExp(source, flags); + assert.sameValue("bacd", "abcd".replace(re, "$<snd>$<fst>")); +} diff --git a/test/built-ins/RegExp/named-groups/string-replace-missing.js b/test/built-ins/RegExp/named-groups/string-replace-missing.js new file mode 100644 index 0000000000000000000000000000000000000000..6d253568c24fcfd1ba3bdcf3ccf3e433c886d9a8 --- /dev/null +++ b/test/built-ins/RegExp/named-groups/string-replace-missing.js @@ -0,0 +1,25 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: SyntaxError is thrown for malformed replacements +esid: sec-getsubstitution +features: [regexp-named-groups] +info: > + Runtime Semantics: GetSubstitution( matched, str, position, captures, namedCaptures, replacement ) + + Table: Replacement Text Symbol Substitutions + + Unicode Characters: $< + Replacement text: + 2. Otherwise, + b. If ? HasProperty(namedCaptures, groupName) is false, throw a SyntaxError exception. +---*/ + +let source = "(?<fst>.)(?<snd>.)|(?<thd>x)"; +for (let flags of ["", "u", "g", "gu"]) { + let re = new RegExp(source, flags); + assert.throws(SyntaxError, () => "abcd".replace(re, "$<42$1>")); + assert.throws(SyntaxError, () => "abcd".replace(re, "$<fth>")); + assert.throws(SyntaxError, () => "abcd".replace(re, "$<$1>")); +} diff --git a/test/built-ins/RegExp/named-groups/string-replace-nocaptures.js b/test/built-ins/RegExp/named-groups/string-replace-nocaptures.js new file mode 100644 index 0000000000000000000000000000000000000000..03fccdc87af38578577c205a5addbfeb4cba90e4 --- /dev/null +++ b/test/built-ins/RegExp/named-groups/string-replace-nocaptures.js @@ -0,0 +1,31 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: If there are no named captures, don't replace $<> +esid: sec-getsubstitution +features: [regexp-named-groups] +info: > + Runtime Semantics: GetSubstitution( matched, str, position, captures, namedCaptures, replacement ) + + Table: Replacement Text Symbol Substitutions + + Unicode Characters: $< + Replacement text: + 1. If namedCaptures is undefined, the replacement text is the literal string $<. +---*/ + +// @@replace with a string replacement argument (no named captures). + +let source = "(.)(.)|(x)"; +for (let flags of ["", "u", "g", "gu"]) { + let re = new RegExp(source, flags); + assert.sameValue("$<snd>$<fst>cd", "abcd".replace(re, "$<snd>$<fst>")); + assert.sameValue("bacd", "abcd".replace(re, "$2$1")); + assert.sameValue("cd", "abcd".replace(re, "$3")); + assert.sameValue("$<sndcd", "abcd".replace(re, "$<snd")); + assert.sameValue("$<42a>cd", "abcd".replace(re, "$<42$1>")); + assert.sameValue("$<fth>cd", "abcd".replace(re, "$<fth>")); + assert.sameValue("$<a>cd", "abcd".replace(re, "$<$1>")); +} + diff --git a/test/built-ins/RegExp/named-groups/string-replace-numbered.js b/test/built-ins/RegExp/named-groups/string-replace-numbered.js new file mode 100644 index 0000000000000000000000000000000000000000..0750bd124c2905678d28e14e9cece41d41b2dae4 --- /dev/null +++ b/test/built-ins/RegExp/named-groups/string-replace-numbered.js @@ -0,0 +1,29 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Named groups may be accessed in their replacement string by number +esid: sec-getsubstitution +features: [regexp-named-groups] +info: > + Runtime Semantics: GetSubstitution( matched, str, position, captures, namedCaptures, replacement ) + + Table: Replacement Text Symbol Substitutions + + Unicode Characters: $n + Replacement text: + The nth element of captures, where n is a single digit in the range 1 to 9. If + n≤m and the nth element of captures is undefined, use the empty String instead. + If n>m, the result is implementation-defined. +---*/ + +let source = "(?<fst>.)(?<snd>.)|(?<thd>x)"; +for (let flags of ["g", "gu"]) { + let re = new RegExp(source, flags); + assert.sameValue("badc", "abcd".replace(re, "$2$1")); +} +for (let flags of ["", "u"]) { + let re = new RegExp(source, flags); + assert.sameValue("bacd", "abcd".replace(re, "$2$1")); +} + diff --git a/test/built-ins/RegExp/named-groups/string-replace-unclosed.js b/test/built-ins/RegExp/named-groups/string-replace-unclosed.js new file mode 100644 index 0000000000000000000000000000000000000000..588bc3ed8571bbc250b325f490235e0d973a1220 --- /dev/null +++ b/test/built-ins/RegExp/named-groups/string-replace-unclosed.js @@ -0,0 +1,24 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: SyntaxError is thrown for malformed replacements +esid: sec-getsubstitution +features: [regexp-named-groups] +info: > + Runtime Semantics: GetSubstitution( matched, str, position, captures, namedCaptures, replacement ) + + Table: Replacement Text Symbol Substitutions + + Unicode Characters: $< + Replacement text: + 2. Otherwise, + a. Scan until the next >, throwing a SyntaxError exception if one is not found, and let the enclosed substring be groupName. +---*/ + +let source = "(?<fst>.)(?<snd>.)|(?<thd>x)"; +for (let flags of ["", "u", "g", "gu"]) { + let re = new RegExp(source, flags); + assert.throws(SyntaxError, () => "abcd".replace(re, "$<snd"), + "unclosed named group in replacement should throw a SyntaxError"); +} diff --git a/test/built-ins/RegExp/named-groups/string-replace-undefined.js b/test/built-ins/RegExp/named-groups/string-replace-undefined.js new file mode 100644 index 0000000000000000000000000000000000000000..5806669ba8705570229bc23e4e5febad01b93d99 --- /dev/null +++ b/test/built-ins/RegExp/named-groups/string-replace-undefined.js @@ -0,0 +1,28 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: If a named group was not reached, it is replaced by the empty string +esid: sec-getsubstitution +features: [regexp-named-groups] +info: > + Runtime Semantics: GetSubstitution( matched, str, position, captures, namedCaptures, replacement ) + + Table: Replacement Text Symbol Substitutions + + Unicode Characters: $< + Replacement text: + 2. Otherwise, + c. Let capture be ? Get(namedCaptures, groupName). + d. If capture is undefined, replace the text through > with the empty string. +---*/ + +let source = "(?<fst>.)(?<snd>.)|(?<thd>x)"; +for (let flags of ["g", "gu"]) { + let re = new RegExp(source, flags); + assert.sameValue("", "abcd".replace(re, "$<thd>")); +} +for (let flags of ["", "u"]) { + let re = new RegExp(source, flags); + assert.sameValue("cd", "abcd".replace(re, "$<thd>")); +} diff --git a/test/built-ins/RegExp/named-groups/unicode-malformed.js b/test/built-ins/RegExp/named-groups/unicode-malformed.js new file mode 100644 index 0000000000000000000000000000000000000000..f0f08a8ed4529e0b4ebc29a73f5f50c39f9280c9 --- /dev/null +++ b/test/built-ins/RegExp/named-groups/unicode-malformed.js @@ -0,0 +1,27 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Various syntax errors for Unicode RegExps containing named groups +esid: pending +features: [regexp-named-groups] +---*/ + +assert.throws(SyntaxError, () => eval("/(?<>a)/u"), "Empty name"); +assert.throws(SyntaxError, () => eval("/(?<aa)/u"), "Unterminated name"); +assert.throws(SyntaxError, () => eval("/(?<42a>a)/u"), "Name starting with digits"); +assert.throws(SyntaxError, () => eval("/(?<:a>a)/u"), "Name starting with invalid char"); +assert.throws(SyntaxError, () => eval("/(?<a:>a)/u"), "Name containing with invalid char"); +assert.throws(SyntaxError, () => eval("/(?<a>a)(?<a>a)/u"), "Duplicate name"); +assert.throws(SyntaxError, () => eval("/(?<a>a)(?<b>b)(?<a>a)/u"), "Duplicate name"); +assert.throws(SyntaxError, () => eval("/\\k<a>/u"), "Invalid reference"); +assert.throws(SyntaxError, () => eval("/\\k<a/u"), "Unterminated reference"); +assert.throws(SyntaxError, () => eval("/\\k/u"), "Lone \k"); +assert.throws(SyntaxError, () => eval("/(?<a>.)\\k/u"), "Lone \k"); +assert.throws(SyntaxError, () => eval("/(?<a>.)\\k<a/u"), "Unterminated reference"); +assert.throws(SyntaxError, () => eval("/(?<a>.)\\k<b>/u"), "Invalid reference"); +assert.throws(SyntaxError, () => eval("/(?<a>a)\\k<ab>/u"), "Invalid reference"); +assert.throws(SyntaxError, () => eval("/(?<ab>a)\\k<a>/u"), "Invalid reference"); +assert.throws(SyntaxError, () => eval("/\\k<a>(?<ab>a)/u"), "Invalid reference"); +assert.throws(SyntaxError, () => eval("/(?<a>\\a)/u"), "Identity escape in capture"); + diff --git a/test/built-ins/RegExp/named-groups/unicode-match.js b/test/built-ins/RegExp/named-groups/unicode-match.js new file mode 100644 index 0000000000000000000000000000000000000000..f840d4cfe2f9eaa05c14bc175eb80843dc9e33bf --- /dev/null +++ b/test/built-ins/RegExp/named-groups/unicode-match.js @@ -0,0 +1,48 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Basic matching cases with Unicode groups +esid: pending +features: [regexp-named-groups] +includes: [compareArray.js] +---*/ + +assert(compareArray(["a", "a"], "bab".match(/(?<a>a)/u))); +assert(compareArray(["a", "a"], "bab".match(/(?<a42>a)/u))); +assert(compareArray(["a", "a"], "bab".match(/(?<_>a)/u))); +assert(compareArray(["a", "a"], "bab".match(/(?<$>a)/u))); +assert(compareArray(["bab", "a"], "bab".match(/.(?<$>a)./u))); +assert(compareArray(["bab", "a", "b"], "bab".match(/.(?<a>a)(.)/u))); +assert(compareArray(["bab", "a", "b"], "bab".match(/.(?<a>a)(?<b>.)/u))); +assert(compareArray(["bab", "ab"], "bab".match(/.(?<a>\w\w)/u))); +assert(compareArray(["bab", "bab"], "bab".match(/(?<a>\w\w\w)/u))); +assert(compareArray(["bab", "ba", "b"], "bab".match(/(?<a>\w\w)(?<b>\w)/u))); + +let {a, b, c} = /(?<a>.)(?<b>.)(?<c>.)\k<c>\k<b>\k<a>/u.exec("abccba").groups; +assert.sameValue(a, "a"); +assert.sameValue(b, "b"); +assert.sameValue(c, "c"); + +assert(compareArray("bab".match(/(a)/u), "bab".match(/(?<a>a)/u))); +assert(compareArray("bab".match(/(a)/u), "bab".match(/(?<a42>a)/u))); +assert(compareArray("bab".match(/(a)/u), "bab".match(/(?<_>a)/u))); +assert(compareArray("bab".match(/(a)/u), "bab".match(/(?<$>a)/u))); +assert(compareArray("bab".match(/.(a)./u), "bab".match(/.(?<$>a)./u))); +assert(compareArray("bab".match(/.(a)(.)/u), "bab".match(/.(?<a>a)(.)/u))); +assert(compareArray("bab".match(/.(a)(.)/u), "bab".match(/.(?<a>a)(?<b>.)/u))); +assert(compareArray("bab".match(/.(\w\w)/u), "bab".match(/.(?<a>\w\w)/u))); +assert(compareArray("bab".match(/(\w\w\w)/u), "bab".match(/(?<a>\w\w\w)/u))); +assert(compareArray("bab".match(/(\w\w)(\w)/u), "bab".match(/(?<a>\w\w)(?<b>\w)/u))); + +assert(compareArray(["bab", "b"], "bab".match(/(?<b>b).\1/u))); +assert(compareArray(["baba", "b", "a"], "baba".match(/(.)(?<a>a)\1\2/u))); +assert(compareArray(["baba", "b", "a", "b", "a"], + "baba".match(/(.)(?<a>a)(?<b>\1)(\2)/u))); +assert(compareArray(["<a", "<"], "<a".match(/(?<lt><)a/u))); +assert(compareArray([">a", ">"], ">a".match(/(?<gt>>)a/u))); + +// Nested groups. +assert(compareArray(["bab", "bab", "ab", "b"], "bab".match(/(?<a>.(?<b>.(?<c>.)))/u))); +assert(compareArray({a: "bab", b: "ab", c: "b"}, + "bab".match(/(?<a>.(?<b>.(?<c>.)))/u).groups)); diff --git a/test/built-ins/RegExp/named-groups/unicode-property-names.js b/test/built-ins/RegExp/named-groups/unicode-property-names.js new file mode 100644 index 0000000000000000000000000000000000000000..149322bb2d0f345c6e563b8505abadd7b7f6a77b --- /dev/null +++ b/test/built-ins/RegExp/named-groups/unicode-property-names.js @@ -0,0 +1,46 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Exotic named group names in Unicode RegExps +esid: pending +features: [regexp-named-groups] +---*/ + +assert.sameValue("a", /(?<Ï€>a)/u.exec("bab").groups.Ï€); +assert.sameValue("a", /(?<\u{03C0}>a)/u.exec("bab").groups.Ï€); +assert.sameValue("a", /(?<Ï€>a)/u.exec("bab").groups.\u03C0); +assert.sameValue("a", /(?<\u{03C0}>a)/u.exec("bab").groups.\u03C0); +assert.sameValue("a", /(?<$>a)/u.exec("bab").groups.$); +assert.sameValue("a", /(?<_>a)/u.exec("bab").groups._); +assert.sameValue("a", /(?<$ð’¤>a)/u.exec("bab").groups.$ð’¤); +assert.sameValue("a", /(?<_\u200C>a)/u.exec("bab").groups._\u200C); +assert.sameValue("a", /(?<_\u200D>a)/u.exec("bab").groups._\u200D); +assert.sameValue("a", /(?<ಠ_ಠ>a)/u.exec("bab").groups.ಠ_ಠ); +assert.throws(SyntaxError, () => eval('/(?<â¤>a)/u')); +assert.throws(SyntaxError, () => eval('/(?<ð’¤>a)/u'), "ID_Continue but not ID_Start."); + +// Unicode escapes in capture names. +assert(/(?<a\uD801\uDCA4>.)/u.test("a"), "\\u Lead \\u Trail"); +assert.throws(SyntaxError, () => eval("/(?<a\\uD801>.)/u"), "\\u Lea"); +assert.throws(SyntaxError, () => eval("/(?<a\\uDCA4>.)/u"), "\\u Trai"); +assert(/(?<\u0041>.)/u.test("a"), "\\u NonSurrogate"); +assert(/(?<\u{0041}>.)/u.test("a"), "\\u{ Non-surrogate }"); +assert(/(?<a\u{104A4}>.)/u.test("a"), "\\u{ Surrogate, ID_Continue }"); +assert.throws(SyntaxError, () => eval("/(?<a\\u{110000}>.)/u"), "\\u{ Out-of-bounds "); +assert.throws(SyntaxError, () => eval("/(?<a\uD801>.)/u"), "Lea"); +assert.throws(SyntaxError, () => eval("/(?<a\uDCA4>.)/u"), "Trai"); +assert(RegExp("(?<\u{0041}>.)", "u").test("a"), "Non-surrogate"); +assert(RegExp("(?<a\u{104A4}>.)", "u").test("a"), "Surrogate,ID_Continue"); + +// Bracketed escapes are not allowed; +// 4-char escapes must be the proper ID_Start/ID_Continue +assert.throws(SyntaxError, () => eval("/(?<a\\uD801>.)/u"), "Lead"); +assert.throws(SyntaxError, () => eval("/(?<a\\uDCA4>.)/u"), "Trail"); +assert((/(?<\u{0041}>.)/u).test("a"), "Non-surrogate"); +assert(/(?<a\u{104A4}>.)/u.test("a"), "Surrogate, ID_Continue"); +assert(RegExp("(?<\\u0041>.)", "u").test("a"), "Non-surrogate"); + +// Backslash is not allowed as ID_Start and ID_Continue +assert.throws(SyntaxError, () => eval("/(?<\\>.)/u"), "'\' misclassified as ID_Start"); +assert.throws(SyntaxError, () => eval("/(?<a\\>.)/u"), "'\' misclassified as ID_Continue"); diff --git a/test/built-ins/RegExp/named-groups/unicode-references.js b/test/built-ins/RegExp/named-groups/unicode-references.js new file mode 100644 index 0000000000000000000000000000000000000000..080bda0bebb25cf095102aed0a5b073f2fc04697 --- /dev/null +++ b/test/built-ins/RegExp/named-groups/unicode-references.js @@ -0,0 +1,47 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Named backreferences in Unicode RegExps +esid: sec-atomescape +info: > + The production AtomEscape :: [+N] k GroupName evaluates as follows: + + 1. Search the enclosing RegExp for an instance of a GroupSpecifier for an + RegExpIdentifierName which has a StringValue equal to the StringValue + of the RegExpIdentifierName contained in GroupName. + 2. Assert: A unique such GroupSpecifier is found. + 3. Let parenIndex be the number of left capturing parentheses in the entire + regular expression that occur to the left of the located GroupSpecifier. + This is the total number of times the Atom::(GroupSpecifierDisjunction) + production is expanded prior to that production's Term plus the total + number of Atom :: (GroupSpecifierDisjunction) productions enclosing this Term. + 4. Call BackreferenceMatcher(parenIndex) and return its Matcher result. +features: [regexp-named-groups] +includes: [compareArray.js] +---*/ + +// Named references. +assert(compareArray(["bab", "b"], "bab".match(/(?<b>.).\k<b>/u))); +assert.sameValue(null, "baa".match(/(?<b>.).\k<b>/u)); + +// Reference inside group. +assert(compareArray(["bab", "b"], "bab".match(/(?<a>\k<a>\w)../u))); +assert.sameValue("b", "bab".match(/(?<a>\k<a>\w)../u).groups.a); + +// Reference before group. +assert(compareArray(["bab", "b"], "bab".match(/\k<a>(?<a>b)\w\k<a>/u))); +assert.sameValue("b", "bab".match(/\k<a>(?<a>b)\w\k<a>/u).groups.a); +assert(compareArray(["bab", "b", "a"], "bab".match(/(?<b>b)\k<a>(?<a>a)\k<b>/u))); +let {a, b} = "bab".match(/(?<b>b)\k<a>(?<a>a)\k<b>/u).groups; +assert.sameValue(a, "a"); +assert.sameValue(b, "b"); + +assert(compareArray(["bab", "b"], "bab".match(/\k<a>(?<a>b)\w\k<a>/))); +assert(compareArray(["bab", "b", "a"], "bab".match(/(?<b>b)\k<a>(?<a>a)\k<b>/))); + +// Reference properties. +assert.sameValue("a", /(?<a>a)(?<b>b)\k<a>/u.exec("aba").groups.a); +assert.sameValue("b", /(?<a>a)(?<b>b)\k<a>/u.exec("aba").groups.b); +assert.sameValue(undefined, /(?<a>a)(?<b>b)\k<a>/u.exec("aba").groups.c); +assert.sameValue(undefined, /(?<a>a)(?<b>b)\k<a>|(?<c>c)/u.exec("aba").groups.c);