Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix RegExp methods in old browsers #453

Merged
merged 9 commits into from
Nov 17, 2018
47 changes: 39 additions & 8 deletions packages/core-js/internals/fix-regexp-well-known-symbol-logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ var fails = require('../internals/fails');
var requireObjectCoercible = require('../internals/require-object-coercible');
var wellKnownSymbol = require('../internals/well-known-symbol');

var nativeExec = RegExp.prototype.exec;
var SPECIES = wellKnownSymbol('species');

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

var delegates = !fails(function () {
var delegatesToSymbol = !fails(function () {
// String methods call symbol-named RegEp methods
var O = {};
O[SYMBOL] = function () { return 7; };
return ''[KEY](O) != 7;
}) && !fails(function () {
});

var delegatesToExec = delegatesToSymbol ? !fails(function () {
// Symbol-named RegExp methods call .exec
var execCalled = false;
var re = /a/;
Expand All @@ -30,7 +33,7 @@ module.exports = function (KEY, length, exec, sham) {

re[SYMBOL]('');
return !execCalled;
});
}) : undefined;

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

if (!delegates || (KEY === 'replace' && !replaceSupportsNamedGroups)) {
var methods = exec(requireObjectCoercible, SYMBOL, ''[KEY], /./[SYMBOL], {
delegates: delegates,
replaceSupportsNamedGroups: replaceSupportsNamedGroups
});
var splitWorksWithOverwrittenExec = KEY === 'split' && (function () {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the fix for Chrome 51.

// Chrome 51 has a buggy "split" implementation when RegExp#exec !== nativeExec
var re = /(?:)/;
var originalExec = re.exec;
re.exec = function () { return originalExec.apply(this, arguments); };
var result = 'ab'.split(re);
return result.length === 2 && result[0] === 'a' && result[1] === 'b';
})();

if (
!delegatesToSymbol ||
!delegatesToExec ||
(KEY === 'replace' && !replaceSupportsNamedGroups) ||
(KEY === 'split' && !splitWorksWithOverwrittenExec)
) {
var nativeRegExpMethod = /./[SYMBOL];
var methods = exec(
requireObjectCoercible,
SYMBOL,
''[KEY],
function maybeCallNative(nativeMethod, regexp, str, arg2, forceStringMethod) {
if (regexp.exec === nativeExec) {
if (delegatesToSymbol && !forceStringMethod) {
// The native String method already delegates to @@method (this
// polyfilled function), leasing to infinite recursion.
// We avoid it by directly calling the native @@method method.
return { done: true, value: nativeRegExpMethod.call(regexp, str, arg2) };
}
return { done: true, value: nativeMethod.call(str, regexp, arg2) };
}
return { done: false };
}
);
var stringMethod = methods[0];
var regexMethod = methods[1];

Expand Down
66 changes: 35 additions & 31 deletions packages/core-js/modules/es.string.match.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,44 @@ var anObject = require('../internals/an-object');
var toLength = require('../internals/to-length');
var advanceStringIndex = require('../internals/advance-string-index');
var regExpExec = require('../internals/regexp-exec');
var nativeExec = RegExp.prototype.exec;

// @@match logic
require('../internals/fix-regexp-well-known-symbol-logic')('match', 1, function (defined, MATCH, nativeMatch) {
return [
// `String.prototype.match` method
// https://tc39.github.io/ecma262/#sec-string.prototype.match
function match(regexp) {
var O = defined(this);
var matcher = regexp == undefined ? undefined : regexp[MATCH];
return matcher !== undefined ? matcher.call(regexp, O) : new RegExp(regexp)[MATCH](String(O));
},
// `RegExp.prototype[@@match]` method
// https://tc39.github.io/ecma262/#sec-regexp.prototype-@@match
function (regexp) {
if (regexp.exec === nativeExec) return nativeMatch.call(this, regexp);
require('../internals/fix-regexp-well-known-symbol-logic')(
'match',
1,
function (defined, MATCH, nativeMatch, maybeCallNative) {
return [
// `String.prototype.match` method
// https://tc39.github.io/ecma262/#sec-string.prototype.match
function match(regexp) {
var O = defined(this);
var matcher = regexp == undefined ? undefined : regexp[MATCH];
return matcher !== undefined ? matcher.call(regexp, O) : new RegExp(regexp)[MATCH](String(O));
},
// `RegExp.prototype[@@match]` method
// https://tc39.github.io/ecma262/#sec-regexp.prototype-@@match
function (regexp) {
var res = maybeCallNative(nativeMatch, regexp, this);
Copy link
Contributor Author

@nicolo-ribaudo nicolo-ribaudo Nov 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All those "maybeCallNative" calls are to fix the infinite recursion in Chrome 50

if (res.done) return res.value;

var rx = anObject(regexp);
var S = String(this);
var rx = anObject(regexp);
var S = String(this);

if (!rx.global) return regExpExec(rx, S);
if (!rx.global) return regExpExec(rx, S);

var fullUnicode = rx.unicode;
rx.lastIndex = 0;
var A = [];
var n = 0;
var result;
while ((result = regExpExec(rx, S)) !== null) {
var matchStr = String(result[0]);
A[n] = matchStr;
if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode);
n++;
var fullUnicode = rx.unicode;
rx.lastIndex = 0;
var A = [];
var n = 0;
var result;
while ((result = regExpExec(rx, S)) !== null) {
var matchStr = String(result[0]);
A[n] = matchStr;
if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode);
n++;
}
return n === 0 ? null : A;
}
return n === 0 ? null : A;
}
];
});
];
}
);
14 changes: 3 additions & 11 deletions packages/core-js/modules/es.string.replace.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ var toLength = require('../internals/to-length');
var toInteger = require('../internals/to-integer');
var advanceStringIndex = require('../internals/advance-string-index');
var regExpExec = require('../internals/regexp-exec');
var nativeExec = RegExp.prototype.exec;
var max = Math.max;
var min = Math.min;
var floor = Math.floor;
Expand All @@ -21,7 +20,7 @@ var maybeToString = function (it) {
require('../internals/fix-regexp-well-known-symbol-logic')(
'replace',
2,
function (defined, REPLACE, nativeReplace, nativeRegExpReplace, reason) {
function (defined, REPLACE, nativeReplace, maybeCallNative) {
return [
// `String.prototype.replace` method
// https://tc39.github.io/ecma262/#sec-string.prototype.replace
Expand All @@ -35,15 +34,8 @@ require('../internals/fix-regexp-well-known-symbol-logic')(
// `RegExp.prototype[@@replace]` method
// https://tc39.github.io/ecma262/#sec-regexp.prototype-@@replace
function (regexp, replaceValue) {
if (regexp.exec === nativeExec) {
if (reason.delegates) {
// The native #replaceMethod already delegates to @@replace (this
// polyfilled function, leasing to infinite recursion).
// We avoid it by directly calling the native @@replace method.
return nativeRegExpReplace.call(regexp, this, replaceValue);
}
return nativeReplace.call(this, regexp, replaceValue);
}
var res = maybeCallNative(nativeReplace, regexp, this, replaceValue);
if (res.done) return res.value;

var rx = anObject(regexp);
var S = String(this);
Expand Down
52 changes: 28 additions & 24 deletions packages/core-js/modules/es.string.search.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,35 @@
var anObject = require('../internals/an-object');
var sameValue = require('../internals/same-value');
var regExpExec = require('../internals/regexp-exec');
var nativeExec = RegExp.prototype.exec;

// @@search logic
require('../internals/fix-regexp-well-known-symbol-logic')('search', 1, function (defined, SEARCH, nativeSearch) {
return [
// `String.prototype.search` method
// https://tc39.github.io/ecma262/#sec-string.prototype.search
function search(regexp) {
var O = defined(this);
var searcher = regexp == undefined ? undefined : regexp[SEARCH];
return searcher !== undefined ? searcher.call(regexp, O) : new RegExp(regexp)[SEARCH](String(O));
},
// `RegExp.prototype[@@search]` method
// https://tc39.github.io/ecma262/#sec-regexp.prototype-@@search
function (regexp) {
if (regexp.exec === nativeExec) return nativeSearch.call(this, regexp);
require('../internals/fix-regexp-well-known-symbol-logic')(
'search',
1,
function (defined, SEARCH, nativeSearch, maybeCallNative) {
return [
// `String.prototype.search` method
// https://tc39.github.io/ecma262/#sec-string.prototype.search
function search(regexp) {
var O = defined(this);
var searcher = regexp == undefined ? undefined : regexp[SEARCH];
return searcher !== undefined ? searcher.call(regexp, O) : new RegExp(regexp)[SEARCH](String(O));
},
// `RegExp.prototype[@@search]` method
// https://tc39.github.io/ecma262/#sec-regexp.prototype-@@search
function (regexp) {
var res = maybeCallNative(nativeSearch, regexp, this);
if (res.done) return res.value;

var rx = anObject(regexp);
var S = String(this);
var rx = anObject(regexp);
var S = String(this);

var previousLastIndex = rx.lastIndex;
if (!sameValue(previousLastIndex, 0)) rx.lastIndex = 0;
var result = regExpExec(rx, S);
if (!sameValue(rx.lastIndex, previousLastIndex)) rx.lastIndex = previousLastIndex;
return result === null ? -1 : result.index;
}
];
});
var previousLastIndex = rx.lastIndex;
if (!sameValue(previousLastIndex, 0)) rx.lastIndex = 0;
var result = regExpExec(rx, S);
if (!sameValue(rx.lastIndex, previousLastIndex)) rx.lastIndex = previousLastIndex;
return result === null ? -1 : result.index;
}
];
}
);
Loading