Skip to content

Commit 1a510ac

Browse files
committed
add basic NCG support
1 parent 0efc25a commit 1a510ac

File tree

5 files changed

+131
-39
lines changed

5 files changed

+131
-39
lines changed

packages/core-js/internals/regexp-exec.js

+19-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
'use strict';
22
/* eslint-disable regexp/no-assertion-capturing-group, regexp/no-empty-group, regexp/no-lazy-ends -- testing */
33
/* eslint-disable regexp/no-useless-quantifier -- testing */
4-
var regexpFlags = require('./regexp-flags');
5-
var stickyHelpers = require('./regexp-sticky-helpers');
6-
var shared = require('./shared');
4+
var regexpFlags = require('../internals/regexp-flags');
5+
var stickyHelpers = require('../internals/regexp-sticky-helpers');
6+
var shared = require('../internals/shared');
7+
var create = require('../internals/object-create');
78
var getInternalState = require('../internals/internal-state').get;
89
var UNSUPPORTED_DOT_ALL = require('../internals/regexp-unsupported-dot-all');
10+
var UNSUPPORTED_NCG = require('../internals/regexp-unsupported-ncg');
911

1012
var nativeExec = RegExp.prototype.exec;
1113
var nativeReplace = shared('native-string-replace', String.prototype.replace);
@@ -25,13 +27,15 @@ var UNSUPPORTED_Y = stickyHelpers.UNSUPPORTED_Y || stickyHelpers.BROKEN_CARET;
2527
// nonparticipating capturing group, copied from es5-shim's String#split patch.
2628
var NPCG_INCLUDED = /()??/.exec('')[1] !== undefined;
2729

28-
var PATCH = UPDATES_LAST_INDEX_WRONG || NPCG_INCLUDED || UNSUPPORTED_Y || UNSUPPORTED_DOT_ALL;
30+
var PATCH = UPDATES_LAST_INDEX_WRONG || NPCG_INCLUDED || UNSUPPORTED_Y || UNSUPPORTED_DOT_ALL || UNSUPPORTED_NCG;
2931

3032
if (PATCH) {
33+
// eslint-disable-next-line max-statements -- TODO
3134
patchedExec = function exec(str) {
3235
var re = this;
33-
var lastIndex, reCopy, match, i, result;
34-
var raw = UNSUPPORTED_DOT_ALL && getInternalState(re).raw;
36+
var lastIndex, reCopy, match, i, result, object, group;
37+
var state = getInternalState(re);
38+
var raw = state.raw;
3539

3640
if (raw) {
3741
raw.lastIndex = re.lastIndex;
@@ -40,6 +44,7 @@ if (PATCH) {
4044
return result;
4145
}
4246

47+
var groups = state.groups;
4348
var sticky = UNSUPPORTED_Y && re.sticky;
4449
var flags = regexpFlags.call(re);
4550
var source = re.source;
@@ -91,6 +96,14 @@ if (PATCH) {
9196
});
9297
}
9398

99+
if (match && groups) {
100+
match.groups = object = create(null);
101+
for (i = 0; i < groups.length; i++) {
102+
group = groups[i];
103+
object[group[0]] = match[group[1]];
104+
}
105+
}
106+
94107
return match;
95108
};
96109
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
var fails = require('./fails');
2+
3+
module.exports = fails(function () {
4+
// babel-minify transpiles RegExp('.', 'g') -> /./g and it causes SyntaxError
5+
return RegExp('(?<a>b)', (typeof '').charAt(5)).exec('b').groups.a !== 'b';
6+
});

packages/core-js/modules/es.regexp.constructor.js

+77-11
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@ var DESCRIPTORS = require('../internals/descriptors');
22
var global = require('../internals/global');
33
var isForced = require('../internals/is-forced');
44
var inheritIfRequired = require('../internals/inherit-if-required');
5+
var createNonEnumerableProperty = require('../internals/create-non-enumerable-property');
56
var defineProperty = require('../internals/object-define-property').f;
67
var getOwnPropertyNames = require('../internals/object-get-own-property-names').f;
78
var isRegExp = require('../internals/is-regexp');
89
var getFlags = require('../internals/regexp-flags');
910
var stickyHelpers = require('../internals/regexp-sticky-helpers');
1011
var redefine = require('../internals/redefine');
1112
var fails = require('../internals/fails');
13+
var has = require('../internals/has');
1214
var enforceInternalState = require('../internals/internal-state').enforce;
1315
var setSpecies = require('../internals/set-species');
1416
var wellKnownSymbol = require('../internals/well-known-symbol');
1517
var UNSUPPORTED_DOT_ALL = require('../internals/regexp-unsupported-dot-all');
18+
var UNSUPPORTED_NCG = require('../internals/regexp-unsupported-ncg');
1619

1720
var MATCH = wellKnownSymbol('match');
1821
var NativeRegExp = global.RegExp;
@@ -25,16 +28,17 @@ var CORRECT_NEW = new NativeRegExp(re1) !== re1;
2528

2629
var UNSUPPORTED_Y = stickyHelpers.UNSUPPORTED_Y;
2730

28-
var BASE_FORCED = DESCRIPTORS && !CORRECT_NEW || UNSUPPORTED_Y || UNSUPPORTED_DOT_ALL || fails(function () {
29-
re2[MATCH] = false;
30-
// RegExp constructor can alter flags and IsRegExp works correct with @@match
31-
return NativeRegExp(re1) != re1 || NativeRegExp(re2) == re2 || NativeRegExp(re1, 'i') != '/a/i';
32-
});
31+
var BASE_FORCED = DESCRIPTORS &&
32+
(!CORRECT_NEW || UNSUPPORTED_Y || UNSUPPORTED_DOT_ALL || UNSUPPORTED_NCG || fails(function () {
33+
re2[MATCH] = false;
34+
// RegExp constructor can alter flags and IsRegExp works correct with @@match
35+
return NativeRegExp(re1) != re1 || NativeRegExp(re2) == re2 || NativeRegExp(re1, 'i') != '/a/i';
36+
}));
3337

34-
var deDotAll = function (string) {
35-
var result = '';
36-
var index = 0;
38+
var handleDotAll = function (string) {
3739
var length = string.length;
40+
var index = 0;
41+
var result = '';
3842
var brackets = false;
3943
var chr;
4044
for (; index <= length; index++) {
@@ -55,14 +59,60 @@ var deDotAll = function (string) {
5559
} return result;
5660
};
5761

62+
var handleNCG = function (string) {
63+
var length = string.length;
64+
var index = 0;
65+
var result = '';
66+
var named = [];
67+
var names = {};
68+
var brackets = false;
69+
var ncg = false;
70+
var groupid = 0;
71+
var groupname = '';
72+
var chr;
73+
for (; index <= length; index++) {
74+
chr = string.charAt(index);
75+
if (chr === '\\') {
76+
chr = chr + string.charAt(++index);
77+
} else if (chr === ']') {
78+
brackets = false;
79+
} else if (!brackets) switch (true) {
80+
case chr === '[':
81+
brackets = true;
82+
break;
83+
case chr === '(':
84+
// TODO: Use only propper RegExpIdentifierName
85+
if (/\?<[^!#%&*+=@^]/.test(string.slice(index + 1, index + 4))) {
86+
index += 2;
87+
ncg = true;
88+
}
89+
result += chr;
90+
groupid++;
91+
continue;
92+
case chr === '>' && ncg:
93+
if (groupname === '' || has(names, groupname)) {
94+
throw new TypeError('Invalid capture group name');
95+
}
96+
names[groupname] = true;
97+
named.push([groupname, groupid]);
98+
ncg = false;
99+
groupname = '';
100+
continue;
101+
}
102+
if (ncg) groupname += chr;
103+
else result += chr;
104+
} return [result, named];
105+
};
106+
58107
// `RegExp` constructor
59108
// https://tc39.es/ecma262/#sec-regexp-constructor
60109
if (isForced('RegExp', BASE_FORCED)) {
61110
var RegExpWrapper = function RegExp(pattern, flags) {
62111
var thisIsRegExp = this instanceof RegExpWrapper;
63112
var patternIsRegExp = isRegExp(pattern);
64113
var flagsAreUndefined = flags === undefined;
65-
var rawFlags, dotAll, sticky, result, state;
114+
var groups = [];
115+
var rawPattern, rawFlags, dotAll, sticky, handled, result, state;
66116

67117
if (!thisIsRegExp && patternIsRegExp && pattern.constructor === RegExpWrapper && flagsAreUndefined) {
68118
return pattern;
@@ -75,6 +125,10 @@ if (isForced('RegExp', BASE_FORCED)) {
75125
pattern = pattern.source;
76126
}
77127

128+
pattern = pattern === undefined ? '' : String(pattern);
129+
if (pattern === '') pattern = '(?:)';
130+
rawPattern = pattern;
131+
78132
if (UNSUPPORTED_DOT_ALL) {
79133
dotAll = !!flags && flags.indexOf('s') > -1;
80134
if (dotAll) flags = flags.replace(/s/g, '');
@@ -87,21 +141,33 @@ if (isForced('RegExp', BASE_FORCED)) {
87141
if (sticky) flags = flags.replace(/y/g, '');
88142
}
89143

144+
if (UNSUPPORTED_NCG) {
145+
handled = handleNCG(pattern);
146+
pattern = handled[0];
147+
groups = handled[1];
148+
}
149+
90150
result = inheritIfRequired(
91151
CORRECT_NEW ? new NativeRegExp(pattern, flags) : NativeRegExp(pattern, flags),
92152
thisIsRegExp ? this : RegExpPrototype,
93153
RegExpWrapper
94154
);
95155

96-
if (dotAll || sticky) {
156+
if (dotAll || sticky || groups.length) {
97157
state = enforceInternalState(result);
98158
if (dotAll) {
99159
state.dotAll = true;
100-
state.raw = RegExpWrapper(deDotAll(pattern), rawFlags);
160+
state.raw = RegExpWrapper(handleDotAll(pattern), rawFlags);
101161
}
102162
if (sticky) state.sticky = true;
163+
if (groups.length) state.groups = groups;
103164
}
104165

166+
if (pattern !== rawPattern) try {
167+
// fails in old engines, but we have no alternatives for unsupported regex syntax
168+
createNonEnumerableProperty(result, 'source', rawPattern);
169+
} catch (error) { /* empty */ }
170+
105171
return result;
106172
};
107173

tests/tests/es.regexp.constructor.js

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable regexp/order-in-character-class, regexp/no-dupe-characters-character-class -- required for testing */
2-
/* eslint-disable regexp/no-useless-flag, regexp/sort-flags -- required for testing */
2+
/* eslint-disable regexp/no-useless-character-class, regexp/no-useless-flag, regexp/sort-flags -- required for testing */
33
import { DESCRIPTORS, GLOBAL } from '../helpers/constants';
44
import { nativeSubclass } from '../helpers/helpers';
55

@@ -43,4 +43,31 @@ if (DESCRIPTORS) {
4343
assert.ok(new Subclass('^abc$').test('abc'), 'correct subclassing with native classes #3');
4444
}
4545
});
46+
47+
QUnit.test('RegExp dotAll', assert => {
48+
assert.same(RegExp('.', '').test('\n'), false, 'dotAll missed');
49+
assert.same(RegExp('.', 's').test('\n'), true, 'dotAll basic');
50+
assert.same(RegExp('[.]', 's').test('\n'), false, 'dotAll brackets #1');
51+
assert.same(RegExp('[.].', '').test('.\n'), false, 'dotAll brackets #2');
52+
assert.same(RegExp('[.].', 's').test('.\n'), true, 'dotAll brackets #3');
53+
assert.same(RegExp('[[].', 's').test('[\n'), true, 'dotAll brackets #4');
54+
assert.same(RegExp('.[.[].\\..', 's').source, '.[.[].\\..', 'dotAll correct source');
55+
56+
const string = '123\n456789\n012';
57+
const re = RegExp('(\\d{3}).\\d{3}', 'sy');
58+
59+
let match = re.exec(string);
60+
assert.same(match[1], '123', 's with y #1');
61+
assert.same(re.lastIndex, 7, 's with y #2');
62+
63+
match = re.exec(string);
64+
assert.same(match[1], '789', 's with y #3');
65+
assert.same(re.lastIndex, 14, 's with y #4');
66+
});
67+
68+
QUnit.test('RegExp NCG', assert => {
69+
assert.same(RegExp('(?<a>b)', (typeof '').charAt(5)).exec('b').groups?.a, 'b', 'NCG #1');
70+
// eslint-disable-next-line regexp/no-unused-capturing-group -- required for testing
71+
assert.same(RegExp('(b)', (typeof '').charAt(5)).exec('b').groups, undefined, 'NCG #2');
72+
});
4673
}

tests/tests/es.regexp.dot-all.js

+1-21
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,13 @@
1-
/* eslint-disable regexp/no-useless-character-class, regexp/no-useless-flag -- required for testing */
21
import { DESCRIPTORS } from '../helpers/constants';
32

43
if (DESCRIPTORS) {
54
QUnit.test('RegExp#dotAll', assert => {
6-
let re = RegExp('.', 's');
5+
const re = RegExp('.', 's');
76
assert.same(re.dotAll, true, '.dotAll is true');
87
assert.same(re.flags, 's', '.flags contains s');
98
assert.same(RegExp('.').dotAll, false, 'no');
109
assert.same(/a/.dotAll, false, 'no in literal');
1110

12-
assert.same(RegExp('.', '').test('\n'), false, 'dotAll missed');
13-
assert.same(RegExp('.', 's').test('\n'), true, 'dotAll basic');
14-
assert.same(RegExp('[.]', 's').test('\n'), false, 'dotAll brackets #1');
15-
assert.same(RegExp('[.].', '').test('.\n'), false, 'dotAll brackets #2');
16-
assert.same(RegExp('[.].', 's').test('.\n'), true, 'dotAll brackets #3');
17-
assert.same(RegExp('[[].', 's').test('[\n'), true, 'dotAll brackets #4');
18-
assert.same(RegExp('.[.[].\\..', 's').source, '.[.[].\\..', 'dotAll correct source');
19-
20-
const string = '123\n456789\n012';
21-
re = RegExp('(\\d{3}).\\d{3}', 'sy');
22-
23-
let match = re.exec(string);
24-
assert.same(match[1], '123', 's with y #1');
25-
assert.same(re.lastIndex, 7, 's with y #2');
26-
27-
match = re.exec(string);
28-
assert.same(match[1], '789', 's with y #3');
29-
assert.same(re.lastIndex, 14, 's with y #4');
30-
3111
const dotAllGetter = Object.getOwnPropertyDescriptor(RegExp.prototype, 'dotAll').get;
3212
if (typeof dotAllGetter === 'function') {
3313
assert.throws(() => {

0 commit comments

Comments
 (0)