Skip to content

Commit

Permalink
[New] add Iterator.concat
Browse files Browse the repository at this point in the history
  • Loading branch information
ljharb committed Oct 8, 2024
1 parent bd34766 commit 1c07c21
Show file tree
Hide file tree
Showing 11 changed files with 307 additions and 3 deletions.
3 changes: 3 additions & 0 deletions Iterator.concat/auto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

require('./shim')();
112 changes: 112 additions & 0 deletions Iterator.concat/implementation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
'use strict';

var $TypeError = require('es-errors/type');

var AdvanceStringIndex = require('es-abstract/2024/AdvanceStringIndex');
var Call = require('es-abstract/2024/Call');
var CompletionRecord = require('es-abstract/2024/CompletionRecord');
var CreateIteratorFromClosure = require('../aos/CreateIteratorFromClosure');
var GetIteratorDirect = require('../aos/GetIteratorDirect');
var GetMethod = require('es-abstract/2024/GetMethod');
var IsArray = require('es-abstract/2024/IsArray');
var IteratorClose = require('es-abstract/2024/IteratorClose');
var IteratorStepValue = require('es-abstract/2024/IteratorStepValue');
var ThrowCompletion = require('es-abstract/2024/ThrowCompletion');
var Type = require('es-abstract/2024/Type');

var forEach = require('es-abstract/helpers/forEach');
var getIteratorMethod = require('es-abstract/helpers/getIteratorMethod');

var iterHelperProto = require('../IteratorHelperPrototype');

var SLOT = require('internal-slot');

module.exports = function concat() {
if (this instanceof concat) {
throw new $TypeError('`Iterator.concat` is not a constructor');
}

var iterables = []; // step 1

forEach(arguments, function (item) { // step 2
if (Type(item) !== 'Object') {
throw new $TypeError('`Iterator.concat` requires all arguments to be objects'); // step 2.1
}
// var method = GetMethod(item, Symbol.iterator); // step 2.2
var method = getIteratorMethod(
{
AdvanceStringIndex: AdvanceStringIndex,
GetMethod: GetMethod,
IsArray: IsArray
},
item
);
if (typeof method === 'undefined') {
throw new $TypeError('`Iterator.concat` requires all arguments to be iterable'); // step 2.3
}
iterables[iterables.length] = { '[[OpenMethod]]': method, '[[Iterable]]': item }; // step 2.4
});

var sentinel = {};
var innerIterator = sentinel;
var closeIfAbrupt = function (abruptCompletion) {
if (!(abruptCompletion instanceof CompletionRecord)) {
throw new $TypeError('`abruptCompletion` must be a Completion Record');
}
if (innerIterator !== sentinel) {
IteratorClose(
innerIterator,
abruptCompletion
);
}
};

var index = 0;
var closure = function () { // step 3
if (index < iterables.length) {
// forEach(iterables, function (iterable) { // step 3.a
var iteratorRecord;
if (innerIterator === sentinel) {
var iterable = iterables[index];
var iter = Call(iterable['[[OpenMethod]]'], iterable['[[Iterable]]']); // step 3.a.i
if (Type(iter) !== 'Object') {
closeIfAbrupt(ThrowCompletion(new $TypeError('???'))); // step 3.a.ii
}
iteratorRecord = GetIteratorDirect(iter); // step 3.a.iii
innerIterator = iteratorRecord;
} else {
iteratorRecord = innerIterator;
}

// var innerAlive = true; // step 3.a.iv
// while (innerAlive) { // step 3.a.v
if (innerIterator !== sentinel) {
// step 3.a.v.3.a
var innerValue;
try {
innerValue = IteratorStepValue(iteratorRecord); // step 5.b.ix.4.a
} catch (e) {
// innerAlive = false;
innerIterator = sentinel;
index += 1;
closeIfAbrupt(ThrowCompletion(e)); // step 3.a.v.3.b
}
if (iteratorRecord['[[Done]]']) {
// innerAlive = false;
innerIterator = sentinel;
index += 1;
return closure();
}
return innerValue; // // step 3.a.v.3.a
}
// });
}

// return ReturnCompletion(undefined); // step 3.b
return sentinel;
};
SLOT.set(closure, '[[Sentinel]]', sentinel); // for the userland implementation
SLOT.set(closure, '[[CloseIfAbrupt]]', closeIfAbrupt); // for the userland implementation

return CreateIteratorFromClosure(closure, 'Iterator Helper', iterHelperProto, []); // step 4
};
18 changes: 18 additions & 0 deletions Iterator.concat/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

var callBind = require('call-bind');
var define = require('define-properties');

var implementation = require('./implementation');
var getPolyfill = require('./polyfill');
var shim = require('./shim');

var bound = callBind(getPolyfill(), null);

define(bound, {
getPolyfill: getPolyfill,
implementation: implementation,
shim: shim
});

module.exports = bound;
9 changes: 9 additions & 0 deletions Iterator.concat/polyfill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

var implementation = require('./implementation');

var $Iterator = require('../Iterator');

module.exports = function getPolyfill() {
return typeof $Iterator.concat === 'function' ? $Iterator.concat : implementation;
};
18 changes: 18 additions & 0 deletions Iterator.concat/shim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

var getPolyfill = require('./polyfill');
var define = require('define-properties');

var getIteratorPolyfill = require('../Iterator/polyfill');

module.exports = function shimIteratorConcat() {
var $Iterator = getIteratorPolyfill();
var polyfill = getPolyfill();
define(
$Iterator,
{ concat: polyfill },
{ concat: function () { return $Iterator.concat !== polyfill; } }
);

return polyfill;
};
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

An ESnext spec-compliant sync iterator helpers shim/polyfill/replacement that works as far down as ES3.

This package implements the [es-shim API](https://github.com/es-shims/api) “multi” interface. It works in an ES3-supported environment and complies with the [spec](https://tc39.es/proposal-iterator-helpers/).
This package implements the [es-shim API](https://github.com/es-shims/api) “multi” interface. It works in an ES3-supported environment and complies with the [iterator helpers spec](https://tc39.es/proposal-iterator-helpers/) and the [iterator sequencing spec](https://tc39.es/proposal-iterator-sequencing/).

Because the `Iterator.prototype` methods depend on a receiver (the `this` value), the main export in each subdirectory takes the iterator to operate on as the first argument.

Expand All @@ -19,6 +19,7 @@ The main export of the package itself is simply an array of the available direct

- [`Iterator` constructor](https://tc39.es/proposal-iterator-helpers/#sec-iterator-constructor)
- [`Iterator.prototype`](https://tc39.es/proposal-iterator-helpers/#sec-iterator.prototype)
- [`Iterator.concat`](https://tc39.es/proposal-iterator-sequencing/)
- [`Iterator.from`](https://tc39.es/proposal-iterator-helpers/#sec-iterator.from)
- [`Iterator.prototype.constructor`](https://tc39.es/proposal-iterator-helpers/#sec-iteratorprototype.constructor)
- [`Iterator.prototype.drop`](https://tc39.es/proposal-iterator-helpers/#sec-iteratorprototype.drop)
Expand All @@ -37,6 +38,7 @@ The main export of the package itself is simply an array of the available direct

- node v22, Chrome >= v122: has a [bug](https://issues.chromium.org/issues/336839115)
- node < v22, Chrome < v122, Safari <= v17.1, Firefox <= v125: not implemented
- all environments lack Iterator.concat

## Getting started

Expand Down
1 change: 1 addition & 0 deletions index.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[
"Iterator",
"Iterator.concat",
"Iterator.from",
"Iterator.prototype",
"Iterator.prototype.constructor",
Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
"./Iterator.prototype/polyfill": "./Iterator.prototype/polyfill.js",
"./Iterator.prototype/implementation": "./Iterator.prototype/implementation.js",
"./Iterator.prototype/shim": "./Iterator.prototype/shim.js",
"./Iterator.concat": "./Iterator.concat/index.js",
"./Iterator.concat/auto": "./Iterator.concat/auto.js",
"./Iterator.concat/polyfill": "./Iterator.concat/polyfill.js",
"./Iterator.concat/implementation": "./Iterator.concat/implementation.js",
"./Iterator.concat/shim": "./Iterator.concat/shim.js",
"./Iterator.from": "./Iterator.from/index.js",
"./Iterator.from/auto": "./Iterator.from/auto.js",
"./Iterator.from/polyfill": "./Iterator.from/polyfill.js",
Expand Down Expand Up @@ -143,7 +148,7 @@
"has-proto": "^1.0.3",
"has-symbols": "^1.0.3",
"internal-slot": "^1.0.7",
"iterator.prototype": "^1.1.2",
"iterator.prototype": "^1.1.3",
"safe-array-concat": "^1.1.2"
},
"devDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

var shimIterator = require('./Iterator/shim');
var shimIteratorFrom = require('./Iterator.from/shim');
var shimIteratorConcat = require('./Iterator.concat/shim');
var shimIteratorProto = require('./Iterator.prototype/shim');
var shimIteratorCtor = require('./Iterator.prototype.constructor/shim');
var shimIteratorDrop = require('./Iterator.prototype.drop/shim');
Expand All @@ -19,6 +20,7 @@ var shimIteratorToArray = require('./Iterator.prototype.toArray/shim');
module.exports = function shimIteratorHelpers() {
shimIterator();
shimIteratorFrom();
shimIteratorConcat();
shimIteratorProto();
shimIteratorCtor();
shimIteratorDrop();
Expand Down
127 changes: 127 additions & 0 deletions test/Iterator.concat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
'use strict';

var defineProperties = require('define-properties');
var test = require('tape');
var callBind = require('call-bind');
var functionsHaveNames = require('functions-have-names')();
var forEach = require('for-each');
var debug = require('object-inspect');
var v = require('es-value-fixtures');
var hasSymbols = require('has-symbols/shams')();
var mockProperty = require('mock-property');

var index = require('../Iterator.concat');
var impl = require('../Iterator.concat/implementation');
var from = require('../Iterator.from/polyfill')();

var isEnumerable = Object.prototype.propertyIsEnumerable;

var testIterator = require('./helpers/testIterator');

module.exports = {
tests: function (concat, name, t) {
t['throws'](
function () { return new concat(); }, // eslint-disable-line new-cap
TypeError,
'`' + name + '` itself is not a constructor'
);
t['throws'](
function () { return new concat({}); }, // eslint-disable-line new-cap
TypeError,
'`' + name + '` itself is not a constructor, with an argument'
);

forEach(v.primitives.concat(v.objects), function (nonIterator) {
t['throws'](
function () { concat(nonIterator); },
TypeError,
debug(nonIterator) + ' is not an iterable Object'
);
});

t.test('actual iteration', { skip: !hasSymbols }, function (st) {
forEach(v.nonFunctions, function (nonFunction) {
var badIterable = {};
badIterable[Symbol.iterator] = nonFunction;
st['throws'](
function () { concat([], badIterable, []).next(); },
TypeError,
debug(badIterable) + ' is not a function'
);
});

forEach(v.strings, function (string) {
st['throws'](
function () { concat(string); },
TypeError,
'non-objects are not considered iterable'
);
var stringIt = concat(['a'], [string], ['c']);
testIterator(stringIt, ['a', string, 'c'], st, 'string iterator: ' + debug(string));
});

var arrayIt = concat([1, 2, 3]);
st.equal(typeof arrayIt.next, 'function', 'has a `next` function');

st.test('real iterators', { skip: !hasSymbols }, function (s2t) {
var iter = [1, 2][Symbol.iterator]();
testIterator(concat(iter, [3]), [1, 2, 3], s2t, 'array iterator + array yields combined results');

s2t.end();
});

st.test('observability in a replaced String iterator', function (s2t) {
var originalStringIterator = String.prototype[Symbol.iterator];
var observedType;
s2t.teardown(mockProperty(String.prototype, Symbol.iterator, {
get: function () {
'use strict'; // eslint-disable-line strict, lines-around-directive

observedType = typeof this;
return originalStringIterator;
}
}));

concat(from(''));
s2t.equal(observedType, 'string', 'string primitive -> primitive receiver in Symbol.iterator getter');
concat(from(Object('')));
s2t.equal(observedType, 'object', 'boxed string -> boxed string in Symbol.iterator getter');

s2t.end();
});

st.end();
});
},
index: function () {
test('Iterator.concat: index', function (t) {
module.exports.tests(index, 'Iterator.concat', t);

t.end();
});
},
implementation: function () {
test('Iterator.concat: implementation', function (t) {
module.exports.tests(impl, 'Iterator.concat', t);

t.end();
});
},
shimmed: function () {
test('Iterator.concat: shimmed', function (t) {
t.test('Function name', { skip: !functionsHaveNames }, function (st) {
st.equal(Iterator.concat.name, 'concat', 'Iterator.concat has name "concat"');
st.end();
});

t.test('enumerability', { skip: !defineProperties.supportsDescriptors }, function (et) {
et.equal(false, isEnumerable.call(Iterator, 'concat'), 'Iterator.concat is not enumerable');
et.end();
});

module.exports.tests(callBind(Iterator.concat, Iterator), 'Iterator.concat', t);

t.end();
});
}
};
9 changes: 8 additions & 1 deletion test/Iterator.from.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,14 @@ module.exports = {

st.test('real iterators', { skip: !hasSymbols }, function (s2t) {
var iter = [][Symbol.iterator]();
s2t.equal(from(iter), iter, 'array iterator becomes itself');
// eslint-disable-next-line no-proto
var arrayIterHasIterProto = hasProto && iter.__proto__.__proto__ !== Object.prototype;
s2t.equal(
from(iter),
iter,
'array iterator becomes itself',
{ skip: !arrayIterHasIterProto && 'node 0.12 - 3 do not have Iterator.prototype in the proto chains' }
);

s2t.end();
});
Expand Down

0 comments on commit 1c07c21

Please sign in to comment.