toIterator.js 4.67 KB
/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule toIterator
 */
'use strict';

/**
 * Given an object `toIterator` will return the itrator for that object. If the
 * object has a `Symbol.iterator` method we just call that. Otherwise we
 * implement the ES6 `Array` and `String` Iterator.
 */

/**
 * Constants
 */

var KIND_KEY = 'key';
var KIND_VALUE = 'value';
var KIND_KEY_VAL = 'key+value';
/*global Symbol: true*/
var ITERATOR_SYMBOL = (typeof Symbol === 'function')
    ? Symbol.iterator
    : '@@iterator';

var toIterator = (function() {
  if (!(Array.prototype[ITERATOR_SYMBOL] &&
        String.prototype[ITERATOR_SYMBOL])) {
    // IIFE to avoid creating classes for no reason because of hoisting.
    return (function() {
      class ArrayIterator {
        // 22.1.5.1 CreateArrayIterator Abstract Operation
        constructor(array, kind) {
          if (!Array.isArray(array)) {
            throw new TypeError('Object is not an Array');
          }
          this._iteratedObject = array;
          this._kind = kind;
          this._nextIndex = 0;
        }

        // 22.1.5.2.1 %ArrayIteratorPrototype%.next()
        next() {
          if (!this instanceof ArrayIterator) {
            throw new TypeError('Object is not an ArrayIterator');
          }

          if (this._iteratedObject == null) {
            return createIterResultObject(undefined, true);
          }

          var array = this._iteratedObject;
          var len = this._iteratedObject.length;
          var index = this._nextIndex;
          var kind = this._kind;

          if (index >= len) {
            this._iteratedObject = undefined;
            return createIterResultObject(undefined, true);
          }

          this._nextIndex = index + 1;

          if (kind === KIND_KEY) {
            return createIterResultObject(index, false);
          } else if (kind === KIND_VALUE) {
            return createIterResultObject(array[index], false);
          } else if (kind === KIND_KEY_VAL) {
            return createIterResultObject([index, array[index]], false);
          }
        }

        // 22.1.5.2.2 %ArrayIteratorPrototype%[@@iterator]()
        '@@iterator'() {
          return this;
        }
      }

      class StringIterator {
        // 21.1.5.1 CreateStringIterator Abstract Operation
        constructor(string) {
          if (typeof string !== 'string') {
            throw new TypeError('Object is not a string');
          }
          this._iteratedString = string;
          this._nextIndex = 0;
        }

        // 21.1.5.2.1 %StringIteratorPrototype%.next()
        next() {
          if (!this instanceof StringIterator) {
            throw new TypeError('Object is not a StringIterator');
          }

          if (this._iteratedString == null) {
            return createIterResultObject(undefined, true);
          }

          var index = this._nextIndex;
          var s = this._iteratedString;
          var len = s.length;

          if (index >= len) {
            this._iteratedString = undefined;
            return createIterResultObject(undefined, true);
          }

          var ret;
          var first = s.charCodeAt(index);

          if (first < 0xD800 || first > 0xDBFF || index + 1 === len) {
            ret = s[index];
          } else {
            var 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 createIterResultObject(ret, false);
        }

        // 21.1.5.2.2 %StringIteratorPrototype%[@@ITERATOR_SYMBOL]()
        '@@iterator'() {
          return this;
        }
      }

      // 7.4.7 createIterResultObject(value, done)
      function createIterResultObject(value, done) {
        return {value: value, done: done};
      }

      return function(object, kind) {
        if (typeof object === 'string') {
          return new StringIterator(object);
        } else if (Array.isArray(object)) {
          return new ArrayIterator(object, kind || KIND_VALUE);
        } else {
          return object[ITERATOR_SYMBOL]();
        }
      };
    })();
  } else {
    return function(object) {
      return object[ITERATOR_SYMBOL]();
    };
  }
})();

/**
 * Export constants
 */

Object.assign(toIterator, {
  KIND_KEY,
  KIND_VALUE,
  KIND_KEY_VAL,
  ITERATOR_SYMBOL
});

module.exports = toIterator;