/** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @providesModule enumerate * */ const KIND_KEYS = 'keys'; const KIND_VALUES = 'values'; const KIND_ENTRIES = 'entries'; /** * Specific Array iterators. */ const ArrayIterators = function () { let hasNative = hasNativeIterator(Array); let ArrayIterator; if (!hasNative) { ArrayIterator = class ArrayIterator { // 22.1.5.1 CreateArrayIterator Abstract Operation constructor(array, kind) { this._iteratedObject = array; this._kind = kind; this._nextIndex = 0; } // 22.1.5.2.1 %ArrayIteratorPrototype%.next() next() { if (this._iteratedObject == null) { return { value: undefined, done: true }; } let array = this._iteratedObject; let len = this._iteratedObject.length; let index = this._nextIndex; let kind = this._kind; if (index >= len) { this._iteratedObject = undefined; return { value: undefined, done: true }; } this._nextIndex = index + 1; if (kind === KIND_KEYS) { return { value: index, done: false }; } else if (kind === KIND_VALUES) { return { value: array[index], done: false }; } else if (kind === KIND_ENTRIES) { return { value: [index, array[index]], done: false }; } } // 22.1.5.2.2 %ArrayIteratorPrototype%[@@iterator]() [Symbol.iterator]() { return this; } }; } return { keys: hasNative ? array => array.keys() : array => new ArrayIterator(array, KIND_KEYS), values: hasNative ? array => array.values() : array => new ArrayIterator(array, KIND_VALUES), entries: hasNative ? array => array.entries() : array => new ArrayIterator(array, KIND_ENTRIES) }; }(); // ----------------------------------------------------------------- /** * Specific String iterators. */ const StringIterators = function () { let hasNative = hasNativeIterator(String); let StringIterator; if (!hasNative) { StringIterator = class StringIterator { // 21.1.5.1 CreateStringIterator Abstract Operation constructor(string) { this._iteratedString = string; this._nextIndex = 0; } // 21.1.5.2.1 %StringIteratorPrototype%.next() next() { if (this._iteratedString == null) { return { value: undefined, done: true }; } let index = this._nextIndex; let s = this._iteratedString; let len = s.length; if (index >= len) { this._iteratedString = undefined; return { value: undefined, done: true }; } let ret; let first = s.charCodeAt(index); if (first < 0xD800 || first > 0xDBFF || index + 1 === len) { ret = s[index]; } else { let second = s.charCodeAt(index + 1); if (second < 0xDC00 || second > 0xDFFF) { ret = s[index]; } else { ret = s[index] + s[index + 1]; } } this._nextIndex = index + ret.length; return { value: ret, done: false }; } // 21.1.5.2.2 %StringIteratorPrototype%[@@iterator]() [Symbol.iterator]() { return this; } }; } return { keys() { throw TypeError(`Strings default iterator doesn't implement keys.`); }, values: hasNative ? string => string[Symbol.iterator]() : string => new StringIterator(string), entries() { throw TypeError(`Strings default iterator doesn't implement entries.`); } }; }(); function hasNativeIterator(classObject) { return typeof classObject.prototype[Symbol.iterator] === 'function' && typeof classObject.prototype.values === 'function' && typeof classObject.prototype.keys === 'function' && typeof classObject.prototype.entries === 'function'; } // ----------------------------------------------------------------- /** * Generic object iterator. */ class ObjectIterator { constructor(object, kind) { this._iteratedObject = object; this._kind = kind; this._keys = Object.keys(object); this._nextIndex = 0; } next() { let len = this._keys.length; let index = this._nextIndex; let kind = this._kind; let key = this._keys[index]; if (index >= len) { this._iteratedObject = undefined; return { value: undefined, done: true }; } this._nextIndex = index + 1; if (kind === KIND_KEYS) { return { value: key, done: false }; } else if (kind === KIND_VALUES) { return { value: this._iteratedObject[key], done: false }; } else if (kind === KIND_ENTRIES) { return { value: [key, this._iteratedObject[key]], done: false }; } } [Symbol.iterator]() { return this; } } /** * Generic object iterator, iterates over all own enumerable * properties. Used only if if no specific iterator is available, * and object don't implement iterator protocol. */ const GenericIterators = { keys(object) { return new ObjectIterator(object, KIND_KEYS); }, values(object) { return new ObjectIterator(object, KIND_VALUES); }, entries(object) { return new ObjectIterator(object, KIND_ENTRIES); } }; // ----------------------------------------------------------------- /** * Main iterator function. Returns default iterator based * on the class of an instance. */ function enumerate(object, kind) { // First check specific iterators. if (typeof object === 'string') { return StringIterators[kind || KIND_VALUES](object); } else if (Array.isArray(object)) { return ArrayIterators[kind || KIND_VALUES](object); // Then see if an object implements own. } else if (object[Symbol.iterator]) { return object[Symbol.iterator](); // And fallback to generic with entries. } else { return GenericIterators[kind || KIND_ENTRIES](object); } } Object.assign(enumerate, { /** * Export constants */ KIND_KEYS, KIND_VALUES, KIND_ENTRIES, /** * Convenient explicit iterators for special kinds. */ keys(object) { return enumerate(object, KIND_KEYS); }, values(object) { return enumerate(object, KIND_VALUES); }, entries(object) { return enumerate(object, KIND_ENTRIES); }, generic: GenericIterators.entries }); module.exports = enumerate;