diff --git a/packages/core-js-builder/config.js b/packages/core-js-builder/config.js index ecaaa31f9d96..75b3b7181c78 100644 --- a/packages/core-js-builder/config.js +++ b/packages/core-js-builder/config.js @@ -95,6 +95,7 @@ module.exports = { 'es.string.sub', 'es.string.sup', 'es.regexp.constructor', + 'es.regexp.exec', 'es.regexp.flags', 'es.regexp.to-string', 'es.parse-int', diff --git a/packages/core-js/es/index.js b/packages/core-js/es/index.js index 8e08a7494c1b..2dbc434eb84a 100644 --- a/packages/core-js/es/index.js +++ b/packages/core-js/es/index.js @@ -80,6 +80,7 @@ require('../modules/es.string.strike'); require('../modules/es.string.sub'); require('../modules/es.string.sup'); require('../modules/es.regexp.constructor'); +require('../modules/es.regexp.exec'); require('../modules/es.regexp.flags'); require('../modules/es.regexp.to-string'); require('../modules/es.parse-int'); diff --git a/packages/core-js/es/regexp/index.js b/packages/core-js/es/regexp/index.js index 6ea2b737da87..69467e49e33c 100644 --- a/packages/core-js/es/regexp/index.js +++ b/packages/core-js/es/regexp/index.js @@ -1,5 +1,6 @@ require('../../modules/es.regexp.constructor'); require('../../modules/es.regexp.to-string'); +require('../../modules/es.regexp.exec'); require('../../modules/es.regexp.flags'); require('../../modules/es.string.match'); require('../../modules/es.string.replace'); diff --git a/packages/core-js/index.js b/packages/core-js/index.js index 807cc5002df8..ef9b4d2706c4 100644 --- a/packages/core-js/index.js +++ b/packages/core-js/index.js @@ -92,6 +92,7 @@ require('./modules/es.string.strike'); require('./modules/es.string.sub'); require('./modules/es.string.sup'); require('./modules/es.regexp.constructor'); +require('./modules/es.regexp.exec'); require('./modules/es.regexp.flags'); require('./modules/es.regexp.to-string'); require('./modules/es.parse-int'); 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 4b13ad22f0ca..53ee44fd95a6 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 @@ -4,18 +4,21 @@ var redefine = require('../internals/redefine'); var fails = require('../internals/fails'); var requireObjectCoercible = require('../internals/require-object-coercible'); var wellKnownSymbol = require('../internals/well-known-symbol'); +var regexpExec = require('../internals/regexp-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/; @@ -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. @@ -45,11 +48,39 @@ module.exports = function (KEY, length, exec, sham) { return ''.replace(re, '$') !== '7'; }); - if (!delegates || (KEY === 'replace' && !replaceSupportsNamedGroups)) { - var methods = exec(requireObjectCoercible, SYMBOL, ''[KEY], /./[SYMBOL], { - delegates: delegates, - replaceSupportsNamedGroups: replaceSupportsNamedGroups - }); + var splitWorksWithOverwrittenExec = KEY === 'split' && (function () { + // 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 === regexpExec.impl) { + 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]; diff --git a/packages/core-js/internals/regexp-exec-abstract.js b/packages/core-js/internals/regexp-exec-abstract.js new file mode 100644 index 000000000000..5e1645bb667e --- /dev/null +++ b/packages/core-js/internals/regexp-exec-abstract.js @@ -0,0 +1,22 @@ +var classof = require('./classof-raw'); +var regexpExec = require('./regexp-exec'); + +// `RegExpExec` abstract operation +// https://tc39.github.io/ecma262/#sec-regexpexec +module.exports = function (R, S) { + var exec = R.exec; + if (typeof exec === 'function') { + var result = exec.call(R, S); + if (typeof result !== 'object') { + throw new TypeError('RegExp exec method returned something other than an Object or null'); + } + return result; + } + + if (classof(R) !== 'RegExp') { + throw new TypeError('RegExp#exec called on incompatible receiver'); + } + + return regexpExec.impl.call(R, S); +}; + diff --git a/packages/core-js/internals/regexp-exec.js b/packages/core-js/internals/regexp-exec.js index 7e8658a023fe..9eae2c437b4e 100644 --- a/packages/core-js/internals/regexp-exec.js +++ b/packages/core-js/internals/regexp-exec.js @@ -1,22 +1,63 @@ -var classof = require('../internals/classof-raw'); -var builtinExec = RegExp.prototype.exec; - -// `RegExpExec` abstract operation -// https://tc39.github.io/ecma262/#sec-regexpexec -module.exports = function (R, S) { - var exec = R.exec; - if (typeof exec === 'function') { - var result = exec.call(R, S); - if (typeof result !== 'object') { - throw new TypeError('RegExp exec method returned something other than an Object or null'); +'use strict'; + +var regexpFlags = require('./regexp-flags'); + +var nativeExec = RegExp.prototype.exec; +// This always refers to the native implementation, because the +// String#replace polyfill uses ./fix-regexp-well-known-symbol-logic.js, +// which loads this file before patching the method. +var nativeReplace = String.prototype.replace; + +var patchedExec = nativeExec; + +var LAST_INDEX = 'lastIndex'; +var LENGTH = 'length'; + +var UPDATES_LAST_INDEX_WRONG = (function () { + var re1 = /a/, + re2 = /b*/g; + nativeExec.call(re1, 'a'); + nativeExec.call(re2, 'a'); + return re1[LAST_INDEX] !== 0 || re2[LAST_INDEX] !== 0; +})(); + +// 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; + +if (patch) { + patchedExec = function exec(str) { + var re = this; + var lastIndex, reCopy, match, i; + + if (NPCG_INCLUDED) { + reCopy = new RegExp('^' + re.source + '$(?!\\s)', regexpFlags.call(re)); } - return result; - } + if (UPDATES_LAST_INDEX_WRONG) lastIndex = re[LAST_INDEX]; - if (classof(R) !== 'RegExp') { - throw new TypeError('RegExp#exec called on incompatible receiver'); - } + match = nativeExec.call(re, str); - return builtinExec.call(R, S); -}; + if (UPDATES_LAST_INDEX_WRONG && match) { + re[LAST_INDEX] = re.global ? match.index + match[0][LENGTH] : lastIndex; + } + if (NPCG_INCLUDED && match && match[LENGTH] > 1) { + // Fix browsers whose `exec` methods don't consistently return `undefined` + // for NPCG, like IE8. NOTE: This doesn' work for /(.?)?/ + // eslint-disable-next-line no-loop-func + nativeReplace.call(match[0], reCopy, function () { + for (i = 1; i < arguments[LENGTH] - 2; i++) { + if (arguments[i] === undefined) match[i] = undefined; + } + }); + } + + return match; + }; +} +module.exports = { + orig: nativeExec, + impl: patchedExec, + patched: patch +}; diff --git a/packages/core-js/modules/es.regexp.exec.js b/packages/core-js/modules/es.regexp.exec.js new file mode 100644 index 000000000000..52cbeebbe36e --- /dev/null +++ b/packages/core-js/modules/es.regexp.exec.js @@ -0,0 +1,11 @@ +'use strict'; + +var regexpExec = require('../internals/regexp-exec'); + +require('../internals/export')({ + target: 'RegExp', + proto: true, + forced: regexpExec.patched +}, { + exec: regexpExec.impl +}); diff --git a/packages/core-js/modules/es.string.match.js b/packages/core-js/modules/es.string.match.js index a396ab7caf35..f0c35baaf7d2 100644 --- a/packages/core-js/modules/es.string.match.js +++ b/packages/core-js/modules/es.string.match.js @@ -3,41 +3,45 @@ 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; +var regExpExec = require('../internals/regexp-exec-abstract'); // @@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); + 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; - } - ]; -}); + ]; + } +); diff --git a/packages/core-js/modules/es.string.replace.js b/packages/core-js/modules/es.string.replace.js index 6e85087666f0..c33906ba4903 100644 --- a/packages/core-js/modules/es.string.replace.js +++ b/packages/core-js/modules/es.string.replace.js @@ -5,8 +5,7 @@ var toObject = require('../internals/to-object'); 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 regExpExec = require('../internals/regexp-exec-abstract'); var max = Math.max; var min = Math.min; var floor = Math.floor; @@ -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 @@ -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); @@ -75,7 +67,13 @@ require('../internals/fix-regexp-well-known-symbol-logic')( var matched = String(result[0]); var position = max(min(toInteger(result.index), S.length), 0); - var captures = result.slice(1).map(maybeToString); + var captures = []; + // NOTE: This is equivalent to + // captures = result.slice(1).map(maybeToString) + // but for some reason `nativeSlice.call(result, 1, result.length)` (called in + // the slice polyfill when slicing native arrays) "doesn't work" in safari 9 and + // causes a crash (https://pastebin.com/N21QzeQA) when trying to debug it. + for (var j = 1; j < result.length; j++) captures.push(maybeToString(result[j])); var namedCaptures = result.groups; if (functionalReplace) { var replacerArgs = [matched].concat(captures, position, S); @@ -104,7 +102,7 @@ require('../internals/fix-regexp-well-known-symbol-logic')( } return nativeReplace.call(replacement, symbols, function (match, ch) { var capture; - switch (ch[0]) { + switch (ch.charAt(0)) { case '$': return '$'; case '&': return matched; case '`': return str.slice(0, position); @@ -118,7 +116,7 @@ require('../internals/fix-regexp-well-known-symbol-logic')( if (n > m) { var f = floor(n / 10); if (f === 0) return ch; - if (f <= m) return captures[f - 1] === undefined ? ch[1] : captures[f - 1] + ch[1]; + if (f <= m) return captures[f - 1] === undefined ? ch.charAt(1) : captures[f - 1] + ch.charAt(1); return ch; } capture = captures[n - 1]; diff --git a/packages/core-js/modules/es.string.search.js b/packages/core-js/modules/es.string.search.js index d12f70bd650c..f39d4c2eccb4 100644 --- a/packages/core-js/modules/es.string.search.js +++ b/packages/core-js/modules/es.string.search.js @@ -2,32 +2,36 @@ var anObject = require('../internals/an-object'); var sameValue = require('../internals/same-value'); -var regExpExec = require('../internals/regexp-exec'); -var nativeExec = RegExp.prototype.exec; +var regExpExec = require('../internals/regexp-exec-abstract'); // @@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; + } + ]; + } +); diff --git a/packages/core-js/modules/es.string.split.js b/packages/core-js/modules/es.string.split.js index 420477890d38..878219225c08 100644 --- a/packages/core-js/modules/es.string.split.js +++ b/packages/core-js/modules/es.string.split.js @@ -5,8 +5,8 @@ var anObject = require('../internals/an-object'); var speciesConstructor = require('../internals/species-constructor'); var advanceStringIndex = require('../internals/advance-string-index'); var toLength = require('../internals/to-length'); -var regExpExec = require('../internals/regexp-exec'); -var nativeExec = RegExp.prototype.exec; +var callRegExpExec = require('../internals/regexp-exec-abstract'); +var regexpExec = require('../internals/regexp-exec'); var arrayPush = [].push; var min = Math.min; var LENGTH = 'length'; @@ -15,127 +15,121 @@ var LENGTH = 'length'; var SUPPORTS_Y = !!(function () { try { return new RegExp('x', 'y'); } catch (e) {} })(); // @@split logic -require('../internals/fix-regexp-well-known-symbol-logic')('split', 2, function (defined, SPLIT, nativeSplit) { - var internalSplit = nativeSplit; - if ( - 'abbc'.split(/(b)*/)[1] == 'c' || - 'test'.split(/(?:)/, -1)[LENGTH] != 4 || - 'ab'.split(/(?:ab)*/)[LENGTH] != 2 || - '.'.split(/(.?)(.?)/)[LENGTH] != 4 || - '.'.split(/()()/)[LENGTH] > 1 || - ''.split(/.?/)[LENGTH] - ) { - var NPCG = /()??/.exec('')[1] === undefined; // nonparticipating capturing group - // based on es5-shim implementation, need to rework it - internalSplit = function (separator, limit) { - var string = String(this); - if (separator === undefined && limit === 0) return []; - // If `separator` is not a regex, use native split - if (!isRegExp(separator)) { - return nativeSplit.call(string, separator, limit !== undefined ? limit >>> 0 : 4294967295); - } - var output = []; - var flags = (separator.ignoreCase ? 'i' : '') + - (separator.multiline ? 'm' : '') + - (separator.unicode ? 'u' : '') + - (separator.sticky ? 'y' : ''); - var lastLastIndex = 0; - var splitLimit = limit === undefined ? 4294967295 : limit >>> 0; - // Make `global` and avoid `lastIndex` issues by working with a copy - var separatorCopy = new RegExp(separator.source, flags + 'g'); - var separator2, match, lastIndex, lastLength, i; - // Doesn't need flags gy, but they don't hurt - if (!NPCG) separator2 = new RegExp('^' + separatorCopy.source + '$(?!\\s)', flags); - while (match = separatorCopy.exec(string)) { - // `separatorCopy.lastIndex` is not reliable cross-browser - lastIndex = match.index + match[0][LENGTH]; - if (lastIndex > lastLastIndex) { - output.push(string.slice(lastLastIndex, match.index)); - // Fix browsers whose `exec` methods don't consistently return `undefined` for NPCG - // eslint-disable-next-line no-loop-func - if (!NPCG && match[LENGTH] > 1) match[0].replace(separator2, function () { - for (i = 1; i < arguments[LENGTH] - 2; i++) if (arguments[i] === undefined) match[i] = undefined; - }); - if (match[LENGTH] > 1 && match.index < string[LENGTH]) arrayPush.apply(output, match.slice(1)); - lastLength = match[0][LENGTH]; - lastLastIndex = lastIndex; - if (output[LENGTH] >= splitLimit) break; +require('../internals/fix-regexp-well-known-symbol-logic')( + 'split', + 2, + function (defined, SPLIT, nativeSplit, maybeCallNative) { + var internalSplit = nativeSplit; + if ( + 'abbc'.split(/(b)*/)[1] == 'c' || + 'test'.split(/(?:)/, -1)[LENGTH] != 4 || + 'ab'.split(/(?:ab)*/)[LENGTH] != 2 || + '.'.split(/(.?)(.?)/)[LENGTH] != 4 || + '.'.split(/()()/)[LENGTH] > 1 || + ''.split(/.?/)[LENGTH] + ) { + // based on es5-shim implementation, need to rework it + internalSplit = function (separator, limit) { + var string = String(this); + if (separator === undefined && limit === 0) return []; + // If `separator` is not a regex, use native split + if (!isRegExp(separator)) { + return nativeSplit.call(string, separator, limit !== undefined ? limit >>> 0 : 4294967295); } - if (separatorCopy.lastIndex === match.index) separatorCopy.lastIndex++; // Avoid an infinite loop - } - if (lastLastIndex === string[LENGTH]) { - if (lastLength || !separatorCopy.test('')) output.push(''); - } else output.push(string.slice(lastLastIndex)); - return output[LENGTH] > splitLimit ? output.slice(0, splitLimit) : output; - }; - // Chakra, V8 - } else if ('0'.split(undefined, 0)[LENGTH]) { - internalSplit = function (separator, limit) { - return separator === undefined && limit === 0 ? [] : nativeSplit.call(this, separator, limit); - }; - } + var output = []; + var flags = (separator.ignoreCase ? 'i' : '') + + (separator.multiline ? 'm' : '') + + (separator.unicode ? 'u' : '') + + (separator.sticky ? 'y' : ''); + var lastLastIndex = 0; + var splitLimit = limit === undefined ? 4294967295 : limit >>> 0; + // Make `global` and avoid `lastIndex` issues by working with a copy + var separatorCopy = new RegExp(separator.source, flags + 'g'); + var match, lastIndex, lastLength; + while (match = regexpExec.impl.call(separatorCopy, string)) { + lastIndex = separatorCopy.lastIndex; + if (lastIndex > lastLastIndex) { + output.push(string.slice(lastLastIndex, match.index)); + if (match[LENGTH] > 1 && match.index < string[LENGTH]) arrayPush.apply(output, match.slice(1)); + lastLength = match[0][LENGTH]; + lastLastIndex = lastIndex; + if (output[LENGTH] >= splitLimit) break; + } + if (separatorCopy.lastIndex === match.index) separatorCopy.lastIndex++; // Avoid an infinite loop + } + if (lastLastIndex === string[LENGTH]) { + if (lastLength || !separatorCopy.test('')) output.push(''); + } else output.push(string.slice(lastLastIndex)); + return output[LENGTH] > splitLimit ? output.slice(0, splitLimit) : output; + }; + // Chakra, V8 + } else if ('0'.split(undefined, 0)[LENGTH]) { + internalSplit = function (separator, limit) { + return separator === undefined && limit === 0 ? [] : nativeSplit.call(this, separator, limit); + }; + } - return [ - // `String.prototype.split` method - // https://tc39.github.io/ecma262/#sec-string.prototype.split - function split(separator, limit) { - var O = defined(this); - var splitter = separator == undefined ? undefined : separator[SPLIT]; - return splitter !== undefined - ? splitter.call(separator, O, limit) - : internalSplit.call(String(O), separator, limit); - }, - // `RegExp.prototype[@@split]` method - // https://tc39.github.io/ecma262/#sec-regexp.prototype-@@split - // - // NOTE: This cannot be properly polyfilled in engines that don't support - // the 'y' flag. - function (regexp, limit) { - // We can never use `internalSplit` if exec has been changed, because - // internalSplit contains workarounds for things which might have been - // purposely changed by the developer. - if (regexp.exec === nativeExec) return internalSplit.call(this, regexp, limit); + return [ + // `String.prototype.split` method + // https://tc39.github.io/ecma262/#sec-string.prototype.split + function split(separator, limit) { + var O = defined(this); + var splitter = separator == undefined ? undefined : separator[SPLIT]; + return splitter !== undefined + ? splitter.call(separator, O, limit) + : internalSplit.call(String(O), separator, limit); + }, + // `RegExp.prototype[@@split]` method + // https://tc39.github.io/ecma262/#sec-regexp.prototype-@@split + // + // NOTE: This cannot be properly polyfilled in engines that don't support + // the 'y' flag. + function (regexp, limit) { + var res = maybeCallNative(internalSplit, regexp, this, limit, internalSplit !== nativeSplit); + if (res.done) return res.value; - var rx = anObject(regexp); - var S = String(this); - var C = speciesConstructor(rx, RegExp); + 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' : '') + - (SUPPORTS_Y ? 'y' : 'g'); + var unicodeMatching = rx.unicode; + var flags = (rx.ignoreCase ? 'i' : '') + + (rx.multiline ? 'm' : '') + + (rx.unicode ? 'u' : '') + + (SUPPORTS_Y ? 'y' : 'g'); - // ^(? + 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 lim = limit === undefined ? 0xffffffff : limit >>> 0; - if (lim === 0) return []; - if (S.length === 0) return regExpExec(splitter, S) === null ? [S] : []; - var p = 0; - var q = 0; - var A = []; - while (q < S.length) { - splitter.lastIndex = SUPPORTS_Y ? q : 0; - var z = regExpExec(splitter, SUPPORTS_Y ? S : S.slice(q)); - var e; - if ( - z === null || - (e = min(toLength(splitter.lastIndex + (SUPPORTS_Y ? 0 : q)), S.length)) === p - ) { - q = advanceStringIndex(S, q, unicodeMatching); - } else { - A.push(S.slice(p, q)); - if (A.length === lim) return A; - for (var i = 1; i <= z.length - 1; i++) { - A.push(z[i]); + // ^(? + 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 lim = limit === undefined ? 0xffffffff : limit >>> 0; + if (lim === 0) return []; + if (S.length === 0) return callRegExpExec(splitter, S) === null ? [S] : []; + var p = 0; + 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)); + var e; + if ( + z === null || + (e = min(toLength(splitter.lastIndex + (SUPPORTS_Y ? 0 : q)), S.length)) === p + ) { + q = advanceStringIndex(S, q, unicodeMatching); + } else { + A.push(S.slice(p, q)); if (A.length === lim) return A; + for (var i = 1; i <= z.length - 1; i++) { + A.push(z[i]); + if (A.length === lim) return A; + } + q = p = e; } - q = p = e; } + A.push(S.slice(p)); + return A; } - A.push(S.slice(p)); - return A; - } - ]; -}, !SUPPORTS_Y); + ]; + }, + !SUPPORTS_Y +); diff --git a/tests/helpers/helpers.js b/tests/helpers/helpers.js index 2fb92f40d0ee..3095b09c7993 100644 --- a/tests/helpers/helpers.js +++ b/tests/helpers/helpers.js @@ -61,8 +61,8 @@ export function patchRegExp$exec(run) { return assert => { const originalExec = RegExp.prototype.exec; // eslint-disable-next-line no-extend-native - RegExp.prototype.exec = function (...args) { - return originalExec.apply(this, args); + RegExp.prototype.exec = function () { + return originalExec.apply(this, arguments); }; try { return run(assert); diff --git a/tests/tests/es.regexp.exec.js b/tests/tests/es.regexp.exec.js new file mode 100644 index 000000000000..fd9222a01373 --- /dev/null +++ b/tests/tests/es.regexp.exec.js @@ -0,0 +1,28 @@ +QUnit.test('RegExp#exec lastIndex updating', assert => { + let re = /b/; + assert.strictEqual(re.lastIndex, 0, '.lastIndex starts at 0 for non-global regexps'); + re.exec('abc'); + assert.strictEqual(re.lastIndex, 0, '.lastIndex isn\'t updated for non-global regexps'); + + re = /b/g; + assert.strictEqual(re.lastIndex, 0, '.lastIndex starts at 0 for global regexps'); + re.exec('abc'); + assert.strictEqual(re.lastIndex, 2, '.lastIndex is updated for global regexps'); + + re = /b*/; + re.exec('a'); + assert.strictEqual(re.lastIndex, 0, '.lastIndex isn\'t updated for non-global regexps if the match is empty'); + + re = /b*/g; + re.exec('a'); + assert.strictEqual(re.lastIndex, 0, '.lastIndex isn\'t updated for global regexps if the match is empty'); +}); + +QUnit.test('RegExp#exec capturing groups', assert => { + assert.deepEqual(/(a?)/.exec('x'), ['', ''], '/(a?)/.exec("x") returns ["", ""]'); + assert.deepEqual(/(a)?/.exec('x'), ['', undefined], '/(a)?/.exec("x") returns ["", undefined]'); + + // @nicolo-ribaudo: I don't know how to fix this in IE8. For the `/(a)?/` case it is fixed using + // #replace, but here also #replace is buggy :( + // assert.deepEqual(/(a?)?/.exec('x'), ['', undefined], '/(a?)?/.exec("x") returns ["", undefined]'); +}); diff --git a/tests/tests/index.js b/tests/tests/index.js index 08c438b0aeea..fed422b61e33 100644 --- a/tests/tests/index.js +++ b/tests/tests/index.js @@ -103,6 +103,7 @@ import './es.reflect.prevent-extensions'; import './es.reflect.set-prototype-of'; import './es.reflect.set'; import './es.regexp.constructor'; +import './es.regexp.exec'; import './es.regexp.flags'; import './es.regexp.to-string'; import './es.set';