Skip to content

Commit 20921e5

Browse files
Copilotmattcosta7
andcommitted
Address all 4 review comments: CSP-safe getIteratorPrototype, primitive iterables in iteratorFrom, complete isPolyfilled check, string Iterator.from test
Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com>
1 parent e87f628 commit 20921e5

File tree

2 files changed

+46
-8
lines changed

2 files changed

+46
-8
lines changed

src/iterator-helpers.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
/*#__PURE__*/
33
function getIteratorPrototype(): Iterator<unknown> | null {
44
try {
5-
// Try to get Iterator prototype from a generator
6-
const GeneratorFunction = Object.getPrototypeOf(function* () {}).constructor
7-
const generator = new GeneratorFunction('return (function*(){})();')()
8-
return Object.getPrototypeOf(Object.getPrototypeOf(generator))
5+
// Derive IteratorPrototype from the Array iterator prototype chain.
6+
// This avoids dynamic evaluation and works under strict CSP.
7+
if (typeof Symbol !== 'function' || typeof Symbol.iterator !== 'symbol') {
8+
return null
9+
}
10+
const arrayIter = [][Symbol.iterator]()
11+
const arrayIterProto = Object.getPrototypeOf(arrayIter)
12+
if (!arrayIterProto) return null
13+
return Object.getPrototypeOf(arrayIterProto) as Iterator<unknown> | null
914
} catch {
1015
return null
1116
}
@@ -279,12 +284,15 @@ function find<T>(this: Iterator<T>, predicate: (value: T, index: number) => bool
279284

280285
/*#__PURE__*/
281286
function iteratorFrom<T>(obj: Iterator<T> | Iterable<T>): Iterator<T> {
282-
if (typeof obj === 'object' && obj !== null) {
283-
if ('next' in obj && typeof obj.next === 'function') {
287+
if (obj != null) {
288+
// Check for iterator (an object with a .next method)
289+
if (typeof obj === 'object' && 'next' in obj && typeof (obj as Iterator<T>).next === 'function') {
284290
return obj as Iterator<T>
285291
}
286-
if (Symbol.iterator in obj) {
287-
return obj[Symbol.iterator]()
292+
// Box primitives (e.g. strings) so we can check Symbol.iterator on them
293+
const iterable = Object(obj) as {[Symbol.iterator]?: () => Iterator<T>}
294+
if (Symbol.iterator in iterable && typeof iterable[Symbol.iterator] === 'function') {
295+
return iterable[Symbol.iterator]!()
288296
}
289297
}
290298
throw new TypeError('Object is not an iterator or iterable')
@@ -322,6 +330,26 @@ export function isPolyfilled(): boolean {
322330
IteratorPrototype !== null &&
323331
'map' in IteratorPrototype &&
324332
IteratorPrototype.map === map &&
333+
'filter' in IteratorPrototype &&
334+
IteratorPrototype.filter === filter &&
335+
'take' in IteratorPrototype &&
336+
IteratorPrototype.take === take &&
337+
'drop' in IteratorPrototype &&
338+
IteratorPrototype.drop === drop &&
339+
'flatMap' in IteratorPrototype &&
340+
IteratorPrototype.flatMap === flatMap &&
341+
'reduce' in IteratorPrototype &&
342+
IteratorPrototype.reduce === reduce &&
343+
'toArray' in IteratorPrototype &&
344+
IteratorPrototype.toArray === toArray &&
345+
'forEach' in IteratorPrototype &&
346+
IteratorPrototype.forEach === forEach &&
347+
'some' in IteratorPrototype &&
348+
IteratorPrototype.some === some &&
349+
'every' in IteratorPrototype &&
350+
IteratorPrototype.every === every &&
351+
'find' in IteratorPrototype &&
352+
IteratorPrototype.find === find &&
325353
IteratorConstructor !== undefined &&
326354
'from' in IteratorConstructor &&
327355
IteratorConstructor.from === iteratorFrom

test/iterator-helpers.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,16 @@ describe('Iterator helpers', () => {
241241
const iter = IteratorConstructor.from(original)
242242
expect(iter).to.equal(original)
243243
})
244+
245+
it('handles primitive iterable strings', () => {
246+
const IteratorConstructor = globalThis.Iterator
247+
if (!IteratorConstructor?.from) {
248+
// Skip if Iterator.from is not available after polyfill
249+
return
250+
}
251+
const iter = IteratorConstructor.from('abc')
252+
expect([...iter]).to.eql(['a', 'b', 'c'])
253+
})
244254
})
245255

246256
describe('chaining', () => {

0 commit comments

Comments
 (0)