Skip to content

Commit 8a90bf6

Browse files
authored
Merge pull request #453 from nicolo-ribaudo/fix-regexs-on-old-browsers
Fix RegExp methods in old browsers
2 parents be1206b + d3fa95e commit 8a90bf6

15 files changed

+355
-217
lines changed

packages/core-js-builder/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ module.exports = {
9595
'es.string.sub',
9696
'es.string.sup',
9797
'es.regexp.constructor',
98+
'es.regexp.exec',
9899
'es.regexp.flags',
99100
'es.regexp.to-string',
100101
'es.parse-int',

packages/core-js/es/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ require('../modules/es.string.strike');
8080
require('../modules/es.string.sub');
8181
require('../modules/es.string.sup');
8282
require('../modules/es.regexp.constructor');
83+
require('../modules/es.regexp.exec');
8384
require('../modules/es.regexp.flags');
8485
require('../modules/es.regexp.to-string');
8586
require('../modules/es.parse-int');

packages/core-js/es/regexp/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require('../../modules/es.regexp.constructor');
22
require('../../modules/es.regexp.to-string');
3+
require('../../modules/es.regexp.exec');
34
require('../../modules/es.regexp.flags');
45
require('../../modules/es.string.match');
56
require('../../modules/es.string.replace');

packages/core-js/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ require('./modules/es.string.strike');
9292
require('./modules/es.string.sub');
9393
require('./modules/es.string.sup');
9494
require('./modules/es.regexp.constructor');
95+
require('./modules/es.regexp.exec');
9596
require('./modules/es.regexp.flags');
9697
require('./modules/es.regexp.to-string');
9798
require('./modules/es.parse-int');

packages/core-js/internals/fix-regexp-well-known-symbol-logic.js

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@ var redefine = require('../internals/redefine');
44
var fails = require('../internals/fails');
55
var requireObjectCoercible = require('../internals/require-object-coercible');
66
var wellKnownSymbol = require('../internals/well-known-symbol');
7+
var regexpExec = require('../internals/regexp-exec');
78

89
var SPECIES = wellKnownSymbol('species');
910

1011
module.exports = function (KEY, length, exec, sham) {
1112
var SYMBOL = wellKnownSymbol(KEY);
1213

13-
var delegates = !fails(function () {
14+
var delegatesToSymbol = !fails(function () {
1415
// String methods call symbol-named RegEp methods
1516
var O = {};
1617
O[SYMBOL] = function () { return 7; };
1718
return ''[KEY](O) != 7;
18-
}) && !fails(function () {
19+
});
20+
21+
var delegatesToExec = delegatesToSymbol ? !fails(function () {
1922
// Symbol-named RegExp methods call .exec
2023
var execCalled = false;
2124
var re = /a/;
@@ -30,7 +33,7 @@ module.exports = function (KEY, length, exec, sham) {
3033

3134
re[SYMBOL]('');
3235
return !execCalled;
33-
});
36+
}) : undefined;
3437

3538
var replaceSupportsNamedGroups = KEY === 'replace' && !fails(function () {
3639
// #replace needs built-in support for named groups.
@@ -45,11 +48,39 @@ module.exports = function (KEY, length, exec, sham) {
4548
return ''.replace(re, '$<a>') !== '7';
4649
});
4750

48-
if (!delegates || (KEY === 'replace' && !replaceSupportsNamedGroups)) {
49-
var methods = exec(requireObjectCoercible, SYMBOL, ''[KEY], /./[SYMBOL], {
50-
delegates: delegates,
51-
replaceSupportsNamedGroups: replaceSupportsNamedGroups
52-
});
51+
var splitWorksWithOverwrittenExec = KEY === 'split' && (function () {
52+
// Chrome 51 has a buggy "split" implementation when RegExp#exec !== nativeExec
53+
var re = /(?:)/;
54+
var originalExec = re.exec;
55+
re.exec = function () { return originalExec.apply(this, arguments); };
56+
var result = 'ab'.split(re);
57+
return result.length === 2 && result[0] === 'a' && result[1] === 'b';
58+
})();
59+
60+
if (
61+
!delegatesToSymbol ||
62+
!delegatesToExec ||
63+
(KEY === 'replace' && !replaceSupportsNamedGroups) ||
64+
(KEY === 'split' && !splitWorksWithOverwrittenExec)
65+
) {
66+
var nativeRegExpMethod = /./[SYMBOL];
67+
var methods = exec(
68+
requireObjectCoercible,
69+
SYMBOL,
70+
''[KEY],
71+
function maybeCallNative(nativeMethod, regexp, str, arg2, forceStringMethod) {
72+
if (regexp.exec === regexpExec.impl) {
73+
if (delegatesToSymbol && !forceStringMethod) {
74+
// The native String method already delegates to @@method (this
75+
// polyfilled function), leasing to infinite recursion.
76+
// We avoid it by directly calling the native @@method method.
77+
return { done: true, value: nativeRegExpMethod.call(regexp, str, arg2) };
78+
}
79+
return { done: true, value: nativeMethod.call(str, regexp, arg2) };
80+
}
81+
return { done: false };
82+
}
83+
);
5384
var stringMethod = methods[0];
5485
var regexMethod = methods[1];
5586

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
var classof = require('./classof-raw');
2+
var regexpExec = require('./regexp-exec');
3+
4+
// `RegExpExec` abstract operation
5+
// https://tc39.github.io/ecma262/#sec-regexpexec
6+
module.exports = function (R, S) {
7+
var exec = R.exec;
8+
if (typeof exec === 'function') {
9+
var result = exec.call(R, S);
10+
if (typeof result !== 'object') {
11+
throw new TypeError('RegExp exec method returned something other than an Object or null');
12+
}
13+
return result;
14+
}
15+
16+
if (classof(R) !== 'RegExp') {
17+
throw new TypeError('RegExp#exec called on incompatible receiver');
18+
}
19+
20+
return regexpExec.impl.call(R, S);
21+
};
22+
Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,63 @@
1-
var classof = require('../internals/classof-raw');
2-
var builtinExec = RegExp.prototype.exec;
3-
4-
// `RegExpExec` abstract operation
5-
// https://tc39.github.io/ecma262/#sec-regexpexec
6-
module.exports = function (R, S) {
7-
var exec = R.exec;
8-
if (typeof exec === 'function') {
9-
var result = exec.call(R, S);
10-
if (typeof result !== 'object') {
11-
throw new TypeError('RegExp exec method returned something other than an Object or null');
1+
'use strict';
2+
3+
var regexpFlags = require('./regexp-flags');
4+
5+
var nativeExec = RegExp.prototype.exec;
6+
// This always refers to the native implementation, because the
7+
// String#replace polyfill uses ./fix-regexp-well-known-symbol-logic.js,
8+
// which loads this file before patching the method.
9+
var nativeReplace = String.prototype.replace;
10+
11+
var patchedExec = nativeExec;
12+
13+
var LAST_INDEX = 'lastIndex';
14+
var LENGTH = 'length';
15+
16+
var UPDATES_LAST_INDEX_WRONG = (function () {
17+
var re1 = /a/,
18+
re2 = /b*/g;
19+
nativeExec.call(re1, 'a');
20+
nativeExec.call(re2, 'a');
21+
return re1[LAST_INDEX] !== 0 || re2[LAST_INDEX] !== 0;
22+
})();
23+
24+
// nonparticipating capturing group, copied from es5-shim's String#split patch.
25+
var NPCG_INCLUDED = /()??/.exec('')[1] !== undefined;
26+
27+
var patch = UPDATES_LAST_INDEX_WRONG || NPCG_INCLUDED;
28+
29+
if (patch) {
30+
patchedExec = function exec(str) {
31+
var re = this;
32+
var lastIndex, reCopy, match, i;
33+
34+
if (NPCG_INCLUDED) {
35+
reCopy = new RegExp('^' + re.source + '$(?!\\s)', regexpFlags.call(re));
1236
}
13-
return result;
14-
}
37+
if (UPDATES_LAST_INDEX_WRONG) lastIndex = re[LAST_INDEX];
1538

16-
if (classof(R) !== 'RegExp') {
17-
throw new TypeError('RegExp#exec called on incompatible receiver');
18-
}
39+
match = nativeExec.call(re, str);
1940

20-
return builtinExec.call(R, S);
21-
};
41+
if (UPDATES_LAST_INDEX_WRONG && match) {
42+
re[LAST_INDEX] = re.global ? match.index + match[0][LENGTH] : lastIndex;
43+
}
44+
if (NPCG_INCLUDED && match && match[LENGTH] > 1) {
45+
// Fix browsers whose `exec` methods don't consistently return `undefined`
46+
// for NPCG, like IE8. NOTE: This doesn' work for /(.?)?/
47+
// eslint-disable-next-line no-loop-func
48+
nativeReplace.call(match[0], reCopy, function () {
49+
for (i = 1; i < arguments[LENGTH] - 2; i++) {
50+
if (arguments[i] === undefined) match[i] = undefined;
51+
}
52+
});
53+
}
54+
55+
return match;
56+
};
57+
}
2258

59+
module.exports = {
60+
orig: nativeExec,
61+
impl: patchedExec,
62+
patched: patch
63+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict';
2+
3+
var regexpExec = require('../internals/regexp-exec');
4+
5+
require('../internals/export')({
6+
target: 'RegExp',
7+
proto: true,
8+
forced: regexpExec.patched
9+
}, {
10+
exec: regexpExec.impl
11+
});

packages/core-js/modules/es.string.match.js

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,45 @@
33
var anObject = require('../internals/an-object');
44
var toLength = require('../internals/to-length');
55
var advanceStringIndex = require('../internals/advance-string-index');
6-
var regExpExec = require('../internals/regexp-exec');
7-
var nativeExec = RegExp.prototype.exec;
6+
var regExpExec = require('../internals/regexp-exec-abstract');
87

98
// @@match logic
10-
require('../internals/fix-regexp-well-known-symbol-logic')('match', 1, function (defined, MATCH, nativeMatch) {
11-
return [
12-
// `String.prototype.match` method
13-
// https://tc39.github.io/ecma262/#sec-string.prototype.match
14-
function match(regexp) {
15-
var O = defined(this);
16-
var matcher = regexp == undefined ? undefined : regexp[MATCH];
17-
return matcher !== undefined ? matcher.call(regexp, O) : new RegExp(regexp)[MATCH](String(O));
18-
},
19-
// `RegExp.prototype[@@match]` method
20-
// https://tc39.github.io/ecma262/#sec-regexp.prototype-@@match
21-
function (regexp) {
22-
if (regexp.exec === nativeExec) return nativeMatch.call(this, regexp);
9+
require('../internals/fix-regexp-well-known-symbol-logic')(
10+
'match',
11+
1,
12+
function (defined, MATCH, nativeMatch, maybeCallNative) {
13+
return [
14+
// `String.prototype.match` method
15+
// https://tc39.github.io/ecma262/#sec-string.prototype.match
16+
function match(regexp) {
17+
var O = defined(this);
18+
var matcher = regexp == undefined ? undefined : regexp[MATCH];
19+
return matcher !== undefined ? matcher.call(regexp, O) : new RegExp(regexp)[MATCH](String(O));
20+
},
21+
// `RegExp.prototype[@@match]` method
22+
// https://tc39.github.io/ecma262/#sec-regexp.prototype-@@match
23+
function (regexp) {
24+
var res = maybeCallNative(nativeMatch, regexp, this);
25+
if (res.done) return res.value;
2326

24-
var rx = anObject(regexp);
25-
var S = String(this);
27+
var rx = anObject(regexp);
28+
var S = String(this);
2629

27-
if (!rx.global) return regExpExec(rx, S);
30+
if (!rx.global) return regExpExec(rx, S);
2831

29-
var fullUnicode = rx.unicode;
30-
rx.lastIndex = 0;
31-
var A = [];
32-
var n = 0;
33-
var result;
34-
while ((result = regExpExec(rx, S)) !== null) {
35-
var matchStr = String(result[0]);
36-
A[n] = matchStr;
37-
if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode);
38-
n++;
32+
var fullUnicode = rx.unicode;
33+
rx.lastIndex = 0;
34+
var A = [];
35+
var n = 0;
36+
var result;
37+
while ((result = regExpExec(rx, S)) !== null) {
38+
var matchStr = String(result[0]);
39+
A[n] = matchStr;
40+
if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode);
41+
n++;
42+
}
43+
return n === 0 ? null : A;
3944
}
40-
return n === 0 ? null : A;
41-
}
42-
];
43-
});
45+
];
46+
}
47+
);

packages/core-js/modules/es.string.replace.js

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ var toObject = require('../internals/to-object');
55
var toLength = require('../internals/to-length');
66
var toInteger = require('../internals/to-integer');
77
var advanceStringIndex = require('../internals/advance-string-index');
8-
var regExpExec = require('../internals/regexp-exec');
9-
var nativeExec = RegExp.prototype.exec;
8+
var regExpExec = require('../internals/regexp-exec-abstract');
109
var max = Math.max;
1110
var min = Math.min;
1211
var floor = Math.floor;
@@ -21,7 +20,7 @@ var maybeToString = function (it) {
2120
require('../internals/fix-regexp-well-known-symbol-logic')(
2221
'replace',
2322
2,
24-
function (defined, REPLACE, nativeReplace, nativeRegExpReplace, reason) {
23+
function (defined, REPLACE, nativeReplace, maybeCallNative) {
2524
return [
2625
// `String.prototype.replace` method
2726
// https://tc39.github.io/ecma262/#sec-string.prototype.replace
@@ -35,15 +34,8 @@ require('../internals/fix-regexp-well-known-symbol-logic')(
3534
// `RegExp.prototype[@@replace]` method
3635
// https://tc39.github.io/ecma262/#sec-regexp.prototype-@@replace
3736
function (regexp, replaceValue) {
38-
if (regexp.exec === nativeExec) {
39-
if (reason.delegates) {
40-
// The native #replaceMethod already delegates to @@replace (this
41-
// polyfilled function, leasing to infinite recursion).
42-
// We avoid it by directly calling the native @@replace method.
43-
return nativeRegExpReplace.call(regexp, this, replaceValue);
44-
}
45-
return nativeReplace.call(this, regexp, replaceValue);
46-
}
37+
var res = maybeCallNative(nativeReplace, regexp, this, replaceValue);
38+
if (res.done) return res.value;
4739

4840
var rx = anObject(regexp);
4941
var S = String(this);
@@ -75,7 +67,13 @@ require('../internals/fix-regexp-well-known-symbol-logic')(
7567

7668
var matched = String(result[0]);
7769
var position = max(min(toInteger(result.index), S.length), 0);
78-
var captures = result.slice(1).map(maybeToString);
70+
var captures = [];
71+
// NOTE: This is equivalent to
72+
// captures = result.slice(1).map(maybeToString)
73+
// but for some reason `nativeSlice.call(result, 1, result.length)` (called in
74+
// the slice polyfill when slicing native arrays) "doesn't work" in safari 9 and
75+
// causes a crash (https://pastebin.com/N21QzeQA) when trying to debug it.
76+
for (var j = 1; j < result.length; j++) captures.push(maybeToString(result[j]));
7977
var namedCaptures = result.groups;
8078
if (functionalReplace) {
8179
var replacerArgs = [matched].concat(captures, position, S);
@@ -104,7 +102,7 @@ require('../internals/fix-regexp-well-known-symbol-logic')(
104102
}
105103
return nativeReplace.call(replacement, symbols, function (match, ch) {
106104
var capture;
107-
switch (ch[0]) {
105+
switch (ch.charAt(0)) {
108106
case '$': return '$';
109107
case '&': return matched;
110108
case '`': return str.slice(0, position);
@@ -118,7 +116,7 @@ require('../internals/fix-regexp-well-known-symbol-logic')(
118116
if (n > m) {
119117
var f = floor(n / 10);
120118
if (f === 0) return ch;
121-
if (f <= m) return captures[f - 1] === undefined ? ch[1] : captures[f - 1] + ch[1];
119+
if (f <= m) return captures[f - 1] === undefined ? ch.charAt(1) : captures[f - 1] + ch.charAt(1);
122120
return ch;
123121
}
124122
capture = captures[n - 1];

0 commit comments

Comments
 (0)