Skip to content

Commit dd8e7d8

Browse files
fix: add disposable methods for TypeScript 5.2 compatibility (#82)
Hi 👋 First off, thanks for you work on this 🙇 It's making my life a lot easier 💜 I recently ran into a problem when updating `JSDOM` in my project, where the return type wasn't compatible any more: ``` error TS2322: Type 'typeof Headers$1' is not assignable to type '{ new (init?: HeadersInit | undefined): Headers; prototype: Headers; }'. The types returned by 'prototype.entries()' are incompatible between these types. Property '[Symbol.dispose]' is missing in type 'IterableIterator<[string, string]>' but required in type 'HeadersIterator<[string, string]>'. 6 globalThis.Headers = HeadersPolyfill; ~~~~~~~~~~~~~~~~~~ node_modules/typescript/lib/lib.esnext.disposable.d.ts:36:5 36 [Symbol.dispose](): void; ~~~~~~~~~~~~~~~~~~~~~~~~~ '[Symbol.dispose]' is declared here. ``` This PR fixes this and makes makes the polyfill compatible with TypeScript 5.2+ 🎉 **Background** TypeScript 5.2 added `[Symbol.dispose]` to iterator types as part of the [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) proposal. This property was missing in the polyfills iterators, making them incompatible with e.g. current JSDOM and TypeScript. **Change** This removes the explicit `IterableIterator` return type annotations from the `entries()`, `keys()` and `values()` generator functions in favor of the `Generator` type. --------- Co-authored-by: Artem Zakharchenko <kettanaito@gmail.com>
1 parent 9b42765 commit dd8e7d8

5 files changed

Lines changed: 34 additions & 13 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
node-version: [16, 18]
15+
node-version: [20, 22, 24]
1616
steps:
1717
- name: Checkout
18-
uses: actions/checkout@v2
18+
uses: actions/checkout@v4
1919
with:
2020
fetch-depth: 0
2121
token: ${{ secrets.GH_ADMIN_TOKEN }}
2222

2323
- name: Setup Node.js
24-
uses: actions/setup-node@v3
24+
uses: actions/setup-node@v4
2525
with:
2626
node-version: ${{ matrix.node-version }}
2727
always-auth: true

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ jobs:
1111
runs-on: ubuntu-latest
1212
steps:
1313
- name: Checkout
14-
uses: actions/checkout@v2
14+
uses: actions/checkout@v4
1515
with:
1616
fetch-depth: 0
1717
token: ${{ secrets.GH_ADMIN_TOKEN }}
1818

1919
- name: Setup Node.js
20-
uses: actions/setup-node@v3
20+
uses: actions/setup-node@v4
2121
with:
2222
node-version: 18
2323
always-auth: true

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@
2929
],
3030
"devDependencies": {
3131
"@ossjs/release": "^0.7.3",
32-
"@swc/core": "^1.3.84",
33-
"@swc/jest": "^0.2.29",
34-
"@types/jest": "^28.1.4",
32+
"@swc/core": "^1.15.18",
33+
"@swc/jest": "^0.2.39",
34+
"@types/jest": "^30.0.0",
3535
"@types/set-cookie-parser": "^2.4.3",
36-
"jest": "^28.1.2",
37-
"jest-environment-jsdom": "^28.1.2",
36+
"jest": "^30.2.0",
37+
"jest-environment-jsdom": "^30.2.0",
3838
"rimraf": "^3.0.2",
3939
"set-cookie-parser": "^2.6.0",
4040
"tsup": "^7.2.0",

src/Headers.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ describe('.keys()', () => {
131131
headers.append('Set-Cookie', 'b=2')
132132
expect(Array.from(headers.keys())).toEqual(['set-cookie', 'set-cookie'])
133133
})
134+
135+
if (process.version.startsWith('22.')) {
136+
it('returns an iterator with [Symbol.dispose] defined', () => {
137+
const headers = new Headers()
138+
expect(headers.keys()[Symbol.dispose]).toBeInstanceOf(Function)
139+
})
140+
}
134141
})
135142

136143
describe('.values()', () => {
@@ -174,6 +181,13 @@ describe('.values()', () => {
174181
headers.append('Set-Cookie', 'b=2')
175182
expect(Array.from(headers.values())).toEqual(['a=1', 'b=2'])
176183
})
184+
185+
if (process.version.startsWith('22.')) {
186+
it('returns an iterator with [Symbol.dispose] defined', () => {
187+
const headers = new Headers()
188+
expect(headers.values()[Symbol.dispose]).toBeInstanceOf(Function)
189+
})
190+
}
177191
})
178192

179193
describe('.entries()', () => {
@@ -228,6 +242,13 @@ describe('.entries()', () => {
228242
['set-cookie', 'b=2'],
229243
])
230244
})
245+
246+
if (process.version.startsWith('22.')) {
247+
it('returns an iterator with [Symbol.dispose] defined', () => {
248+
const headers = new Headers()
249+
expect(headers.entries()[Symbol.dispose]).toBeInstanceOf(Function)
250+
})
251+
}
231252
})
232253

233254
describe('.has()', () => {

src/Headers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,19 @@ export class Headers {
5858
return this.entries()
5959
}
6060

61-
*keys(): IterableIterator<string> {
61+
*keys(): Generator<string> {
6262
for (const [name] of this.entries()) {
6363
yield name
6464
}
6565
}
6666

67-
*values(): IterableIterator<string> {
67+
*values(): Generator<string> {
6868
for (const [, value] of this.entries()) {
6969
yield value
7070
}
7171
}
7272

73-
*entries(): IterableIterator<[string, string]> {
73+
*entries(): Generator<[string, string]> {
7474
// https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
7575
let sortedKeys = Object.keys(this[NORMALIZED_HEADERS]).sort((a, b) =>
7676
a.localeCompare(b)

0 commit comments

Comments
 (0)