Skip to content

Commit 0a06e72

Browse files
Claudemattcosta7
andcommitted
Add polyfills for ES2024 and ES2025 features
- Add Object.groupBy polyfill (ES2024) - Add Map.groupBy polyfill (ES2024) - Add Iterator helpers polyfill (ES2025): map, filter, take, drop, flatMap, reduce, toArray, forEach, some, every, find - Add Iterator.from polyfill (ES2025) - Add Promise.try polyfill (ES2025) - Add comprehensive tests for all new polyfills - Update src/index.ts to register new polyfills - All tests passing (56 tests) Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com>
1 parent 58b9fa9 commit 0a06e72

File tree

9 files changed

+845
-0
lines changed

9 files changed

+845
-0
lines changed

src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import * as withResolvers from './promise-withResolvers.js'
55
import * as requestIdleCallback from './requestidlecallback.js'
66
import * as popover from '@oddbird/popover-polyfill/fn'
77
import * as commandAndCommandFor from 'invokers-polyfill/fn'
8+
import * as objectGroupBy from './object-groupby.js'
9+
import * as mapGroupBy from './map-groupby.js'
10+
import * as promiseTry from './promise-try.js'
11+
import * as iteratorHelpers from './iterator-helpers.js'
812

913
let supportsModalPseudo = false
1014
try {
@@ -53,6 +57,10 @@ export const polyfills = {
5357
withResolvers,
5458
popover,
5559
commandAndCommandFor,
60+
objectGroupBy,
61+
mapGroupBy,
62+
promiseTry,
63+
iteratorHelpers,
5664
}
5765

5866
export function isSupported() {

src/iterator-helpers.ts

Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
// Get the Iterator prototype
2+
/*#__PURE__*/
3+
function getIteratorPrototype(): Iterator<unknown> | null {
4+
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))
9+
} catch {
10+
return null
11+
}
12+
}
13+
14+
/*#__PURE__*/
15+
function map<T, U>(this: Iterator<T>, mapper: (value: T, index: number) => U): Iterator<U> {
16+
// eslint-disable-next-line @typescript-eslint/no-this-alias
17+
const iterator = this
18+
let index = 0
19+
return {
20+
next() {
21+
const result = iterator.next()
22+
if (result.done) {
23+
return {done: true, value: undefined} as IteratorReturnResult<undefined>
24+
}
25+
return {done: false, value: mapper(result.value, index++)}
26+
},
27+
return(value?: U) {
28+
if (iterator.return) {
29+
iterator.return()
30+
}
31+
return {done: true, value} as IteratorReturnResult<U>
32+
},
33+
} as Iterator<U>
34+
}
35+
36+
/*#__PURE__*/
37+
function filter<T>(this: Iterator<T>, predicate: (value: T, index: number) => boolean): Iterator<T> {
38+
// eslint-disable-next-line @typescript-eslint/no-this-alias
39+
const iterator = this
40+
let index = 0
41+
return {
42+
next() {
43+
// eslint-disable-next-line no-constant-condition
44+
while (true) {
45+
const result = iterator.next()
46+
if (result.done) {
47+
return {done: true, value: undefined} as IteratorReturnResult<undefined>
48+
}
49+
if (predicate(result.value, index++)) {
50+
return result
51+
}
52+
}
53+
},
54+
return(value?: T) {
55+
if (iterator.return) {
56+
iterator.return()
57+
}
58+
return {done: true, value} as IteratorReturnResult<T>
59+
},
60+
} as Iterator<T>
61+
}
62+
63+
/*#__PURE__*/
64+
function take<T>(this: Iterator<T>, limit: number): Iterator<T> {
65+
// eslint-disable-next-line @typescript-eslint/no-this-alias
66+
const iterator = this
67+
let remaining = limit
68+
return {
69+
next() {
70+
if (remaining <= 0) {
71+
if (iterator.return) {
72+
iterator.return()
73+
}
74+
return {done: true, value: undefined} as IteratorReturnResult<undefined>
75+
}
76+
remaining--
77+
return iterator.next()
78+
},
79+
return(value?: T) {
80+
if (iterator.return) {
81+
iterator.return()
82+
}
83+
return {done: true, value} as IteratorReturnResult<T>
84+
},
85+
} as Iterator<T>
86+
}
87+
88+
/*#__PURE__*/
89+
function drop<T>(this: Iterator<T>, limit: number): Iterator<T> {
90+
// eslint-disable-next-line @typescript-eslint/no-this-alias
91+
const iterator = this
92+
let remaining = limit
93+
return {
94+
next() {
95+
while (remaining > 0) {
96+
const result = iterator.next()
97+
if (result.done) {
98+
return {done: true, value: undefined} as IteratorReturnResult<undefined>
99+
}
100+
remaining--
101+
}
102+
return iterator.next()
103+
},
104+
return(value?: T) {
105+
if (iterator.return) {
106+
iterator.return()
107+
}
108+
return {done: true, value} as IteratorReturnResult<T>
109+
},
110+
} as Iterator<T>
111+
}
112+
113+
/*#__PURE__*/
114+
function flatMap<T, U>(this: Iterator<T>, mapper: (value: T, index: number) => Iterable<U> | Iterator<U>): Iterator<U> {
115+
// eslint-disable-next-line @typescript-eslint/no-this-alias
116+
const iterator = this
117+
let index = 0
118+
let innerIterator: Iterator<U> | null = null
119+
120+
return {
121+
next() {
122+
// eslint-disable-next-line no-constant-condition
123+
while (true) {
124+
if (innerIterator) {
125+
const result = innerIterator.next()
126+
if (!result.done) {
127+
return result
128+
}
129+
innerIterator = null
130+
}
131+
132+
const result = iterator.next()
133+
if (result.done) {
134+
return {done: true, value: undefined} as IteratorReturnResult<undefined>
135+
}
136+
137+
const mapped = mapper(result.value, index++)
138+
innerIterator = Symbol.iterator in mapped ? mapped[Symbol.iterator]() : (mapped as Iterator<U>)
139+
}
140+
},
141+
return(value?: U) {
142+
if (innerIterator?.return) {
143+
innerIterator.return()
144+
}
145+
if (iterator.return) {
146+
iterator.return()
147+
}
148+
return {done: true, value} as IteratorReturnResult<U>
149+
},
150+
} as Iterator<U>
151+
}
152+
153+
/*#__PURE__*/
154+
function reduce<T>(this: Iterator<T>, reducer: (accumulator: T, value: T, index: number) => T): T
155+
/*#__PURE__*/
156+
function reduce<T, U>(this: Iterator<T>, reducer: (accumulator: U, value: T, index: number) => U, initialValue: U): U
157+
/*#__PURE__*/
158+
function reduce<T, U>(this: Iterator<T>, reducer: (accumulator: U, value: T, index: number) => U, initialValue?: U): U {
159+
// eslint-disable-next-line @typescript-eslint/no-this-alias
160+
const iterator = this
161+
let index = 0
162+
let accumulator: U
163+
164+
if (arguments.length < 2) {
165+
const result = iterator.next()
166+
if (result.done) {
167+
throw new TypeError('Reduce of empty iterator with no initial value')
168+
}
169+
accumulator = result.value as unknown as U
170+
index = 1
171+
} else {
172+
accumulator = initialValue!
173+
}
174+
175+
// eslint-disable-next-line no-constant-condition
176+
while (true) {
177+
const result = iterator.next()
178+
if (result.done) {
179+
return accumulator
180+
}
181+
accumulator = reducer(accumulator, result.value, index++)
182+
}
183+
}
184+
185+
/*#__PURE__*/
186+
function toArray<T>(this: Iterator<T>): T[] {
187+
const result: T[] = []
188+
// eslint-disable-next-line no-constant-condition
189+
while (true) {
190+
const next = this.next()
191+
if (next.done) {
192+
return result
193+
}
194+
result.push(next.value)
195+
}
196+
}
197+
198+
/*#__PURE__*/
199+
function forEach<T>(this: Iterator<T>, callback: (value: T, index: number) => void): void {
200+
let index = 0
201+
// eslint-disable-next-line no-constant-condition
202+
while (true) {
203+
const result = this.next()
204+
if (result.done) {
205+
return
206+
}
207+
callback(result.value, index++)
208+
}
209+
}
210+
211+
/*#__PURE__*/
212+
function some<T>(this: Iterator<T>, predicate: (value: T, index: number) => boolean): boolean {
213+
let index = 0
214+
// eslint-disable-next-line no-constant-condition
215+
while (true) {
216+
const result = this.next()
217+
if (result.done) {
218+
return false
219+
}
220+
if (predicate(result.value, index++)) {
221+
if (this.return) {
222+
this.return()
223+
}
224+
return true
225+
}
226+
}
227+
}
228+
229+
/*#__PURE__*/
230+
function every<T>(this: Iterator<T>, predicate: (value: T, index: number) => boolean): boolean {
231+
let index = 0
232+
// eslint-disable-next-line no-constant-condition
233+
while (true) {
234+
const result = this.next()
235+
if (result.done) {
236+
return true
237+
}
238+
if (!predicate(result.value, index++)) {
239+
if (this.return) {
240+
this.return()
241+
}
242+
return false
243+
}
244+
}
245+
}
246+
247+
/*#__PURE__*/
248+
function find<T>(this: Iterator<T>, predicate: (value: T, index: number) => boolean): T | undefined {
249+
let index = 0
250+
// eslint-disable-next-line no-constant-condition
251+
while (true) {
252+
const result = this.next()
253+
if (result.done) {
254+
return undefined
255+
}
256+
if (predicate(result.value, index++)) {
257+
if (this.return) {
258+
this.return()
259+
}
260+
return result.value
261+
}
262+
}
263+
}
264+
265+
/*#__PURE__*/
266+
function iteratorFrom<T>(obj: Iterator<T> | Iterable<T>): Iterator<T> {
267+
if (typeof obj === 'object' && obj !== null) {
268+
if ('next' in obj && typeof obj.next === 'function') {
269+
return obj as Iterator<T>
270+
}
271+
if (Symbol.iterator in obj) {
272+
return obj[Symbol.iterator]()
273+
}
274+
}
275+
throw new TypeError('Object is not an iterator or iterable')
276+
}
277+
278+
/*#__PURE__*/
279+
export function isSupported(): boolean {
280+
const IteratorPrototype = getIteratorPrototype()
281+
const IteratorConstructor = (globalThis as typeof globalThis & {Iterator?: {from?: unknown}}).Iterator
282+
return (
283+
IteratorPrototype !== null &&
284+
'map' in IteratorPrototype &&
285+
'filter' in IteratorPrototype &&
286+
'take' in IteratorPrototype &&
287+
'drop' in IteratorPrototype &&
288+
'flatMap' in IteratorPrototype &&
289+
'reduce' in IteratorPrototype &&
290+
'toArray' in IteratorPrototype &&
291+
'forEach' in IteratorPrototype &&
292+
'some' in IteratorPrototype &&
293+
'every' in IteratorPrototype &&
294+
'find' in IteratorPrototype &&
295+
IteratorConstructor !== undefined &&
296+
typeof IteratorConstructor === 'object' &&
297+
IteratorConstructor !== null &&
298+
'from' in IteratorConstructor
299+
)
300+
}
301+
302+
/*#__PURE__*/
303+
export function isPolyfilled(): boolean {
304+
const IteratorPrototype = getIteratorPrototype()
305+
const IteratorConstructor = (globalThis as typeof globalThis & {Iterator?: {from?: unknown}}).Iterator
306+
return (
307+
IteratorPrototype !== null &&
308+
'map' in IteratorPrototype &&
309+
IteratorPrototype.map === map &&
310+
IteratorConstructor !== undefined &&
311+
'from' in IteratorConstructor &&
312+
IteratorConstructor.from === iteratorFrom
313+
)
314+
}
315+
316+
export function apply(): void {
317+
if (isSupported()) return
318+
319+
const IteratorPrototype = getIteratorPrototype()
320+
if (IteratorPrototype) {
321+
if (!('map' in IteratorPrototype)) {
322+
Object.assign(IteratorPrototype, {map})
323+
}
324+
if (!('filter' in IteratorPrototype)) {
325+
Object.assign(IteratorPrototype, {filter})
326+
}
327+
if (!('take' in IteratorPrototype)) {
328+
Object.assign(IteratorPrototype, {take})
329+
}
330+
if (!('drop' in IteratorPrototype)) {
331+
Object.assign(IteratorPrototype, {drop})
332+
}
333+
if (!('flatMap' in IteratorPrototype)) {
334+
Object.assign(IteratorPrototype, {flatMap})
335+
}
336+
if (!('reduce' in IteratorPrototype)) {
337+
Object.assign(IteratorPrototype, {reduce})
338+
}
339+
if (!('toArray' in IteratorPrototype)) {
340+
Object.assign(IteratorPrototype, {toArray})
341+
}
342+
if (!('forEach' in IteratorPrototype)) {
343+
Object.assign(IteratorPrototype, {forEach})
344+
}
345+
if (!('some' in IteratorPrototype)) {
346+
Object.assign(IteratorPrototype, {some})
347+
}
348+
if (!('every' in IteratorPrototype)) {
349+
Object.assign(IteratorPrototype, {every})
350+
}
351+
if (!('find' in IteratorPrototype)) {
352+
Object.assign(IteratorPrototype, {find})
353+
}
354+
}
355+
356+
const IteratorConstructor = (globalThis as typeof globalThis & {Iterator?: {from?: unknown}}).Iterator
357+
if (IteratorConstructor && !('from' in IteratorConstructor)) {
358+
Object.assign(IteratorConstructor, {from: iteratorFrom})
359+
}
360+
}

0 commit comments

Comments
 (0)