diff --git a/.eslintrc b/.eslintrc index 381d7ed..1342f1c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,30 +6,33 @@ "rules": { "complexity": [2, 12], "func-name-matching": 0, + "id-length": 0, "max-nested-callbacks": [2, 3], "max-params": [2, 4], "max-statements-per-line": [2, { "max": 2 }], "max-statements": [2, 24], "new-cap": [2, { "capIsNewExceptions": [ + "AdvanceStringIndex", + "Call", + "Construct", + "CreateIterResultObject", + "CreateRegExpStringIterator", + "Get", + "GetIntrinsic", + "GetMethod", + "Invoke", "IsRegExp", + "MatchAllIterator", + "ObjectCreate", + "RegExpExec", "RequireObjectCoercible", + "Set", + "SpeciesConstructor", "ToBoolean", "ToLength", "ToString", - "GetMethod", - "Call", - "MatchAllIterator", - "SpeciesConstructor", - "Get", - "Set", - "Construct", "Type", - "RegExpExec", - "CreateIterResultObject", - "AdvanceStringIndex", - "GetIntrinsic", - "ObjectCreate", ], }], "no-restricted-syntax": [2, "BreakStatement", "ContinueStatement", "DebuggerStatement", "LabeledStatement", "WithStatement"], diff --git a/implementation.js b/implementation.js index 3b0f2a9..b5da35e 100644 --- a/implementation.js +++ b/implementation.js @@ -3,7 +3,7 @@ var ES = require('es-abstract'); var hasSymbols = require('has-symbols')(); -var MatchAllIterator = require('./helpers/MatchAllIterator'); +var regexMatchAll = require('./regexp-matchall'); module.exports = function matchAll(regexp) { var O = ES.RequireObjectCoercible(this); @@ -12,11 +12,21 @@ module.exports = function matchAll(regexp) { var matcher; if (hasSymbols && typeof Symbol.matchAll === 'symbol') { matcher = ES.GetMethod(regexp, Symbol.matchAll); + } else if (ES.IsRegExp(regexp)) { + // fallback for pre-Symbol.matchAll environments + matcher = regexMatchAll; } if (typeof matcher !== 'undefined') { return ES.Call(matcher, regexp, [O]); } } - return MatchAllIterator(regexp, O); + var S = ES.ToString(O); + // var rx = ES.RegExpCreate(regexp, 'g'); + var rx = new RegExp(regexp, 'g'); + if (hasSymbols && typeof Symbol.matchAll === 'symbol') { + return ES.Invoke(rx, Symbol.matchAll, [S]); + } + // fallback for pre-Symbol.matchAll environments + return ES.Call(regexMatchAll, rx, [S]); }; diff --git a/regexp-matchall.js b/regexp-matchall.js index efa3e57..a9f26fc 100644 --- a/regexp-matchall.js +++ b/regexp-matchall.js @@ -1,14 +1,61 @@ 'use strict'; var ES = require('es-abstract'); -var MatchAllIterator = require('./helpers/MatchAllIterator'); +var flagsGetter = require('regexp.prototype.flags'); + +var RegExpStringIterator = require('./helpers/RegExpStringIterator'); +var OrigRegExp = RegExp; + +var CreateRegExpStringIterator = function CreateRegExpStringIterator(R, S, global, fullUnicode) { + if (ES.Type(S) !== 'String') { + throw new TypeError('"S" value must be a String'); + } + if (ES.Type(global) !== 'Boolean') { + throw new TypeError('"global" value must be a Boolean'); + } + if (ES.Type(fullUnicode) !== 'Boolean') { + throw new TypeError('"fullUnicode" value must be a Boolean'); + } + + var iterator = new RegExpStringIterator(R, S, global, fullUnicode); + return iterator; +}; + +var constructRegexWithFlags = function constructRegex(C, R) { + var matcher; + var flags = ES.Get(R, 'flags'); + if (typeof flags === 'string') { + matcher = new C(R, flags); + } else if (C === OrigRegExp) { + // workaround for older engines that lack RegExp.prototype.flags + flags = flagsGetter(R); + matcher = new C(R.source, flags); + } else { + flags = flagsGetter(R); + matcher = new C(R, flags); + } + return { flags: flags, matcher: matcher }; +}; var regexMatchAll = function SymbolMatchAll(string) { var R = this; if (ES.Type(R) !== 'Object') { throw new TypeError('"this" value must be an Object'); } - return MatchAllIterator(R, string); + var S = ES.ToString(string); + var C = ES.SpeciesConstructor(R, OrigRegExp); + + var tmp = constructRegexWithFlags(C, R); + // var flags = ES.ToString(ES.Get(R, 'flags')); + var flags = tmp.flags; + // var matcher = ES.Construct(C, [R, flags]); + var matcher = tmp.matcher; + + var lastIndex = ES.ToLength(ES.Get(R, 'lastIndex')); + ES.Set(matcher, 'lastIndex', lastIndex, true); + var global = flags.indexOf('g') > -1; + var fullUnicode = flags.indexOf('u') > -1; + return CreateRegExpStringIterator(matcher, S, global, fullUnicode); }; var defineP = Object.defineProperty; diff --git a/test/.eslintrc b/test/.eslintrc index bd20f63..1b1f85a 100644 --- a/test/.eslintrc +++ b/test/.eslintrc @@ -4,6 +4,7 @@ "array-element-newline": 0, "id-length": 0, "max-lines-per-function": 0, + "max-nested-callbacks": 0, "max-params": 0, "no-magic-numbers": 0, "operator-linebreak": 0, diff --git a/test/shimmed.js b/test/shimmed.js index 7caec19..37e82ed 100644 --- a/test/shimmed.js +++ b/test/shimmed.js @@ -34,6 +34,8 @@ test('shimmed', function (t) { t.test('Symbol.matchAll', { skip: !hasSymbols }, function (st) { st.equal(typeof Symbol.matchAll, 'symbol', 'Symbol.matchAll is a symbol'); + st.equal(typeof RegExp.prototype[Symbol.matchAll], 'function', 'Symbol.matchAll function is on RegExp.prototype'); + st.test('Function name', { skip: !functionsHaveNames }, function (s2t) { if (functionNamesConfigurable) { s2t.equal(RegExp.prototype[Symbol.matchAll].name, '[Symbol.matchAll]', 'RegExp.prototype[Symbol.matchAll] has name "[Symbol.matchAll]"'); @@ -43,6 +45,27 @@ test('shimmed', function (t) { s2t.end(); }); + st.test('no symbol present', function (s2t) { + var desc = Object.getOwnPropertyDescriptor(RegExp.prototype, Symbol.matchAll); + + s2t.doesNotThrow(function () { 'abc'.matchAll('b'); }, 'does not throw on string input, with the symbol on regex prototype'); + + // eslint-disable-next-line no-extend-native + Object.defineProperty(RegExp.prototype, Symbol.matchAll, { + configurable: true, + enumerable: false, + value: undefined, + writable: true + }); + + s2t['throws'](function () { 'abc'.matchAll('b'); }, 'throws on string input, without the symbol on regex prototype'); + + // eslint-disable-next-line no-extend-native + Object.defineProperty(RegExp.prototype, Symbol.matchAll, desc); + + s2t.end(); + }); + st.end(); });