From 6d51f41cd867ff081d83099900ef0702a11df1a1 Mon Sep 17 00:00:00 2001 From: elitvyakov Date: Thu, 24 Nov 2016 13:52:17 +0300 Subject: [PATCH] es5-shim: #412 fix groups with quantifiers preventing its exercise --- es5-shim.js | 85 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 7 deletions(-) diff --git a/es5-shim.js b/es5-shim.js index bad9c942..72c9a29a 100644 --- a/es5-shim.js +++ b/es5-shim.js @@ -1882,23 +1882,94 @@ StringPrototype.replace = function replace(searchValue, replaceValue) { var isFn = isCallable(replaceValue); var hasCapturingGroups = isRegex(searchValue) && (/\)[*?]/).test(searchValue.source); + var extendedGroups = getExtendedGroups(searchValue); + if (!isFn || !hasCapturingGroups) { return str_replace.call(this, searchValue, replaceValue); } else { var wrappedReplaceValue = function (match) { - var length = arguments.length; - var originalLastIndex = searchValue.lastIndex; - searchValue.lastIndex = 0; - var args = searchValue.exec(match) || []; - searchValue.lastIndex = originalLastIndex; - pushCall(args, arguments[length - 2], arguments[length - 1]); - return replaceValue.apply(this, args); + var groups = ArrayPrototype.slice.call(arguments, 1, -2); + + for (var i = 0; i < groups.length; i++) { + var argumentIndex = i + 1; + if (groups[i] === '' && extendedGroups[i].mayBeMissing) { + arguments[argumentIndex] = undefined; + } + } + + return replaceValue.apply(this, arguments); }; return str_replace.call(this, searchValue, wrappedReplaceValue); } }; } + /** + * @param regexp {RegExp} + * @returns {Object[]} groups + * @return {number} groups[].number - The number of group. + * @return {boolean} groups[].mayBeMissing - The group with quantifiers preventing its exercise, the matched text + * for a capturing group is now undefined instead of an empty string. + * The group without quantifiers, may be empty string. + * @return {boolean} groups[].isResulting - The groups with ?:, ?=, ?! are non-capturing groups. + */ + function getExtendedGroups (regexp) { + var source = regexp.source; + var result = []; + var prevFirst, + prevSecond, + nextFirst, + nextSecond; + var groupNumberCounter = 0; + + for (var charIndex in source) { + var char = source[+charIndex]; + prevFirst = source[+charIndex - 1] || null; + prevSecond = source[+charIndex - 2] || null; + nextFirst = source[+charIndex + 1] || null; + nextSecond = source[+charIndex + 2] || null; + + if (char === '(') { + if (prevFirst && prevFirst === '\\') continue; + + if (nextFirst && nextFirst === '?' && nextSecond && nextSecond === ':' || + nextFirst && nextFirst === '?' && nextSecond && nextSecond === '=' || + nextFirst && nextFirst === '?' && nextSecond && nextSecond === '!') { + result.push({ + number: ++groupNumberCounter, + mayBeMissing: true, + isResulting: false + }); + continue; + } + + result.push({ + number: ++groupNumberCounter, + mayBeMissing: false, + isResulting: true + }); + } + + if (char === ')') { + var groupIndex = groupNumberCounter - 1; + + if (prevFirst && prevFirst === '\\') continue; + + if (result[groupIndex] && !result[groupIndex].isResulting) { + result.splice(groupIndex, 1); + groupNumberCounter = --groupNumberCounter; + continue; + } + + if (nextFirst && nextFirst === '?' || nextFirst === '*') { + result[groupIndex].mayBeMissing = true; + } + } + } + + return result; + } + // ECMA-262, 3rd B.2.3 // Not an ECMAScript standard, although ECMAScript 3rd Edition has a // non-normative section suggesting uniform semantics and it should be