From 80c19a6af6ebe04f55fa9c5c30e6e695939a19a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Thu, 7 Mar 2019 02:12:52 +0100 Subject: [PATCH 01/11] Add support for the 'y' flag to the RegExp constructor --- .../fix-regexp-well-known-symbol-logic.js | 4 +- packages/core-js/internals/regexp-exec.js | 29 +++++++++++-- .../core-js/modules/es.regexp.constructor.js | 43 +++++++++++++++---- packages/core-js/modules/es.string.split.js | 17 +++----- tests/tests/es.regexp.constructor.js | 6 +++ tests/tests/es.regexp.exec.js | 30 +++++++++++++ 6 files changed, 104 insertions(+), 25 deletions(-) diff --git a/packages/core-js/internals/fix-regexp-well-known-symbol-logic.js b/packages/core-js/internals/fix-regexp-well-known-symbol-logic.js index 563baa3c2e4e..933b1fe4e71f 100644 --- a/packages/core-js/internals/fix-regexp-well-known-symbol-logic.js +++ b/packages/core-js/internals/fix-regexp-well-known-symbol-logic.js @@ -1,5 +1,4 @@ 'use strict'; -var hide = require('../internals/hide'); var redefine = require('../internals/redefine'); var fails = require('../internals/fails'); var wellKnownSymbol = require('../internals/well-known-symbol'); @@ -30,7 +29,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 () { @@ -88,6 +87,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) hide(RegExp.prototype[SYMBOL], 'sham', true); } }; diff --git a/packages/core-js/internals/regexp-exec.js b/packages/core-js/internals/regexp-exec.js index ce4962052c49..3134e6f7b029 100644 --- a/packages/core-js/internals/regexp-exec.js +++ b/packages/core-js/internals/regexp-exec.js @@ -1,6 +1,7 @@ 'use strict'; var regexpFlags = require('./regexp-flags'); +var fails = require('./fails'); var nativeExec = RegExp.prototype.exec; // This always refers to the native implementation, because the @@ -18,24 +19,46 @@ var UPDATES_LAST_INDEX_WRONG = (function () { return re1.lastIndex !== 0 || re2.lastIndex !== 0; })(); +var UNSUPPORTED_Y = fails(function () { + // babel-minify transpiles RegExp('a', 'y') -> /a/y and it causes SyntaxError + var re = RegExp(Math.random() < 2 && 'a', 'y'); + re.lastIndex = 2; + return re.exec('abcd') != null; +}); + // 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) { diff --git a/packages/core-js/modules/es.regexp.constructor.js b/packages/core-js/modules/es.regexp.constructor.js index 5fa6ee602e24..86596353a3ff 100644 --- a/packages/core-js/modules/es.regexp.constructor.js +++ b/packages/core-js/modules/es.regexp.constructor.js @@ -1,5 +1,6 @@ var DESCRIPTORS = require('../internals/descriptors'); var MATCH = require('../internals/well-known-symbol')('match'); +var hide = require('../internals/hide'); var global = require('../internals/global'); var isForced = require('../internals/is-forced'); var inheritIfRequired = require('../internals/inherit-if-required'); @@ -17,7 +18,10 @@ var re2 = /a/g; // "new" should create a new object, old webkit bug var CORRECT_NEW = new NativeRegExp(re1) !== re1; -var FORCED = isForced('RegExp', DESCRIPTORS && (!CORRECT_NEW || fails(function () { +// babel-minify transpiles RegExp('x', 'y') -> /x/y and it causes SyntaxError +var SUPPORTS_Y = !fails(function () { return RegExp(CORRECT_NEW, 'y'); }); + +var FORCED = isForced('RegExp', DESCRIPTORS && (!CORRECT_NEW || !SUPPORTS_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'; @@ -30,13 +34,35 @@ 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 (!SUPPORTS_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 (!SUPPORTS_Y) defineProperty(result, 'sticky', { + configurable: true, + get: function () { return sticky; } + }); + + return result; }; var proxy = function (key) { key in RegExpWrapper || defineProperty(RegExpWrapper, key, { @@ -51,6 +77,7 @@ if (FORCED) { RegExpPrototype.constructor = RegExpWrapper; RegExpWrapper.prototype = RegExpPrototype; redefine(global, 'RegExp', RegExpWrapper); + if (!SUPPORTS_Y) hide(RegExpWrapper, 'sham', true); } // https://tc39.github.io/ecma262/#sec-get-regexp-@@species diff --git a/packages/core-js/modules/es.string.split.js b/packages/core-js/modules/es.string.split.js index bbced21b5a4b..3fe6f1be8d38 100644 --- a/packages/core-js/modules/es.string.split.js +++ b/packages/core-js/modules/es.string.split.js @@ -8,14 +8,10 @@ var advanceStringIndex = require('../internals/advance-string-index'); var toLength = require('../internals/to-length'); var callRegExpExec = require('../internals/regexp-exec-abstract'); var regexpExec = require('../internals/regexp-exec'); -var fails = require('../internals/fails'); var arrayPush = [].push; var min = Math.min; var MAX_UINT32 = 0xffffffff; -// babel-minify transpiles RegExp('x', 'y') -> /x/y and it causes SyntaxError -var SUPPORTS_Y = !fails(function () { return !RegExp(MAX_UINT32, 'y'); }); - // @@split logic require('../internals/fix-regexp-well-known-symbol-logic')( 'split', @@ -99,11 +95,11 @@ require('../internals/fix-regexp-well-known-symbol-logic')( var flags = (rx.ignoreCase ? 'i' : '') + (rx.multiline ? 'm' : '') + (rx.unicode ? 'u' : '') + - (SUPPORTS_Y ? 'y' : 'g'); + 'y'; // ^(? + rx + ) is needed, in combination with some S slicing, to // simulate the 'y' flag. - var splitter = new C(SUPPORTS_Y ? rx : '^(?:' + rx.source + ')', flags); + var splitter = new C(rx, flags); var lim = limit === undefined ? MAX_UINT32 : limit >>> 0; if (lim === 0) return []; if (S.length === 0) return callRegExpExec(splitter, S) === null ? [S] : []; @@ -111,12 +107,12 @@ require('../internals/fix-regexp-well-known-symbol-logic')( var q = 0; var A = []; while (q < S.length) { - splitter.lastIndex = SUPPORTS_Y ? q : 0; - var z = callRegExpExec(splitter, SUPPORTS_Y ? S : S.slice(q)); + splitter.lastIndex = q; + var z = callRegExpExec(splitter, S); var e; if ( z === null || - (e = min(toLength(splitter.lastIndex + (SUPPORTS_Y ? 0 : q)), S.length)) === p + (e = min(toLength(splitter.lastIndex), S.length)) === p ) { q = advanceStringIndex(S, q, unicodeMatching); } else { @@ -133,6 +129,5 @@ require('../internals/fix-regexp-well-known-symbol-logic')( return A; } ]; - }, - !SUPPORTS_Y + } ); diff --git a/tests/tests/es.regexp.constructor.js b/tests/tests/es.regexp.constructor.js index bd91f5069cc4..281ebab74214 100644 --- a/tests/tests/es.regexp.constructor.js +++ b/tests/tests/es.regexp.constructor.js @@ -41,4 +41,10 @@ if (DESCRIPTORS) { assert.ok(new Subclass('^abc$').test('abc'), 'correct subclassing with native classes #3'); } }); + + QUnit.test('RegExp sticky', assert => { + const re = new RegExp('a', 'y'); + assert.ok(re.sticky, '.sticky is true'); + assert.strictEqual(re.flags, 'y', '.flags contains y') + }) } diff --git a/tests/tests/es.regexp.exec.js b/tests/tests/es.regexp.exec.js index fd9222a01373..cef8788c71a9 100644 --- a/tests/tests/es.regexp.exec.js +++ b/tests/tests/es.regexp.exec.js @@ -26,3 +26,33 @@ 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]'); }); + +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'); +}); From c571f31feac80c3293a8736a929f54eb779d2bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Thu, 7 Mar 2019 11:31:26 +0100 Subject: [PATCH 02/11] Add semicolon --- tests/tests/es.regexp.constructor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests/es.regexp.constructor.js b/tests/tests/es.regexp.constructor.js index 281ebab74214..0dea861789cf 100644 --- a/tests/tests/es.regexp.constructor.js +++ b/tests/tests/es.regexp.constructor.js @@ -45,6 +45,6 @@ if (DESCRIPTORS) { QUnit.test('RegExp sticky', assert => { const re = new RegExp('a', 'y'); assert.ok(re.sticky, '.sticky is true'); - assert.strictEqual(re.flags, 'y', '.flags contains y') - }) + assert.strictEqual(re.flags, 'y', '.flags contains y'); + }); } From 775b17facda8a3cd6c8ec9977e9457beaaa4ee24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 13 Mar 2019 00:31:06 +0100 Subject: [PATCH 03/11] Fix old firefox --- packages/core-js/internals/regexp-exec.js | 11 ++----- packages/core-js/internals/regexp-flags.js | 3 +- .../internals/regexp-sticky-helpers.js | 27 +++++++++++++++++ .../core-js/modules/es.regexp.constructor.js | 30 +++++++++++++------ 4 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 packages/core-js/internals/regexp-sticky-helpers.js diff --git a/packages/core-js/internals/regexp-exec.js b/packages/core-js/internals/regexp-exec.js index 3134e6f7b029..5d27c01e3506 100644 --- a/packages/core-js/internals/regexp-exec.js +++ b/packages/core-js/internals/regexp-exec.js @@ -1,7 +1,7 @@ 'use strict'; var regexpFlags = require('./regexp-flags'); -var fails = require('./fails'); +var stickyHelpers = require('./regexp-sticky-helpers'); var nativeExec = RegExp.prototype.exec; // This always refers to the native implementation, because the @@ -19,12 +19,7 @@ var UPDATES_LAST_INDEX_WRONG = (function () { return re1.lastIndex !== 0 || re2.lastIndex !== 0; })(); -var UNSUPPORTED_Y = fails(function () { - // babel-minify transpiles RegExp('a', 'y') -> /a/y and it causes SyntaxError - var re = RegExp(Math.random() < 2 && 'a', 'y'); - re.lastIndex = 2; - return re.exec('abcd') != null; -}); +var UNSUPPORTED_Y = stickyHelpers.UNSUPPORTED_Y; // nonparticipating capturing group, copied from es5-shim's String#split patch. var NPCG_INCLUDED = /()??/.exec('')[1] !== undefined; @@ -35,7 +30,7 @@ if (PATCH) { patchedExec = function exec(str) { var re = this; var lastIndex, reCopy, match, i; - var sticky = UNSUPPORTED_Y && re.sticky; + var sticky = UNSUPPORTED_Y && stickyHelpers.isSticky(re); if (sticky) { var flags = (re.ignoreCase ? 'i' : '') + diff --git a/packages/core-js/internals/regexp-flags.js b/packages/core-js/internals/regexp-flags.js index 785d896ec3c9..19f8a441c020 100644 --- a/packages/core-js/internals/regexp-flags.js +++ b/packages/core-js/internals/regexp-flags.js @@ -1,5 +1,6 @@ 'use strict'; var anObject = require('../internals/an-object'); +var isSticky = require('./regexp-sticky-helpers').isSticky; // `RegExp.prototype.flags` getter implementation // https://tc39.github.io/ecma262/#sec-get-regexp.prototype.flags @@ -10,6 +11,6 @@ module.exports = function () { if (that.ignoreCase) result += 'i'; if (that.multiline) result += 'm'; if (that.unicode) result += 'u'; - if (that.sticky) result += 'y'; + if (isSticky(that)) result += 'y'; return result; }; diff --git a/packages/core-js/internals/regexp-sticky-helpers.js b/packages/core-js/internals/regexp-sticky-helpers.js new file mode 100644 index 000000000000..01ff483adc86 --- /dev/null +++ b/packages/core-js/internals/regexp-sticky-helpers.js @@ -0,0 +1,27 @@ +'use strict'; + +var fails = require('./fails'); + +var COREJS_STICKY = exports.COREJS_STICKY = '__core-js__sticky__'; + +// 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; +}) || 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.isSticky = function (re) { + return re.sticky || re[COREJS_STICKY]; +}; diff --git a/packages/core-js/modules/es.regexp.constructor.js b/packages/core-js/modules/es.regexp.constructor.js index 86596353a3ff..d44f5719b987 100644 --- a/packages/core-js/modules/es.regexp.constructor.js +++ b/packages/core-js/modules/es.regexp.constructor.js @@ -8,6 +8,7 @@ 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 NativeRegExp = global.RegExp; @@ -18,10 +19,9 @@ var re2 = /a/g; // "new" should create a new object, old webkit bug var CORRECT_NEW = new NativeRegExp(re1) !== re1; -// babel-minify transpiles RegExp('x', 'y') -> /x/y and it causes SyntaxError -var SUPPORTS_Y = !fails(function () { return RegExp(CORRECT_NEW, 'y'); }); +var UNSUPPORTED_Y = stickyHelpers.UNSUPPORTED_Y; -var FORCED = isForced('RegExp', DESCRIPTORS && (!CORRECT_NEW || !SUPPORTS_Y || fails(function () { +var FORCED = isForced('RegExp', DESCRIPTORS && (!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'; @@ -46,7 +46,7 @@ if (FORCED) { pattern = pattern.source; } - if (!SUPPORTS_Y) { + if (UNSUPPORTED_Y) { var sticky = !!flags && flags.indexOf('y') > -1; if (sticky) flags = flags.replace(/y/g, ''); } @@ -57,10 +57,22 @@ if (FORCED) { RegExpWrapper ); - if (!SUPPORTS_Y) defineProperty(result, 'sticky', { - configurable: true, - get: function () { return sticky; } - }); + if (UNSUPPORTED_Y) { + var desc = { + configurable: true, + get: function () { return sticky; } + }; + try { + defineProperty(result, 'sticky', desc); + } catch (e) { + // In old firefox versions (e.g. 11.0), RegExps have a + // non-configurable non-writable "sticky" property. + // It isn't possible to correctly set it to "true", + // but we need to store "sticky" in another property to + // make polyfilled .exec work. + defineProperty(result, stickyHelpers.COREJS_STICKY, desc); + } + } return result; }; @@ -77,7 +89,7 @@ if (FORCED) { RegExpPrototype.constructor = RegExpWrapper; RegExpWrapper.prototype = RegExpPrototype; redefine(global, 'RegExp', RegExpWrapper); - if (!SUPPORTS_Y) hide(RegExpWrapper, 'sham', true); + if (UNSUPPORTED_Y) hide(RegExpWrapper, 'sham', true); } // https://tc39.github.io/ecma262/#sec-get-regexp-@@species From b8fc64bb3058e8a28f6be8f9a5e23d001ec4ae96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 13 Mar 2019 00:32:06 +0100 Subject: [PATCH 04/11] Remove comment --- packages/core-js/modules/es.string.split.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core-js/modules/es.string.split.js b/packages/core-js/modules/es.string.split.js index 3fe6f1be8d38..ac0be96279db 100644 --- a/packages/core-js/modules/es.string.split.js +++ b/packages/core-js/modules/es.string.split.js @@ -97,8 +97,6 @@ require('../internals/fix-regexp-well-known-symbol-logic')( (rx.unicode ? 'u' : '') + 'y'; - // ^(? + rx + ) is needed, in combination with some S slicing, to - // simulate the 'y' flag. var splitter = new C(rx, flags); var lim = limit === undefined ? MAX_UINT32 : limit >>> 0; if (lim === 0) return []; From 5d6a0bddd66c659da35b5e88542e7b78acf6759e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 13 Mar 2019 00:39:59 +0100 Subject: [PATCH 05/11] Use native "sticky" property in old firefox --- packages/core-js/internals/regexp-exec.js | 4 ++-- packages/core-js/internals/regexp-flags.js | 3 +-- .../internals/regexp-sticky-helpers.js | 10 +++------- .../core-js/modules/es.regexp.constructor.js | 20 ++++--------------- 4 files changed, 10 insertions(+), 27 deletions(-) diff --git a/packages/core-js/internals/regexp-exec.js b/packages/core-js/internals/regexp-exec.js index 5d27c01e3506..6d9eeb2e199d 100644 --- a/packages/core-js/internals/regexp-exec.js +++ b/packages/core-js/internals/regexp-exec.js @@ -19,7 +19,7 @@ var UPDATES_LAST_INDEX_WRONG = (function () { return re1.lastIndex !== 0 || re2.lastIndex !== 0; })(); -var UNSUPPORTED_Y = stickyHelpers.UNSUPPORTED_Y; +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; @@ -30,7 +30,7 @@ if (PATCH) { patchedExec = function exec(str) { var re = this; var lastIndex, reCopy, match, i; - var sticky = UNSUPPORTED_Y && stickyHelpers.isSticky(re); + var sticky = UNSUPPORTED_Y && re.sticky; if (sticky) { var flags = (re.ignoreCase ? 'i' : '') + diff --git a/packages/core-js/internals/regexp-flags.js b/packages/core-js/internals/regexp-flags.js index 19f8a441c020..785d896ec3c9 100644 --- a/packages/core-js/internals/regexp-flags.js +++ b/packages/core-js/internals/regexp-flags.js @@ -1,6 +1,5 @@ 'use strict'; var anObject = require('../internals/an-object'); -var isSticky = require('./regexp-sticky-helpers').isSticky; // `RegExp.prototype.flags` getter implementation // https://tc39.github.io/ecma262/#sec-get-regexp.prototype.flags @@ -11,6 +10,6 @@ module.exports = function () { if (that.ignoreCase) result += 'i'; if (that.multiline) result += 'm'; if (that.unicode) result += 'u'; - if (isSticky(that)) result += 'y'; + if (that.sticky) result += 'y'; return result; }; diff --git a/packages/core-js/internals/regexp-sticky-helpers.js b/packages/core-js/internals/regexp-sticky-helpers.js index 01ff483adc86..da7641bb79f7 100644 --- a/packages/core-js/internals/regexp-sticky-helpers.js +++ b/packages/core-js/internals/regexp-sticky-helpers.js @@ -2,8 +2,6 @@ var fails = require('./fails'); -var COREJS_STICKY = exports.COREJS_STICKY = '__core-js__sticky__'; - // babel-minify transpiles RegExp('a', 'y') -> /a/y and it causes SyntaxError, // so we use an intermediate function. function RE(s, f) { @@ -15,13 +13,11 @@ exports.UNSUPPORTED_Y = fails(function () { var re = RE('a', 'y'); re.lastIndex = 2; return re.exec('abcd') != null; -}) || fails(function () { +}); + +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.isSticky = function (re) { - return re.sticky || re[COREJS_STICKY]; -}; diff --git a/packages/core-js/modules/es.regexp.constructor.js b/packages/core-js/modules/es.regexp.constructor.js index d44f5719b987..e723e3e5eb6d 100644 --- a/packages/core-js/modules/es.regexp.constructor.js +++ b/packages/core-js/modules/es.regexp.constructor.js @@ -57,22 +57,10 @@ if (FORCED) { RegExpWrapper ); - if (UNSUPPORTED_Y) { - var desc = { - configurable: true, - get: function () { return sticky; } - }; - try { - defineProperty(result, 'sticky', desc); - } catch (e) { - // In old firefox versions (e.g. 11.0), RegExps have a - // non-configurable non-writable "sticky" property. - // It isn't possible to correctly set it to "true", - // but we need to store "sticky" in another property to - // make polyfilled .exec work. - defineProperty(result, stickyHelpers.COREJS_STICKY, desc); - } - } + if (UNSUPPORTED_Y) defineProperty(result, 'sticky', { + configurable: true, + get: function () { return sticky; } + }); return result; }; From 416055921012686f08048284fd6bf28477554820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 13 Mar 2019 20:11:53 +0100 Subject: [PATCH 06/11] Move #sticky getter to prototype --- .../core-js/modules/es.regexp.constructor.js | 25 ++++++++++++++---- packages/core-js/modules/es.regexp.flags.js | 7 ++++- tests/tests/es.regexp.constructor.js | 6 ----- tests/tests/es.regexp.flags.js | 26 +++++++++++++++++++ 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/packages/core-js/modules/es.regexp.constructor.js b/packages/core-js/modules/es.regexp.constructor.js index e723e3e5eb6d..8689fd1e72b8 100644 --- a/packages/core-js/modules/es.regexp.constructor.js +++ b/packages/core-js/modules/es.regexp.constructor.js @@ -11,6 +11,9 @@ var getFlags = require('../internals/regexp-flags'); var stickyHelpers = require('../internals/regexp-sticky-helpers'); var redefine = require('../internals/redefine'); var fails = require('../internals/fails'); +var InternalStateModule = require('../internals/internal-state'); +var getInternalState = InternalStateModule.get; +var setInternalState = InternalStateModule.set; var NativeRegExp = global.RegExp; var RegExpPrototype = NativeRegExp.prototype; var re1 = /a/g; @@ -57,10 +60,7 @@ if (FORCED) { RegExpWrapper ); - if (UNSUPPORTED_Y) defineProperty(result, 'sticky', { - configurable: true, - get: function () { return sticky; } - }); + if (UNSUPPORTED_Y) setInternalState(result, { sticky: sticky }); return result; }; @@ -77,7 +77,22 @@ if (FORCED) { RegExpPrototype.constructor = RegExpWrapper; RegExpWrapper.prototype = RegExpPrototype; redefine(global, 'RegExp', RegExpWrapper); - if (UNSUPPORTED_Y) hide(RegExpWrapper, 'sham', true); + + if (UNSUPPORTED_Y) { + hide(RegExpWrapper, 'sham', true); + defineProperty(RegExpPrototype, 'sticky', { + configurable: true, + get: function () { + // We can't use InternalStateModule.getterFor because + // we don't add metadata for regexps created by a literal. + if (!(this instanceof RegExpWrapper) && this !== RegExpPrototype) { + throw TypeError('Incompatible receiver, RegExp required'); + } + var state = getInternalState(this); + return state.sticky; + } + }); + } } // https://tc39.github.io/ecma262/#sec-get-regexp-@@species diff --git a/packages/core-js/modules/es.regexp.flags.js b/packages/core-js/modules/es.regexp.flags.js index 01148b9d505e..bc5a655897b4 100644 --- a/packages/core-js/modules/es.regexp.flags.js +++ b/packages/core-js/modules/es.regexp.flags.js @@ -1,6 +1,11 @@ // `RegExp.prototype.flags` getter // https://tc39.github.io/ecma262/#sec-get-regexp.prototype.flags -if (require('../internals/descriptors') && /./g.flags != 'g') { +if ( + require('../internals/descriptors') && ( + /./g.flags != 'g' || + require('../internals/regexp-sticky-helpers').UNSUPPORTED_Y + ) +) { require('../internals/object-define-property').f(RegExp.prototype, 'flags', { configurable: true, get: require('../internals/regexp-flags') diff --git a/tests/tests/es.regexp.constructor.js b/tests/tests/es.regexp.constructor.js index 0dea861789cf..bd91f5069cc4 100644 --- a/tests/tests/es.regexp.constructor.js +++ b/tests/tests/es.regexp.constructor.js @@ -41,10 +41,4 @@ if (DESCRIPTORS) { assert.ok(new Subclass('^abc$').test('abc'), 'correct subclassing with native classes #3'); } }); - - QUnit.test('RegExp sticky', assert => { - const re = new RegExp('a', 'y'); - assert.ok(re.sticky, '.sticky is true'); - assert.strictEqual(re.flags, 'y', '.flags contains y'); - }); } diff --git a/tests/tests/es.regexp.flags.js b/tests/tests/es.regexp.flags.js index 5098080344b8..14c2b978caed 100644 --- a/tests/tests/es.regexp.flags.js +++ b/tests/tests/es.regexp.flags.js @@ -12,4 +12,30 @@ if (DESCRIPTORS) { assert.strictEqual(/./mig.flags, 'gim', '/./mig.flags is "gim"'); assert.strictEqual(/./mgi.flags, 'gim', '/./mgi.flags is "gim"'); }); + + QUnit.test('RegExp#sticky', assert => { + const re = new RegExp('a', 'y'); + assert.ok(re.sticky, '.sticky is true'); + assert.strictEqual(re.flags, 'y', '.flags contains y'); + + assert.ok(Object.hasOwnProperty.call(RegExp.prototype, 'sticky'), 'prototype has .sticky property'); + assert.strictEqual(RegExp.prototype.sticky, undefined, '.sticky is undefined on prototype'); + + const stickyGetter = Object.getOwnPropertyDescriptor(RegExp.prototype, 'sticky').get; + assert.throws(() => { + stickyGetter.call({}); + }, undefined, '.sticky getter can only be called on RegExp instances'); + try { + stickyGetter.call(/a/); + assert.ok(true, '.sticky getter works on literals'); + } catch (e) { + assert.ok(false, '.sticky getter works on literals'); + } + try { + stickyGetter.call(new RegExp('a')); + assert.ok(true, '.sticky getter works on literals'); + } catch (e) { + assert.ok(false, '.sticky getter works on literals'); + } + }); } From 145e4e741bcb85a5338844f38b7f34364262476a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 13 Mar 2019 20:12:09 +0100 Subject: [PATCH 07/11] Fix String#replace in IE11 --- .../internals/fix-regexp-well-known-symbol-logic.js | 10 ++++++++-- packages/core-js/modules/es.string.replace.js | 12 +++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/core-js/internals/fix-regexp-well-known-symbol-logic.js b/packages/core-js/internals/fix-regexp-well-known-symbol-logic.js index 933b1fe4e71f..9fb4084e016e 100644 --- a/packages/core-js/internals/fix-regexp-well-known-symbol-logic.js +++ b/packages/core-js/internals/fix-regexp-well-known-symbol-logic.js @@ -19,6 +19,12 @@ var REPLACE_SUPPORTS_NAMED_GROUPS = !fails(function () { return ''.replace(re, '$') !== '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 () { @@ -59,7 +65,7 @@ module.exports = function (KEY, length, exec) { 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]; @@ -74,7 +80,7 @@ module.exports = function (KEY, length, exec) { 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]; diff --git a/packages/core-js/modules/es.string.replace.js b/packages/core-js/modules/es.string.replace.js index e78c7a5ec853..370ecd1df394 100644 --- a/packages/core-js/modules/es.string.replace.js +++ b/packages/core-js/modules/es.string.replace.js @@ -21,7 +21,7 @@ var maybeToString = function (it) { require('../internals/fix-regexp-well-known-symbol-logic')( 'replace', 2, - function (REPLACE, nativeReplace, maybeCallNative) { + function (REPLACE, nativeReplace, maybeCallNative, reason) { return [ // `String.prototype.replace` method // https://tc39.github.io/ecma262/#sec-string.prototype.replace @@ -35,8 +35,14 @@ require('../internals/fix-regexp-well-known-symbol-logic')( // `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); From 06b6d4f9eecf5180cf6826dcabc2087bf4a97266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 13 Mar 2019 21:33:10 +0100 Subject: [PATCH 08/11] Move #sticky to a separate file --- packages/core-js-compat/src/data.js | 7 +++ packages/core-js/es/index.js | 1 + packages/core-js/es/regexp/index.js | 1 + packages/core-js/es/regexp/sticky.js | 5 ++ .../core-js/modules/es.regexp.constructor.js | 20 +------- packages/core-js/modules/es.regexp.sticky.js | 21 ++++++++ tests/compat/tests.js | 7 +++ tests/tests/es.regexp.exec.js | 48 ++++++++++--------- tests/tests/es.regexp.flags.js | 26 ---------- tests/tests/es.regexp.sticky.js | 36 ++++++++++++++ tests/tests/index.js | 1 + 11 files changed, 107 insertions(+), 66 deletions(-) create mode 100644 packages/core-js/es/regexp/sticky.js create mode 100644 packages/core-js/modules/es.regexp.sticky.js create mode 100644 tests/tests/es.regexp.sticky.js diff --git a/packages/core-js-compat/src/data.js b/packages/core-js-compat/src/data.js index a006910267da..abefda4616ab 100644 --- a/packages/core-js-compat/src/data.js +++ b/packages/core-js-compat/src/data.js @@ -787,6 +787,13 @@ module.exports = { firefox: '37', safari: '9.0', }, + 'es.regexp.sticky': { + chrome: '49', + edge: '13', + firefox: '3', + opera: '36', + safari: '10.0', + }, 'es.regexp.to-string': { chrome: '50', firefox: '46', diff --git a/packages/core-js/es/index.js b/packages/core-js/es/index.js index b73093cce2b0..6f2363e7763b 100644 --- a/packages/core-js/es/index.js +++ b/packages/core-js/es/index.js @@ -102,6 +102,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'); diff --git a/packages/core-js/es/regexp/index.js b/packages/core-js/es/regexp/index.js index 69467e49e33c..ac93a6cde70a 100644 --- a/packages/core-js/es/regexp/index.js +++ b/packages/core-js/es/regexp/index.js @@ -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'); diff --git a/packages/core-js/es/regexp/sticky.js b/packages/core-js/es/regexp/sticky.js new file mode 100644 index 000000000000..eb33fb10565b --- /dev/null +++ b/packages/core-js/es/regexp/sticky.js @@ -0,0 +1,5 @@ +require('../../modules/es.regexp.sticky'); + +module.exports = function (it) { + return it.sticky; +}; diff --git a/packages/core-js/modules/es.regexp.constructor.js b/packages/core-js/modules/es.regexp.constructor.js index 8689fd1e72b8..ea0c5cb76bbe 100644 --- a/packages/core-js/modules/es.regexp.constructor.js +++ b/packages/core-js/modules/es.regexp.constructor.js @@ -11,9 +11,7 @@ var getFlags = require('../internals/regexp-flags'); var stickyHelpers = require('../internals/regexp-sticky-helpers'); var redefine = require('../internals/redefine'); var fails = require('../internals/fails'); -var InternalStateModule = require('../internals/internal-state'); -var getInternalState = InternalStateModule.get; -var setInternalState = InternalStateModule.set; +var setInternalState = require('../internals/internal-state').set; var NativeRegExp = global.RegExp; var RegExpPrototype = NativeRegExp.prototype; var re1 = /a/g; @@ -78,21 +76,7 @@ if (FORCED) { RegExpWrapper.prototype = RegExpPrototype; redefine(global, 'RegExp', RegExpWrapper); - if (UNSUPPORTED_Y) { - hide(RegExpWrapper, 'sham', true); - defineProperty(RegExpPrototype, 'sticky', { - configurable: true, - get: function () { - // We can't use InternalStateModule.getterFor because - // we don't add metadata for regexps created by a literal. - if (!(this instanceof RegExpWrapper) && this !== RegExpPrototype) { - throw TypeError('Incompatible receiver, RegExp required'); - } - var state = getInternalState(this); - return state.sticky; - } - }); - } + if (UNSUPPORTED_Y) hide(RegExpWrapper, 'sham', true); } // https://tc39.github.io/ecma262/#sec-get-regexp-@@species diff --git a/packages/core-js/modules/es.regexp.sticky.js b/packages/core-js/modules/es.regexp.sticky.js new file mode 100644 index 000000000000..02da204fef27 --- /dev/null +++ b/packages/core-js/modules/es.regexp.sticky.js @@ -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'); + } + }); +} diff --git a/tests/compat/tests.js b/tests/compat/tests.js index b7151676763e..549dbab837a5 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -710,6 +710,13 @@ GLOBAL.tests = { 'es.regexp.flags': function () { return /./g.flags === 'g'; }, + 'es.regexp.sticky': function () { + try { + return new RegExp('a', 'y').sticky === true; + } catch (e) { + return false; + } + }, 'es.regexp.to-string': function () { return RegExp.prototype.toString.call({ source: 'a', flags: 'b' }) === '/a/b' && RegExp.prototype.toString.name === 'toString'; diff --git a/tests/tests/es.regexp.exec.js b/tests/tests/es.regexp.exec.js index cef8788c71a9..cd4b8d5e1969 100644 --- a/tests/tests/es.regexp.exec.js +++ b/tests/tests/es.regexp.exec.js @@ -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'); @@ -27,32 +29,34 @@ QUnit.test('RegExp#exec capturing groups', assert => { // assert.deepEqual(/(a?)?/.exec('x'), ['', undefined], '/(a?)?/.exec("x") returns ["", undefined]'); }); -QUnit.test('RegExp#exec sticky', assert => { - const re = new RegExp('a', 'y'); - const str = 'bbabaab'; - assert.strictEqual(re.lastIndex, 0, '#1'); +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'); + 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 = 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'); + 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'); + 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'); + 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.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'); -}); + assert.strictEqual(re.exec(str), null, '#14'); + assert.strictEqual(re.lastIndex, 0, '#15'); + }); +} diff --git a/tests/tests/es.regexp.flags.js b/tests/tests/es.regexp.flags.js index 14c2b978caed..5098080344b8 100644 --- a/tests/tests/es.regexp.flags.js +++ b/tests/tests/es.regexp.flags.js @@ -12,30 +12,4 @@ if (DESCRIPTORS) { assert.strictEqual(/./mig.flags, 'gim', '/./mig.flags is "gim"'); assert.strictEqual(/./mgi.flags, 'gim', '/./mgi.flags is "gim"'); }); - - QUnit.test('RegExp#sticky', assert => { - const re = new RegExp('a', 'y'); - assert.ok(re.sticky, '.sticky is true'); - assert.strictEqual(re.flags, 'y', '.flags contains y'); - - assert.ok(Object.hasOwnProperty.call(RegExp.prototype, 'sticky'), 'prototype has .sticky property'); - assert.strictEqual(RegExp.prototype.sticky, undefined, '.sticky is undefined on prototype'); - - const stickyGetter = Object.getOwnPropertyDescriptor(RegExp.prototype, 'sticky').get; - assert.throws(() => { - stickyGetter.call({}); - }, undefined, '.sticky getter can only be called on RegExp instances'); - try { - stickyGetter.call(/a/); - assert.ok(true, '.sticky getter works on literals'); - } catch (e) { - assert.ok(false, '.sticky getter works on literals'); - } - try { - stickyGetter.call(new RegExp('a')); - assert.ok(true, '.sticky getter works on literals'); - } catch (e) { - assert.ok(false, '.sticky getter works on literals'); - } - }); } diff --git a/tests/tests/es.regexp.sticky.js b/tests/tests/es.regexp.sticky.js new file mode 100644 index 000000000000..bbf4b061d955 --- /dev/null +++ b/tests/tests/es.regexp.sticky.js @@ -0,0 +1,36 @@ +import { DESCRIPTORS } from '../helpers/constants'; + +if (DESCRIPTORS) { + QUnit.test('RegExp#sticky', assert => { + const re = new RegExp('a', 'y'); + assert.strictEqual(re.sticky, true, '.sticky is true'); + assert.strictEqual(re.flags, 'y', '.flags contains y'); + assert.strictEqual(/a/.sticky, false); + + const stickyGetter = Object.getOwnPropertyDescriptor(RegExp.prototype, 'sticky').get; + if (typeof stickyGetter === 'function') { + // Old firefox versions set a non-configurable non-writable .sticky property + // It works correctly, but it isn't a getter and it can't be polyfilled. + // We need to skip these tests. + + assert.throws(() => { + stickyGetter.call({}); + }, undefined, '.sticky getter can only be called on RegExp instances'); + try { + stickyGetter.call(/a/); + assert.ok(true, '.sticky getter works on literals'); + } catch (e) { + assert.ok(false, '.sticky getter works on literals'); + } + try { + stickyGetter.call(new RegExp('a')); + assert.ok(true, '.sticky getter works on instances'); + } catch (e) { + assert.ok(false, '.sticky getter works on instances'); + } + + assert.ok(Object.hasOwnProperty.call(RegExp.prototype, 'sticky'), 'prototype has .sticky property'); + assert.strictEqual(RegExp.prototype.sticky, undefined, '.sticky is undefined on prototype'); + } + }); +} diff --git a/tests/tests/index.js b/tests/tests/index.js index 5fabb11224e1..56a965d6345f 100644 --- a/tests/tests/index.js +++ b/tests/tests/index.js @@ -108,6 +108,7 @@ import './es.reflect.set'; import './es.regexp.constructor'; import './es.regexp.exec'; import './es.regexp.flags'; +import './es.regexp.sticky'; import './es.regexp.to-string'; import './es.set'; import './es.string.anchor'; From f93b81f37b10fb6d215c1319df94edca417e9301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 13 Mar 2019 21:44:54 +0100 Subject: [PATCH 09/11] Remove opera --- packages/core-js-compat/src/data.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core-js-compat/src/data.js b/packages/core-js-compat/src/data.js index abefda4616ab..b52961c5fed5 100644 --- a/packages/core-js-compat/src/data.js +++ b/packages/core-js-compat/src/data.js @@ -791,7 +791,6 @@ module.exports = { chrome: '49', edge: '13', firefox: '3', - opera: '36', safari: '10.0', }, 'es.regexp.to-string': { From c02cdf308c37e05ad392b17d3f2a36911c4edb07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Mon, 18 Mar 2019 21:46:45 +0100 Subject: [PATCH 10/11] Update compat tests --- tests/compat/tests.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/compat/tests.js b/tests/compat/tests.js index 549dbab837a5..699b62abfe18 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -697,25 +697,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 () { - try { - return new RegExp('a', 'y').sticky === true; - } catch (e) { - return false; - } + return new RegExp('a', 'y').sticky === true; }, 'es.regexp.to-string': function () { return RegExp.prototype.toString.call({ source: 'a', flags: 'b' }) === '/a/b' From 698daaadbec4aab0a72d494382a4f6125b1aed08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Mon, 18 Mar 2019 23:26:33 +0100 Subject: [PATCH 11/11] Fix IE8 --- .../internals/regexp-sticky-helpers.js | 19 +++++++++++++++++++ packages/core-js/modules/es.string.split.js | 12 +++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/core-js/internals/regexp-sticky-helpers.js b/packages/core-js/internals/regexp-sticky-helpers.js index da7641bb79f7..3292daa6856d 100644 --- a/packages/core-js/internals/regexp-sticky-helpers.js +++ b/packages/core-js/internals/regexp-sticky-helpers.js @@ -1,6 +1,7 @@ '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. @@ -21,3 +22,21 @@ exports.BROKEN_CARET = fails(function () { 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; +}; diff --git a/packages/core-js/modules/es.string.split.js b/packages/core-js/modules/es.string.split.js index ac0be96279db..774454dac511 100644 --- a/packages/core-js/modules/es.string.split.js +++ b/packages/core-js/modules/es.string.split.js @@ -3,11 +3,11 @@ var isRegExp = require('../internals/is-regexp'); var anObject = require('../internals/an-object'); var requireObjectCoercible = require('../internals/require-object-coercible'); -var speciesConstructor = require('../internals/species-constructor'); var advanceStringIndex = require('../internals/advance-string-index'); var toLength = require('../internals/to-length'); var callRegExpExec = require('../internals/regexp-exec-abstract'); var regexpExec = require('../internals/regexp-exec'); +var stickyHelpers = require('../internals/regexp-sticky-helpers'); var arrayPush = [].push; var min = Math.min; var MAX_UINT32 = 0xffffffff; @@ -89,15 +89,13 @@ require('../internals/fix-regexp-well-known-symbol-logic')( var rx = anObject(regexp); var S = String(this); - var C = speciesConstructor(rx, RegExp); var unicodeMatching = rx.unicode; - var flags = (rx.ignoreCase ? 'i' : '') + - (rx.multiline ? 'm' : '') + - (rx.unicode ? 'u' : '') + - 'y'; + var splitter = stickyHelpers.createStickyRegExp( + rx, + (rx.ignoreCase ? 'i' : '') + (rx.multiline ? 'm' : '') + (unicodeMatching ? 'u' : '') + ); - var splitter = new C(rx, flags); var lim = limit === undefined ? MAX_UINT32 : limit >>> 0; if (lim === 0) return []; if (S.length === 0) return callRegExpExec(splitter, S) === null ? [S] : [];