Skip to content

Commit

Permalink
Rebase nicolo-ribaudo work
Browse files Browse the repository at this point in the history
zloirock#492

Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
  • Loading branch information
cvle and nicolo-ribaudo committed Dec 16, 2019
1 parent b4d0072 commit 05d91a9
Show file tree
Hide file tree
Showing 15 changed files with 233 additions and 22 deletions.
6 changes: 6 additions & 0 deletions packages/core-js-compat/src/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,12 @@ const data = {
firefox: '37',
safari: '9.0',
},
'es.regexp.sticky': {
chrome: '49',
edge: '13',
firefox: '3',
safari: '10.0',
},
'es.regexp.to-string': {
chrome: '50',
firefox: '46',
Expand Down
1 change: 1 addition & 0 deletions packages/core-js/es/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ require('../modules/es.string.sup');
require('../modules/es.regexp.constructor');
require('../modules/es.regexp.exec');
require('../modules/es.regexp.flags');
require('../modules/es.regexp.sticky');
require('../modules/es.regexp.to-string');
require('../modules/es.parse-int');
require('../modules/es.parse-float');
Expand Down
1 change: 1 addition & 0 deletions packages/core-js/es/regexp/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ require('../../modules/es.regexp.constructor');
require('../../modules/es.regexp.to-string');
require('../../modules/es.regexp.exec');
require('../../modules/es.regexp.flags');
require('../../modules/es.regexp.sticky');
require('../../modules/es.string.match');
require('../../modules/es.string.replace');
require('../../modules/es.string.search');
Expand Down
5 changes: 5 additions & 0 deletions packages/core-js/es/regexp/sticky.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require('../../modules/es.regexp.sticky');

module.exports = function (it) {
return it.sticky;
};
14 changes: 9 additions & 5 deletions packages/core-js/internals/fix-regexp-well-known-symbol-logic.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
'use strict';
var createNonEnumerableProperty = require('../internals/create-non-enumerable-property');
var redefine = require('../internals/redefine');
var fails = require('../internals/fails');
var wellKnownSymbol = require('../internals/well-known-symbol');
Expand All @@ -20,6 +19,12 @@ var REPLACE_SUPPORTS_NAMED_GROUPS = !fails(function () {
return ''.replace(re, '$<a>') !== '7';
});

// IE <= 11 replaces $0 with the whole match, as if it was $&
// https://stackoverflow.com/questions/6024666/getting-ie-to-replace-a-regex-with-the-literal-string-0
var REPLACE_KEEPS_$0 = (function () {
return 'a'.replace(/./, '$0') === '$0';
})();

// Chrome 51 has a buggy "split" implementation when RegExp#exec !== nativeExec
// Weex JS has frozen built-in prototypes, so use try / catch wrapper
var SPLIT_WORKS_WITH_OVERWRITTEN_EXEC = !fails(function () {
Expand All @@ -30,7 +35,7 @@ var SPLIT_WORKS_WITH_OVERWRITTEN_EXEC = !fails(function () {
return result.length !== 2 || result[0] !== 'a' || result[1] !== 'b';
});

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

var DELEGATES_TO_SYMBOL = !fails(function () {
Expand Down Expand Up @@ -67,7 +72,7 @@ module.exports = function (KEY, length, exec, sham) {
if (
!DELEGATES_TO_SYMBOL ||
!DELEGATES_TO_EXEC ||
(KEY === 'replace' && !REPLACE_SUPPORTS_NAMED_GROUPS) ||
(KEY === 'replace' && !(REPLACE_SUPPORTS_NAMED_GROUPS && REPLACE_KEEPS_$0)) ||
(KEY === 'split' && !SPLIT_WORKS_WITH_OVERWRITTEN_EXEC)
) {
var nativeRegExpMethod = /./[SYMBOL];
Expand All @@ -82,7 +87,7 @@ module.exports = function (KEY, length, exec, sham) {
return { done: true, value: nativeMethod.call(str, regexp, arg2) };
}
return { done: false };
});
}, { REPLACE_KEEPS_$0: REPLACE_KEEPS_$0 });
var stringMethod = methods[0];
var regexMethod = methods[1];

Expand All @@ -95,6 +100,5 @@ module.exports = function (KEY, length, exec, sham) {
// 21.2.5.9 RegExp.prototype[@@search](string)
: function (string) { return regexMethod.call(string, this); }
);
if (sham) createNonEnumerableProperty(RegExp.prototype[SYMBOL], 'sham', true);
}
};
25 changes: 22 additions & 3 deletions packages/core-js/internals/regexp-exec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
var regexpFlags = require('./regexp-flags');
var stickyHelpers = require('./regexp-sticky-helpers');

var nativeExec = RegExp.prototype.exec;
// This always refers to the native implementation, because the
Expand All @@ -17,24 +18,42 @@ var UPDATES_LAST_INDEX_WRONG = (function () {
return re1.lastIndex !== 0 || re2.lastIndex !== 0;
})();

var UNSUPPORTED_Y = stickyHelpers.UNSUPPORTED_Y || stickyHelpers.BROKEN_CARET;

// nonparticipating capturing group, copied from es5-shim's String#split patch.
var NPCG_INCLUDED = /()??/.exec('')[1] !== undefined;

var PATCH = UPDATES_LAST_INDEX_WRONG || NPCG_INCLUDED;
var PATCH = UPDATES_LAST_INDEX_WRONG || NPCG_INCLUDED || UNSUPPORTED_Y;

if (PATCH) {
patchedExec = function exec(str) {
var re = this;
var lastIndex, reCopy, match, i;
var sticky = UNSUPPORTED_Y && re.sticky;

if (sticky) {
var flags = (re.ignoreCase ? 'i' : '') +
(re.multiline ? 'm' : '') +
(re.unicode ? 'u' : '') +
'g';

// ^(? + rx + ) is needed, in combination with some str slicing, to
// simulate the 'y' flag.
reCopy = new RegExp('^(?:' + re.source + ')', flags);
str = String(str).slice(re.lastIndex);
}

if (NPCG_INCLUDED) {
reCopy = new RegExp('^' + re.source + '$(?!\\s)', regexpFlags.call(re));
}
if (UPDATES_LAST_INDEX_WRONG) lastIndex = re.lastIndex;

match = nativeExec.call(re, str);
match = nativeExec.call(sticky ? reCopy : re, str);

if (UPDATES_LAST_INDEX_WRONG && match) {
if (sticky) {
if (match) re.lastIndex += match[0].length;
else re.lastIndex = 0;
} else if (UPDATES_LAST_INDEX_WRONG && match) {
re.lastIndex = re.global ? match.index + match[0].length : lastIndex;
}
if (NPCG_INCLUDED && match && match.length > 1) {
Expand Down
42 changes: 42 additions & 0 deletions packages/core-js/internals/regexp-sticky-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use strict';

var fails = require('./fails');
var speciesConstructor = require('../internals/species-constructor');

// babel-minify transpiles RegExp('a', 'y') -> /a/y and it causes SyntaxError,
// so we use an intermediate function.
function RE(s, f) {
return RegExp(s, f);
}

exports.UNSUPPORTED_Y = fails(function () {
// babel-minify transpiles RegExp('a', 'y') -> /a/y and it causes SyntaxError
var re = RE('a', 'y');
re.lastIndex = 2;
return re.exec('abcd') != null;
});

exports.BROKEN_CARET = fails(function () {
// https://bugzilla.mozilla.org/show_bug.cgi?id=773687
var re = RE('^r', 'gy');
re.lastIndex = 2;
return re.exec('str') != null;
});

exports.createStickyRegExp = function (re, otherFlags) {
var C = speciesConstructor(re, RegExp);

if (C !== RegExp) return new C(re, otherFlags + 'y');

// y is either supported or polyfilled
if (!exports.UNSUPPORTED_Y || RegExp.sham) {
return new RegExp(re, otherFlags + 'y');
}

// If y hasn't been polyfilled and it isn't supported, assigning
// to .sticky won't throw.
// This usually happens in engines where descriptors aren't supported.
var fakeRe = new RegExp(re, otherFlags);
fakeRe.sticky = true;
return fakeRe;
};
42 changes: 34 additions & 8 deletions packages/core-js/modules/es.regexp.constructor.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
var DESCRIPTORS = require('../internals/descriptors');
var createNonEnumerableProperty = require('../internals/create-non-enumerable-property');
var global = require('../internals/global');
var isForced = require('../internals/is-forced');
var inheritIfRequired = require('../internals/inherit-if-required');
var defineProperty = require('../internals/object-define-property').f;
var getOwnPropertyNames = require('../internals/object-get-own-property-names').f;
var isRegExp = require('../internals/is-regexp');
var getFlags = require('../internals/regexp-flags');
var stickyHelpers = require('../internals/regexp-sticky-helpers');
var redefine = require('../internals/redefine');
var fails = require('../internals/fails');
var setInternalState = require('../internals/internal-state').set;
var setSpecies = require('../internals/set-species');
var wellKnownSymbol = require('../internals/well-known-symbol');

Expand All @@ -20,7 +23,9 @@ var re2 = /a/g;
// "new" should create a new object, old webkit bug
var CORRECT_NEW = new NativeRegExp(re1) !== re1;

var FORCED = DESCRIPTORS && isForced('RegExp', (!CORRECT_NEW || fails(function () {
var UNSUPPORTED_Y = stickyHelpers.UNSUPPORTED_Y;

var FORCED = DESCRIPTORS && isForced('RegExp', (!CORRECT_NEW || UNSUPPORTED_Y || fails(function () {
re2[MATCH] = false;
// RegExp constructor can alter flags and IsRegExp works correct with @@match
return NativeRegExp(re1) != re1 || NativeRegExp(re2) == re2 || NativeRegExp(re1, 'i') != '/a/i';
Expand All @@ -33,13 +38,32 @@ if (FORCED) {
var thisIsRegExp = this instanceof RegExpWrapper;
var patternIsRegExp = isRegExp(pattern);
var flagsAreUndefined = flags === undefined;
return !thisIsRegExp && patternIsRegExp && pattern.constructor === RegExpWrapper && flagsAreUndefined ? pattern
: inheritIfRequired(CORRECT_NEW
? new NativeRegExp(patternIsRegExp && !flagsAreUndefined ? pattern.source : pattern, flags)
: NativeRegExp((patternIsRegExp = pattern instanceof RegExpWrapper)
? pattern.source
: pattern, patternIsRegExp && flagsAreUndefined ? getFlags.call(pattern) : flags)
, thisIsRegExp ? this : RegExpPrototype, RegExpWrapper);

if (!thisIsRegExp && patternIsRegExp && pattern.constructor === RegExpWrapper && flagsAreUndefined) {
return pattern;
}

if (CORRECT_NEW) {
if (patternIsRegExp && !flagsAreUndefined) pattern = pattern.source;
} else if (pattern instanceof RegExpWrapper) {
if (flagsAreUndefined) flags = getFlags.call(pattern);
pattern = pattern.source;
}

if (UNSUPPORTED_Y) {
var sticky = !!flags && flags.indexOf('y') > -1;
if (sticky) flags = flags.replace(/y/g, '');
}

var result = inheritIfRequired(
CORRECT_NEW ? new NativeRegExp(pattern, flags) : NativeRegExp(pattern, flags),
thisIsRegExp ? this : RegExpPrototype,
RegExpWrapper
);

if (UNSUPPORTED_Y) setInternalState(result, { sticky: sticky });

return result;
};
var proxy = function (key) {
key in RegExpWrapper || defineProperty(RegExpWrapper, key, {
Expand All @@ -54,6 +78,8 @@ if (FORCED) {
RegExpPrototype.constructor = RegExpWrapper;
RegExpWrapper.prototype = RegExpPrototype;
redefine(global, 'RegExp', RegExpWrapper);

if (UNSUPPORTED_Y) createNonEnumerableProperty(RegExpWrapper, 'sham', true);
}

// https://tc39.github.io/ecma262/#sec-get-regexp-@@species
Expand Down
3 changes: 2 additions & 1 deletion packages/core-js/modules/es.regexp.flags.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
var DESCRIPTORS = require('../internals/descriptors');
var objectDefinePropertyModule = require('../internals/object-define-property');
var regExpFlags = require('../internals/regexp-flags');
var UNSUPPORTED_Y = require('../internals/regexp-sticky-helpers').UNSUPPORTED_Y;

// `RegExp.prototype.flags` getter
// https://tc39.github.io/ecma262/#sec-get-regexp.prototype.flags
if (DESCRIPTORS && /./g.flags != 'g') {
if (DESCRIPTORS && (/./g.flags != 'g' || UNSUPPORTED_Y)) {
objectDefinePropertyModule.f(RegExp.prototype, 'flags', {
configurable: true,
get: regExpFlags
Expand Down
21 changes: 21 additions & 0 deletions packages/core-js/modules/es.regexp.sticky.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
var DESCRIPTORS = require('../internals/descriptors');
var UNSUPPORTED_Y = require('../internals/regexp-sticky-helpers').UNSUPPORTED_Y;
var defineProperty = require('../internals/object-define-property').f;
var getInternalState = require('../internals/internal-state').get;
var RegExpPrototype = RegExp.prototype;

// `RegExp.prototype.sticky` getter
if (DESCRIPTORS && UNSUPPORTED_Y) {
defineProperty(RegExp.prototype, 'sticky', {
configurable: true,
get: function () {
if (this === RegExpPrototype) return undefined;
// We can't use InternalStateModule.getterFor because
// we don't add metadata for regexps created by a literal.
if (this instanceof RegExp) {
return !!getInternalState(this).sticky;
}
throw TypeError('Incompatible receiver, RegExp required');
}
});
}
12 changes: 9 additions & 3 deletions packages/core-js/modules/es.string.replace.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var maybeToString = function (it) {
};

// @@replace logic
fixRegExpWellKnownSymbolLogic('replace', 2, function (REPLACE, nativeReplace, maybeCallNative) {
fixRegExpWellKnownSymbolLogic('replace', 2, function (REPLACE, nativeReplace, maybeCallNative, reason) {
return [
// `String.prototype.replace` method
// https://tc39.github.io/ecma262/#sec-string.prototype.replace
Expand All @@ -33,8 +33,14 @@ fixRegExpWellKnownSymbolLogic('replace', 2, function (REPLACE, nativeReplace, ma
// `RegExp.prototype[@@replace]` method
// https://tc39.github.io/ecma262/#sec-regexp.prototype-@@replace
function (regexp, replaceValue) {
var res = maybeCallNative(nativeReplace, regexp, this, replaceValue);
if (res.done) return res.value;
if (
reason.REPLACE_KEEPS_$0 || (
typeof replaceValue === 'string' && replaceValue.indexOf('$0') === -1
)
) {
var res = maybeCallNative(nativeReplace, regexp, this, replaceValue);
if (res.done) return res.value;
}

var rx = anObject(regexp);
var S = String(this);
Expand Down
12 changes: 10 additions & 2 deletions tests/compat/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -733,18 +733,26 @@ GLOBAL.tests = {
&& RegExp(re1) === re1
&& RegExp(re2) !== re2
&& RegExp(re1, 'i') == '/a/i'
&& new RegExp('a', 'y') // just check that it doesn't throw
&& RegExp[Symbol.species];
},
'es.regexp.exec': function () {
var re1 = /a/;
var re2 = /b*/g;
var reSticky = new RegExp('a', 'y');
re1.exec('a');
re2.exec('a');
return re1.lastIndex === 0 && re2.lastIndex === 0
&& /()??/.exec('')[1] === undefined;
&& /()??/.exec('')[1] === undefined
&& reSticky.exec('abc')[0] === 'a'
&& reSticky.exec('abc') === null
&& (reSticky.lastIndex = 1, reSticky.exec('bac')[0] === 'a');
},
'es.regexp.flags': function () {
return /./g.flags === 'g';
return /./g.flags === 'g' && new RegExp('a', 'y').flags === 'y';
},
'es.regexp.sticky': function () {
return new RegExp('a', 'y').sticky === true;
},
'es.regexp.to-string': function () {
return RegExp.prototype.toString.call({ source: 'a', flags: 'b' }) === '/a/b'
Expand Down
34 changes: 34 additions & 0 deletions tests/tests/es.regexp.exec.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DESCRIPTORS } from '../helpers/constants';

QUnit.test('RegExp#exec lastIndex updating', assert => {
let re = /b/;
assert.strictEqual(re.lastIndex, 0, '.lastIndex starts at 0 for non-global regexps');
Expand Down Expand Up @@ -26,3 +28,35 @@ QUnit.test('RegExp#exec capturing groups', assert => {
// #replace, but here also #replace is buggy :(
// assert.deepEqual(/(a?)?/.exec('x'), ['', undefined], '/(a?)?/.exec("x") returns ["", undefined]');
});

if (DESCRIPTORS) {
QUnit.test('RegExp#exec sticky', assert => {
const re = new RegExp('a', 'y');
const str = 'bbabaab';
assert.strictEqual(re.lastIndex, 0, '#1');

assert.strictEqual(re.exec(str), null, '#2');
assert.strictEqual(re.lastIndex, 0, '#3');

re.lastIndex = 1;
assert.strictEqual(re.exec(str), null, '#4');
assert.strictEqual(re.lastIndex, 0, '#5');

re.lastIndex = 2;
assert.deepEqual(re.exec(str), ['a'], '#6');
assert.strictEqual(re.lastIndex, 3, '#7');

assert.strictEqual(re.exec(str), null, '#8');
assert.strictEqual(re.lastIndex, 0, '#9');

re.lastIndex = 4;
assert.deepEqual(re.exec(str), ['a'], '#10');
assert.strictEqual(re.lastIndex, 5, '#11');

assert.deepEqual(re.exec(str), ['a'], '#12');
assert.strictEqual(re.lastIndex, 6, '#13');

assert.strictEqual(re.exec(str), null, '#14');
assert.strictEqual(re.lastIndex, 0, '#15');
});
}

0 comments on commit 05d91a9

Please sign in to comment.