| description( |
| 'Test for of RegExp named capture groups' |
| ); |
| |
| // Verfiy that we can create group names and that we properly create the "groups" object, |
| // populated with the groups. |
| var re1 = new RegExp("(?<month>\\d{2})/(?<day>\\d{2})/(?<year>\\d{4})", ""); |
| var src1 = "01/02/2001"; |
| var execResult1 = re1.exec(src1); |
| shouldBe('re1.toString()', '"\\/(?<month>\\\\d{2})\\\\/(?<day>\\\\d{2})\\\\/(?<year>\\\\d{4})\\/"'); |
| shouldBe('execResult1[0]', '"01/02/2001"'); |
| shouldBe('execResult1.groups.month', '"01"'); |
| shouldBe('execResult1.groups.day', '"02"'); |
| shouldBe('execResult1.groups.year', '"2001"'); |
| shouldBe('Object.getOwnPropertyNames(execResult1).sort()', '["0","1","2","3","groups","index","input","length"]'); |
| shouldBe('Object.getOwnPropertyNames(execResult1.groups).sort()', '["day","month","year"]'); |
| |
| var matchResult1 = src1.match(new RegExp(re1, 'd')); |
| shouldBe('matchResult1[0]', '"01/02/2001"'); |
| shouldBe('matchResult1.groups.month', '"01"'); |
| shouldBe('matchResult1.groups.day', '"02"'); |
| shouldBe('matchResult1.groups.year', '"2001"'); |
| shouldBe('matchResult1.indices.groups.month', '[0,2]'); |
| shouldBe('matchResult1.indices.groups.day', '[3,5]'); |
| shouldBe('matchResult1.indices.groups.year', '[6,10]'); |
| shouldBe('Object.getOwnPropertyNames(matchResult1).sort()', '["0","1","2","3","groups","index","indices","input","length"]'); |
| shouldBe('Object.getOwnPropertyNames(matchResult1.groups).sort()', '["day","month","year"]'); |
| shouldBe('Object.getOwnPropertyNames(matchResult1.indices.groups).sort()', '["day","month","year"]'); |
| |
| var re2 = /(?<first_name>\w+)\s(?:(?<middle_initial>\w\.)\s)?(?<last_name>\w+)/d; |
| var matchResult2a = "John W. Smith".match(re2); |
| |
| shouldBe('matchResult2a[0]', '"John W. Smith"'); |
| shouldBe('matchResult2a[1]', '"John"'); |
| shouldBe('matchResult2a[2]', '"W."'); |
| shouldBe('matchResult2a[3]', '"Smith"'); |
| shouldBe('matchResult2a.indices[1]', '[0,4]'); |
| shouldBe('matchResult2a.indices[2]', '[5,7]'); |
| shouldBe('matchResult2a.indices[3]', '[8,13]'); |
| shouldBe('matchResult2a[1]', 'matchResult2a.groups.first_name'); |
| shouldBe('matchResult2a[2]', 'matchResult2a.groups.middle_initial'); |
| shouldBe('matchResult2a[3]', 'matchResult2a.groups.last_name'); |
| shouldBe('Object.getOwnPropertyNames(matchResult1).sort()', '["0","1","2","3","groups","index","indices","input","length"]'); |
| |
| // Verify that named groups that aren't matched are undefined. |
| var matchResult2b = "Sally Brown".match(re2); |
| |
| shouldBe('matchResult2b[0]', '"Sally Brown"'); |
| shouldBe('matchResult2b[1]', '"Sally"'); |
| shouldBeUndefined('matchResult2b[2]'); |
| shouldBe('matchResult2b[3]', '"Brown"'); |
| shouldBe('matchResult2b.indices[1]', '[0,5]'); |
| shouldBeUndefined('matchResult2b.indices[2]'); |
| shouldBe('matchResult2b.indices[3]', '[6,11]'); |
| shouldBe('matchResult2b[1]', 'matchResult2b.groups.first_name'); |
| shouldBe('matchResult2b[2]', 'matchResult2b.groups.middle_initial'); |
| shouldBe('matchResult2b[3]', 'matchResult2b.groups.last_name'); |
| shouldBe('Object.getOwnPropertyNames(matchResult1).sort()', '["0","1","2","3","groups","index","indices","input","length"]'); |
| |
| // Verify that named backreferences work. |
| var re3 = /^(?<part1>.*):(?<part2>.*):\k<part2>:\k<part1>$/; |
| shouldBe('re3.toString()', '"\\/^(?<part1>.*):(?<part2>.*):\\\\k<part2>:\\\\k<part1>$\\/"'); |
| shouldBeTrue('re3.test("a:b:b:a")'); |
| shouldBeTrue('re3.test("a:a:a:a")'); |
| shouldBeFalse('re3.test("a:b:c:a")'); |
| |
| // Destructuring should work nicely with named groups. |
| var {groups: {first, second}} = /^(?<first>.*),(?<second>.*)$/u.exec('1,2'); |
| shouldBe('first', '"1"'); |
| shouldBe('second', '"2"'); |
| |
| // Check that unicode group names work. |
| let re4 = /(?<\u043c\u0435\u0441\u044f\u0446>\d{2})\/(?<\u0434\u0435\u043d\u044c>\d{2})\/(?<\u0433\u043e\u0434>\d{4})/; |
| var result4 = '02/14/2010'.replace(re4, (...args) => { |
| let {\u0434\u0435\u043d\u044c, \u043c\u0435\u0441\u044f\u0446, \u0433\u043e\u0434} = args[args.length - 1]; |
| return `${\u0434\u0435\u043d\u044c}.${\u043c\u0435\u0441\u044f\u0446}.${\u0433\u043e\u0434}`; |
| }); |
| shouldBe('result4', '"14.02.2010"'); |
| |
| // Verify that zero-width joiner and non-joiners can be used as part of a group name identifier |
| shouldBe('"third edition".match(/(?<auf\\u200clage>\\w+) edition/).groups.auf\\u200clage', '"third"'); |
| shouldBe('"fourth edition".match(/(?<auf\\u200dlage>\\w+) edition/).groups.auf\\u200dlage', '"fourth"'); |
| |
| // Verify that both named and numeric group references work in a replacement string. |
| shouldBe('"10/20/1930".replace(/(?<month>\\d{2})\\\/(?<day>\\d{2})\\\/(?<year>\\d{4})/, "$<day>-$<month>-$<year>")', '"20-10-1930"'); |
| shouldBe('"10/20/1930".replace(/(?<month>\\d{2})\\\/(?<day>\\d{2})\\\/(?<year>\\d{4})/, "$2-$<month>-$<year>")', '"20-10-1930"'); |
| shouldBe('"10/20/1930".replace(/(?<month>\\d{2})\\\/(?<day>\\d{2})\\\/(?<year>\\d{4})/, "$<day>-$1-$<year>")', '"20-10-1930"'); |
| shouldBe('"10/20/1930".replace(/(?<month>\\d{2})\\\/(?<day>\\d{2})\\\/(?<year>\\d{4})/, "$<day>-$<month>-$3")', '"20-10-1930"'); |
| |
| // Verify String.replace works correctly without named captures in the RegExp |
| shouldBe('"Replace just THIS in this string".replace(/THIS/, "$<THAT>")', '"Replace just $<THAT> in this string"'); |
| |
| // Verify that named back references for non-existing named group matches the k<groupName> for non-unicode patterns. |
| shouldBe('"Give me a \\\'k\\\'!".match(/Give me a \\\'\\\k\\\'/)[0]', '"Give me a \\\'k\\\'"'); |
| shouldBe('"Give me \\\'k2\\\'!".match(/Give me \\\'\\\k2\\\'/)[0]', '"Give me \\\'k2\\\'"'); |
| shouldBe('"Give me a \\\'kat\\\'!".match(/Give me a \\\'\\\kat\\\'/)[0]', '"Give me a \\\'kat\\\'"'); |
| // Verify that named back references for non-existing named group matches the k<groupName> throw for unicode patterns. |
| shouldThrow('"Give me a \\\'k\\\'!".match(/Give me a \\\'\\\k\\\'/u)[0]', '"SyntaxError: Invalid regular expression: invalid escaped character for Unicode pattern"'); |
| shouldThrow('"Give me \\\'k2\\\'!".match(/Give me \\\'\\\k2\\\'/u)[0]', '"SyntaxError: Invalid regular expression: invalid escaped character for Unicode pattern"'); |
| shouldThrow('"Give me a \\\'kat\\\'!".match(/Give me a \\\'\\\kat\\\'/u)[0]', '"SyntaxError: Invalid regular expression: invalid escaped character for Unicode pattern"'); |
| |
| // Check invalid group name specifiers in a replace string. |
| shouldBe('"10/20/1930".replace(/(?<month>\\d{2})\\\/(?<day>\\d{2})\\\/(?<year>\\d{4})/, "$<day>-$<mouth>-$<year>")', '"20--1930"'); |
| shouldBe('"10/20/1930".replace(/(?<month>\\d{2})\\\/(?<day>\\d{2})\\\/(?<year>\\d{4})/, "$<day>-$<month>-$<year")', '"20-10-$<year"'); |
| |
| // Check invalid group name exceptions. |
| shouldThrow('let r = new RegExp("/(?<groupName1>abc)|(?<groupName1>def)/")', '"SyntaxError: Invalid regular expression: duplicate group specifier name"'); |
| shouldThrow('let r = new RegExp("/(?< groupName1>abc)/")', '"SyntaxError: Invalid regular expression: invalid group specifier name"'); |
| shouldThrow('let r = new RegExp("/(?<g=oupName1>abc)/")', '"SyntaxError: Invalid regular expression: invalid group specifier name"'); |
| |
| // And bad Unicode ID start and ID part |
| shouldThrow('let r = new RegExp("/(?<\u{10190}groupName1>abc)/u")', '"SyntaxError: Invalid regular expression: invalid group specifier name"'); |
| shouldThrow('let r = new RegExp("/(?<g\u{1019b}oupName1>abc)/u")', '"SyntaxError: Invalid regular expression: invalid group specifier name"'); |
| shouldThrow('let r = new RegExp("/(?<\u200cgroupName1>abc)/u")', '"SyntaxError: Invalid regular expression: invalid group specifier name"'); |
| shouldThrow('let r = new RegExp("/(?<\u200dgroupName1>abc)/u")', '"SyntaxError: Invalid regular expression: invalid group specifier name"'); |
| |
| // Check that invalid \u escape errors are not get overriden. |
| shouldThrow('/(?<\\u>.)/u', '"SyntaxError: Invalid regular expression: invalid Unicode \\\\u escape"'); |
| shouldThrow('/\\k<\\uzzz>/u', '"SyntaxError: Invalid regular expression: invalid Unicode \\\\u escape"'); |
| shouldThrow('/(?<\\u{>.)/u', '"SyntaxError: Invalid regular expression: invalid Unicode code point \\\\u{} escape"'); |
| shouldThrow('/\\k<\\u{0>/u', '"SyntaxError: Invalid regular expression: invalid Unicode code point \\\\u{} escape"'); |
| |
| // Check the named forward references work |
| shouldBe('"XzzXzz".match(/\\\k<z>X(?<z>z*)X\\\k<z>/)', '["XzzXzz", "zz"]'); |
| shouldBe('"XzzXzz".match(/\\\k<z>X(?<z>z*)X\\\k<z>/u)', '["XzzXzz", "zz"]'); |
| shouldBe('"1122332211".match(/\\\k<ones>\\\k<twos>\\\k<threes>(?<ones>1*)(?<twos>2*)(?<threes>3*)\\\k<threes>\\\k<twos>\\\k<ones>/)', '["1122332211", "11", "22", "3"]'); |
| shouldBe('"1122332211".match(/\\\k<ones>\\\k<twos>\\\k<threes>(?<ones>1*)(?<twos>2*)(?<threes>3*)\\\k<threes>\\\k<twos>\\\k<ones>/u)', '["1122332211", "11", "22", "3"]'); |
| |
| // Check that a named forward reference for a non-existent named capture |
| // matches for non-Unicode patterns w/o a named group and throws for patterns with a named group or Unicode flag. |
| shouldBe('"\\\k<z>XzzX".match(/\\\k<z>X(z*)X/)', '["k<z>XzzX", "zz"]'); |
| shouldThrow('"\\\k<z>XzzX".match(/\\\k<z>X(z*)X/u)', '"SyntaxError: Invalid regular expression: invalid \\\\k<> named backreference"'); |
| shouldThrow('/\\\k<xxx(?<a>y)(/', '"SyntaxError: Invalid regular expression: invalid \\\\k<> named backreference"'); |