Skip to content

Commit 6bd0212

Browse files
authored
Add an Iterator.from TypeScript goody (#417)
based off of [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/from) closes #343
1 parent 72718d9 commit 6bd0212

File tree

4 files changed

+338
-0
lines changed

4 files changed

+338
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { describe, expect, it } from "@jest/globals";
2+
3+
import "../Iterator.prototype.map";
4+
import "../Iterator.prototype.filter";
5+
6+
(globalThis as Record<string, unknown>).Iterator &&
7+
delete (
8+
(globalThis as Record<string, unknown>).Iterator as Record<string, unknown>
9+
).from;
10+
11+
// eslint-disable-next-line import-x/first
12+
import "./index";
13+
14+
describe("Iterator.from", () => {
15+
it("can convert an Array to an Iterator", () => {
16+
const array = [-3, 1, -4, 1, -5];
17+
const iterator = Iterator.from(array);
18+
19+
expect([...iterator]).toStrictEqual(array);
20+
});
21+
22+
it("can convert a Set to an Iterator", () => {
23+
const set = new Set([-3, 1, 1, -4, -4, 1, -5]);
24+
const iterator = Iterator.from(set);
25+
26+
expect([...iterator]).toStrictEqual([...set]);
27+
});
28+
29+
it("can convert a Map to an Iterator", () => {
30+
const map = new Map([
31+
["z", 3],
32+
["e", 1],
33+
["d", 4],
34+
]);
35+
const iterator = Iterator.from(map);
36+
expect([...iterator]).toStrictEqual([...map.entries()]);
37+
});
38+
39+
it("can convert a Generator Object to an Iterator", () => {
40+
const factory = function* (): Generator<number, void, void> {
41+
yield 3;
42+
yield 1;
43+
yield 4;
44+
yield 1;
45+
yield 5;
46+
};
47+
const iterator = Iterator.from(factory());
48+
49+
expect([...iterator]).toStrictEqual([3, 1, 4, 1, 5]);
50+
});
51+
52+
it("can convert a String to an Iterator", () => {
53+
const string = "🚀💯🔥";
54+
const iterator = Iterator.from(string);
55+
56+
expect([...iterator]).toStrictEqual([...string]);
57+
});
58+
59+
it("can convert an Iterator to an IterableIterator and returns the same reference", () => {
60+
const array = [-3, 1, -4, 1, -5];
61+
const iterator = array.values();
62+
const convertedIterator = Iterator.from(iterator);
63+
64+
expect(convertedIterator).toBe(iterator);
65+
});
66+
67+
it("can convert an IterableIterator to an Iterator", () => {
68+
const array = [-3, 1, -4, 1, -5];
69+
const iterator = Iterator.from(array.values());
70+
const iterator2 = Iterator.from(iterator[Symbol.iterator]());
71+
72+
expect([...iterator2]).toStrictEqual(array);
73+
});
74+
75+
it("can convert an object with a random next() method to an Iterator", () => {
76+
const object = {
77+
values: [-3, 1, -4, 1, -5, 9],
78+
index: 0,
79+
next(): IteratorResult<number, void> {
80+
if (this.index < this.values.length) {
81+
return { value: this.values[this.index++], done: false };
82+
}
83+
return { done: true, value: undefined };
84+
},
85+
};
86+
const iterator = Iterator.from(object)
87+
.map((x) => x * 2)
88+
.filter((x) => x > 0);
89+
90+
expect([...iterator]).toStrictEqual([2, 2, 18]);
91+
});
92+
93+
it("throws an error if the object is not an Iterator, Iterable, or an object with a next method", () => {
94+
const object = {};
95+
// @ts-expect-error Invalid argument
96+
expect(() => Iterator.from(object)).toThrow(TypeError);
97+
});
98+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import "../Iterator.prototype.toIterable/index.ts";
2+
import { iteratorPrototype } from "../Iterator.prototype/index.ts";
3+
4+
declare global {
5+
interface IteratorConstructor {
6+
from<T>(
7+
object: Iterator<T> | Iterable<T> | { next(): IteratorResult<T> },
8+
): IterableIterator<T>;
9+
}
10+
11+
const Iterator: IteratorConstructor;
12+
}
13+
14+
(globalThis as Record<string, unknown>).Iterator ??= {};
15+
16+
((globalThis as Record<string, unknown>).Iterator as { from: unknown }).from ??=
17+
function <T>(
18+
object: Iterator<T> | Iterable<T> | { next(): IteratorResult<T> },
19+
): IterableIterator<T> {
20+
const toIterable = iteratorPrototype.toIterable as (
21+
this: Iterator<T>,
22+
) => IterableIterator<T>;
23+
24+
const iteratorFactory = (object as Iterable<T>)[Symbol.iterator];
25+
if (typeof iteratorFactory === "function") {
26+
return toIterable.call(iteratorFactory.call(object));
27+
}
28+
29+
// eslint-disable-next-line no-prototype-builtins
30+
if (iteratorPrototype.isPrototypeOf(object)) {
31+
return toIterable.call(object as Iterator<T>);
32+
}
33+
34+
if (typeof (object as Record<string, unknown>).next === "function") {
35+
return (function* () {
36+
yield* toIterable.call(object as Iterator<T>);
37+
})();
38+
}
39+
40+
throw new TypeError(
41+
"Object is not an iterator, iterable, or an object with a next method",
42+
);
43+
};

workspaces/adventure-pack/src/app/__tests__/__snapshots__/equip-test.ts.snap

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,53 @@ Function.returnThis = function () {
13641364
/////////////////////////// END ADVENTURE PACK CODE ////////////////////////////"
13651365
`;
13661366

1367+
exports[`App can equip single goody: JavaScript Iterator.from 1`] = `
1368+
"////////////////////////// BEGIN ADVENTURE PACK CODE ///////////////////////////
1369+
// Adventure Pack commit fake-commit-hash
1370+
// Running at: https://example.com/
1371+
1372+
Function.returnThis = function () {
1373+
return this;
1374+
};
1375+
1376+
const iteratorPrototype = Object.getPrototypeOf(
1377+
Object.getPrototypeOf([].values()),
1378+
);
1379+
1380+
iteratorPrototype.toIterable = function () {
1381+
this[Symbol.iterator] ??= Function.returnThis;
1382+
return this;
1383+
};
1384+
1385+
globalThis.Iterator ??= {};
1386+
1387+
globalThis.Iterator.from ??= function (object) {
1388+
const toIterable = iteratorPrototype.toIterable;
1389+
1390+
const iteratorFactory = object[Symbol.iterator];
1391+
if (typeof iteratorFactory === "function") {
1392+
return toIterable.call(iteratorFactory.call(object));
1393+
}
1394+
1395+
// eslint-disable-next-line no-prototype-builtins
1396+
if (iteratorPrototype.isPrototypeOf(object)) {
1397+
return toIterable.call(object);
1398+
}
1399+
1400+
if (typeof object.next === "function") {
1401+
return (function* () {
1402+
yield* toIterable.call(object);
1403+
})();
1404+
}
1405+
1406+
throw new TypeError(
1407+
"Object is not an iterator, iterable, or an object with a next method",
1408+
);
1409+
};
1410+
1411+
/////////////////////////// END ADVENTURE PACK CODE ////////////////////////////"
1412+
`;
1413+
13671414
exports[`App can equip single goody: JavaScript Iterator.prototype 1`] = `
13681415
"////////////////////////// BEGIN ADVENTURE PACK CODE ///////////////////////////
13691416
// Adventure Pack commit fake-commit-hash
@@ -3280,6 +3327,79 @@ Function.returnThis = function <T>(this: T): T {
32803327
/////////////////////////// END ADVENTURE PACK CODE ////////////////////////////"
32813328
`;
32823329

3330+
exports[`App can equip single goody: TypeScript Iterator.from 1`] = `
3331+
"////////////////////////// BEGIN ADVENTURE PACK CODE ///////////////////////////
3332+
// Adventure Pack commit fake-commit-hash
3333+
// Running at: https://example.com/
3334+
3335+
declare global {
3336+
interface FunctionConstructor {
3337+
returnThis<T>(this: T): T;
3338+
}
3339+
3340+
interface Iterator<T> {
3341+
toIterable(this: Iterator<T>): IterableIterator<T>;
3342+
}
3343+
3344+
interface IteratorConstructor {
3345+
from<T>(
3346+
object: Iterator<T> | Iterable<T> | { next(): IteratorResult<T> },
3347+
): IterableIterator<T>;
3348+
}
3349+
3350+
const Iterator: IteratorConstructor;
3351+
}
3352+
3353+
Function.returnThis = function <T>(this: T): T {
3354+
return this;
3355+
};
3356+
3357+
const iteratorPrototype = Object.getPrototypeOf(
3358+
Object.getPrototypeOf([].values()),
3359+
) as Iterator<unknown, unknown, unknown>;
3360+
3361+
iteratorPrototype.toIterable = function <T>(
3362+
this: Iterator<T>,
3363+
): IterableIterator<T> {
3364+
(this as unknown as Record<symbol, unknown>)[Symbol.iterator] ??=
3365+
Function.returnThis;
3366+
return this as unknown as IterableIterator<T>;
3367+
};
3368+
3369+
(globalThis as Record<string, unknown>).Iterator ??= {};
3370+
3371+
((globalThis as Record<string, unknown>).Iterator as { from: unknown }).from ??=
3372+
function <T>(
3373+
object: Iterator<T> | Iterable<T> | { next(): IteratorResult<T> },
3374+
): IterableIterator<T> {
3375+
const toIterable = iteratorPrototype.toIterable as (
3376+
this: Iterator<T>,
3377+
) => IterableIterator<T>;
3378+
3379+
const iteratorFactory = (object as Iterable<T>)[Symbol.iterator];
3380+
if (typeof iteratorFactory === "function") {
3381+
return toIterable.call(iteratorFactory.call(object));
3382+
}
3383+
3384+
// eslint-disable-next-line no-prototype-builtins
3385+
if (iteratorPrototype.isPrototypeOf(object)) {
3386+
return toIterable.call(object as Iterator<T>);
3387+
}
3388+
3389+
if (typeof (object as Record<string, unknown>).next === "function") {
3390+
return (function* () {
3391+
yield* toIterable.call(object as Iterator<T>);
3392+
})();
3393+
}
3394+
3395+
throw new TypeError(
3396+
"Object is not an iterator, iterable, or an object with a next method",
3397+
);
3398+
};
3399+
3400+
/////////////////////////// END ADVENTURE PACK CODE ////////////////////////////"
3401+
`;
3402+
32833403
exports[`App can equip single goody: TypeScript Iterator.prototype 1`] = `
32843404
"////////////////////////// BEGIN ADVENTURE PACK CODE ///////////////////////////
32853405
// Adventure Pack commit fake-commit-hash

workspaces/adventure-pack/src/app/__tests__/__snapshots__/render-test.ts.snap

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,37 @@ exports[`App can render goody: JavaScript Function.returnThis 1`] = `
800800
};"
801801
`;
802802
803+
exports[`App can render goody: JavaScript Iterator.from 1`] = `
804+
"import "Iterator.prototype";
805+
import "Iterator.prototype.toIterable";
806+
807+
globalThis.Iterator ??= {};
808+
809+
globalThis.Iterator.from ??= function (object) {
810+
const toIterable = iteratorPrototype.toIterable;
811+
812+
const iteratorFactory = object[Symbol.iterator];
813+
if (typeof iteratorFactory === "function") {
814+
return toIterable.call(iteratorFactory.call(object));
815+
}
816+
817+
// eslint-disable-next-line no-prototype-builtins
818+
if (iteratorPrototype.isPrototypeOf(object)) {
819+
return toIterable.call(object);
820+
}
821+
822+
if (typeof object.next === "function") {
823+
return (function* () {
824+
yield* toIterable.call(object);
825+
})();
826+
}
827+
828+
throw new TypeError(
829+
"Object is not an iterator, iterable, or an object with a next method",
830+
);
831+
};"
832+
`;
833+
803834
exports[`App can render goody: JavaScript Iterator.prototype 1`] = `
804835
"export const iteratorPrototype = Object.getPrototypeOf(
805836
Object.getPrototypeOf([].values()),
@@ -1939,6 +1970,52 @@ Function.returnThis = function <T>(this: T): T {
19391970
};"
19401971
`;
19411972
1973+
exports[`App can render goody: TypeScript Iterator.from 1`] = `
1974+
"import "Iterator.prototype";
1975+
import "Iterator.prototype.toIterable";
1976+
1977+
declare global {
1978+
interface IteratorConstructor {
1979+
from<T>(
1980+
object: Iterator<T> | Iterable<T> | { next(): IteratorResult<T> },
1981+
): IterableIterator<T>;
1982+
}
1983+
1984+
const Iterator: IteratorConstructor;
1985+
}
1986+
1987+
(globalThis as Record<string, unknown>).Iterator ??= {};
1988+
1989+
((globalThis as Record<string, unknown>).Iterator as { from: unknown }).from ??=
1990+
function <T>(
1991+
object: Iterator<T> | Iterable<T> | { next(): IteratorResult<T> },
1992+
): IterableIterator<T> {
1993+
const toIterable = iteratorPrototype.toIterable as (
1994+
this: Iterator<T>,
1995+
) => IterableIterator<T>;
1996+
1997+
const iteratorFactory = (object as Iterable<T>)[Symbol.iterator];
1998+
if (typeof iteratorFactory === "function") {
1999+
return toIterable.call(iteratorFactory.call(object));
2000+
}
2001+
2002+
// eslint-disable-next-line no-prototype-builtins
2003+
if (iteratorPrototype.isPrototypeOf(object)) {
2004+
return toIterable.call(object as Iterator<T>);
2005+
}
2006+
2007+
if (typeof (object as Record<string, unknown>).next === "function") {
2008+
return (function* () {
2009+
yield* toIterable.call(object as Iterator<T>);
2010+
})();
2011+
}
2012+
2013+
throw new TypeError(
2014+
"Object is not an iterator, iterable, or an object with a next method",
2015+
);
2016+
};"
2017+
`;
2018+
19422019
exports[`App can render goody: TypeScript Iterator.prototype 1`] = `
19432020
"export const iteratorPrototype = Object.getPrototypeOf(
19442021
Object.getPrototypeOf([].values()),

0 commit comments

Comments
 (0)