Skip to content

Commit

Permalink
Merge pull request #411 from nicolo-ribaudo/regexp-es2015
Browse files Browse the repository at this point in the history
Make RegExp#[Symbol.*] methods call exec
  • Loading branch information
zloirock authored Aug 30, 2018
2 parents 0f5dd2b + 5c0cb8f commit 6addfef
Show file tree
Hide file tree
Showing 13 changed files with 546 additions and 59 deletions.
8 changes: 8 additions & 0 deletions packages/core-js/internals/advance-string-index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict';
var at = require('../internals/string-at')(true);

// `AdvanceStringIndex` abstract operation
// https://tc39.github.io/ecma262/#sec-advancestringindex
module.exports = function (S, index, unicode) {
return index + (unicode ? at(S, index).length : 1);
};
23 changes: 21 additions & 2 deletions packages/core-js/internals/fix-regexp-well-known-symbol-logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,43 @@ var fails = require('../internals/fails');
var requireObjectCoercible = require('../internals/require-object-coercible');
var wellKnownSymbol = require('../internals/well-known-symbol');

module.exports = function (KEY, length, exec) {
var SPECIES = wellKnownSymbol('species');

module.exports = function (KEY, length, exec, sham) {
var SYMBOL = wellKnownSymbol(KEY);
var methods = exec(requireObjectCoercible, SYMBOL, ''[KEY]);
var stringMethod = methods[0];
var regexMethod = methods[1];
if (fails(function () {
// String methods call symbol-named RegEp methods
var O = {};
O[SYMBOL] = function () { return 7; };
return ''[KEY](O) != 7;
}) || fails(function () {
// Symbol-named RegExp methods call .exec
var execCalled = false;
var re = /a/;
re.exec = function () { execCalled = true; return null; };

if (KEY === 'split') {
// RegExp[@@split] doesn't call the regex's exec method, but first creates
// a new one. We need to return the patched regex when creating the new one.
re.constructor = {};
re.constructor[SPECIES] = function () { return re; };
}

re[SYMBOL]('');
return !execCalled;
})) {
redefine(String.prototype, KEY, stringMethod);
hide(RegExp.prototype, SYMBOL, length == 2
redefine(RegExp.prototype, SYMBOL, length == 2
// 21.2.5.8 RegExp.prototype[@@replace](string, replaceValue)
// 21.2.5.11 RegExp.prototype[@@split](string, limit)
? function (string, arg) { return regexMethod.call(string, this, arg); }
// 21.2.5.6 RegExp.prototype[@@match](string)
// 21.2.5.9 RegExp.prototype[@@search](string)
: function (string) { return regexMethod.call(string, this); }
);
if (sham) hide(RegExp.prototype[SYMBOL], 'sham', true);
}
};
22 changes: 22 additions & 0 deletions packages/core-js/internals/regexp-exec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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');
}
return result;
}

if (classof(R) !== 'RegExp') {
throw new TypeError('RegExp#exec called on incompatible receiver');
}

return builtinExec.call(R, S);
};

46 changes: 39 additions & 7 deletions packages/core-js/modules/es.string.match.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,43 @@
'use strict';

var anObject = require('../internals/an-object');
var toLength = require('../internals/to-length');
var advanceStringIndex = require('../internals/advance-string-index');
var regExpExec = require('../internals/regexp-exec');
var nativeExec = RegExp.prototype.exec;

// @@match logic
require('../internals/fix-regexp-well-known-symbol-logic')('match', 1, function (defined, MATCH, nativeMatch) {
// `String.prototype.match` method
// https://tc39.github.io/ecma262/#sec-string.prototype.match
return [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));
}, 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);

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

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++;
}
return n === 0 ? null : A;
}
];
});
119 changes: 112 additions & 7 deletions packages/core-js/modules/es.string.replace.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,118 @@
'use strict';

var anObject = require('../internals/an-object');
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 max = Math.max;
var min = Math.min;
var floor = Math.floor;
var SUBSTITUTION_SYMBOLS = /\$([$&`']|\d\d?|<[^>]*>)/g;
var SUBSTITUTION_SYMBOLS_NO_NAMED = /\$([$&`']|\d\d?)/g;

var maybeToString = function (it) {
return it === undefined ? it : String(it);
};

// @@replace logic
require('../internals/fix-regexp-well-known-symbol-logic')('replace', 2, function (defined, REPLACE, nativeReplace) {
// `String.prototype.replace` method
// https://tc39.github.io/ecma262/#sec-string.prototype.replace
return [function replace(searchValue, replaceValue) {
var O = defined(this);
var replacer = searchValue == undefined ? undefined : searchValue[REPLACE];
return replacer !== undefined
return [
// `String.prototype.replace` method
// https://tc39.github.io/ecma262/#sec-string.prototype.replace
function replace(searchValue, replaceValue) {
var O = defined(this);
var replacer = searchValue == undefined ? undefined : searchValue[REPLACE];
return replacer !== undefined
? replacer.call(searchValue, O, replaceValue)
: nativeReplace.call(String(O), searchValue, replaceValue);
}, nativeReplace];
},
// `RegExp.prototype[@@replace]` method
// https://tc39.github.io/ecma262/#sec-regexp.prototype-@@replace
function (regexp, replaceValue) {
if (regexp.exec === nativeExec) return nativeReplace.call(this, regexp, replaceValue);

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

var functionalReplace = typeof replaceValue === 'function';
if (!functionalReplace) replaceValue = String(replaceValue);

var global = rx.global;
if (global) {
var fullUnicode = rx.unicode;
rx.lastIndex = 0;
}
var results = [];
while (true) {
var result = regExpExec(rx, S);
if (result === null) break;

results.push(result);
if (!global) break;

var matchStr = String(result[0]);
if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength(rx.lastIndex), fullUnicode);
}

var accumulatedResult = '';
var nextSourcePosition = 0;
for (var i = 0; i < results.length; i++) {
result = results[i];

var matched = String(result[0]);
var position = max(min(toInteger(result.index), S.length), 0);
var captures = result.slice(1).map(maybeToString);
var namedCaptures = result.groups;
if (functionalReplace) {
var replacerArgs = [matched].concat(captures, position, S);
if (namedCaptures !== undefined) replacerArgs.push(namedCaptures);
var replacement = String(replaceValue.apply(undefined, replacerArgs));
} else {
replacement = getSubstitution(matched, S, position, captures, namedCaptures, replaceValue);
}
if (position >= nextSourcePosition) {
accumulatedResult += S.slice(nextSourcePosition, position) + replacement;
nextSourcePosition = position + matched.length;
}
}
return accumulatedResult + S.slice(nextSourcePosition);
}
];

// https://tc39.github.io/ecma262/#sec-getsubstitution
function getSubstitution(matched, str, position, captures, namedCaptures, replacement) {
var tailPos = position + matched.length;
var m = captures.length;
var symbols = SUBSTITUTION_SYMBOLS_NO_NAMED;
if (namedCaptures !== undefined) {
namedCaptures = toObject(namedCaptures);
symbols = SUBSTITUTION_SYMBOLS;
}
return nativeReplace.call(replacement, symbols, function (match, ch) {
var capture;
switch (ch[0]) {
case '$': return '$';
case '&': return matched;
case '`': return str.slice(0, position);
case "'": return str.slice(tailPos);
case '<':
capture = namedCaptures[ch.slice(1, -1)];
break;
default: // \d\d?
var n = +ch;
if (n === 0) return ch;
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];
return ch;
}
capture = captures[n - 1];
}
return capture === undefined ? '' : capture;
});
}
});
36 changes: 29 additions & 7 deletions packages/core-js/modules/es.string.search.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
'use strict';

var anObject = require('../internals/an-object');
var sameValue = require('../internals/same-value');
var regExpExec = require('../internals/regexp-exec');
var nativeExec = RegExp.prototype.exec;

// @@search logic
require('../internals/fix-regexp-well-known-symbol-logic')('search', 1, function (defined, SEARCH, nativeSearch) {
// `String.prototype.search` method
// https://tc39.github.io/ecma262/#sec-string.prototype.search
return [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));
}, 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);

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;
}
];
});
89 changes: 78 additions & 11 deletions packages/core-js/modules/es.string.split.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
'use strict';

var isRegExp = require('../internals/is-regexp');
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 arrayPush = [].push;
var min = Math.min;
var LENGTH = 'length';

// eslint-disable-next-line no-empty
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 isRegExp = require('../internals/is-regexp');
var internalSplit = nativeSplit;
var arrayPush = [].push;
var LENGTH = 'length';
if (
'abbc'.split(/(b)*/)[1] == 'c' ||
'test'.split(/(?:)/, -1)[LENGTH] != 4 ||
Expand Down Expand Up @@ -62,13 +74,68 @@ require('../internals/fix-regexp-well-known-symbol-logic')('split', 2, function
return separator === undefined && limit === 0 ? [] : nativeSplit.call(this, separator, limit);
};
}
// `String.prototype.split` method
// https://tc39.github.io/ecma262/#sec-string.prototype.split
return [function split(separator, limit) {
var O = defined(this);
var splitter = separator == undefined ? undefined : separator[SPLIT];
return splitter !== undefined

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);
}, internalSplit];
});
},
// `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);

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');

// ^(? + 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]);
if (A.length === lim) return A;
}
q = p = e;
}
}
A.push(S.slice(p));
return A;
}
];
}, !SUPPORTS_Y);
Loading

0 comments on commit 6addfef

Please sign in to comment.