Skip to content

Commit 5712b44

Browse files
Add async iterable flavors (#1403)
Co-authored-by: saschanaz <[email protected]>
1 parent f06eae3 commit 5712b44

12 files changed

+245
-36
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/////////////////////////////
2+
/// AudioWorklet Async Iterable APIs
3+
/////////////////////////////
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/////////////////////////////
2+
/// Window Async Iterable APIs
3+
/////////////////////////////
4+
5+
interface FileSystemDirectoryHandle {
6+
[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileSystemHandle]>;
7+
entries(): AsyncIterableIterator<[string, FileSystemHandle]>;
8+
keys(): AsyncIterableIterator<string>;
9+
values(): AsyncIterableIterator<FileSystemHandle>;
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/////////////////////////////
2+
/// ServiceWorker Async Iterable APIs
3+
/////////////////////////////
4+
5+
interface FileSystemDirectoryHandle {
6+
[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileSystemHandle]>;
7+
entries(): AsyncIterableIterator<[string, FileSystemHandle]>;
8+
keys(): AsyncIterableIterator<string>;
9+
values(): AsyncIterableIterator<FileSystemHandle>;
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/////////////////////////////
2+
/// SharedWorker Async Iterable APIs
3+
/////////////////////////////
4+
5+
interface FileSystemDirectoryHandle {
6+
[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileSystemHandle]>;
7+
entries(): AsyncIterableIterator<[string, FileSystemHandle]>;
8+
keys(): AsyncIterableIterator<string>;
9+
values(): AsyncIterableIterator<FileSystemHandle>;
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/////////////////////////////
2+
/// Worker Async Iterable APIs
3+
/////////////////////////////
4+
5+
interface FileSystemDirectoryHandle {
6+
[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileSystemHandle]>;
7+
entries(): AsyncIterableIterator<[string, FileSystemHandle]>;
8+
keys(): AsyncIterableIterator<string>;
9+
values(): AsyncIterableIterator<FileSystemHandle>;
10+
}

inputfiles/overridingTypes.jsonc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2479,6 +2479,13 @@
24792479
}
24802480
}
24812481
}
2482+
},
2483+
"iterator": {
2484+
"type": {
2485+
"0": {
2486+
"overrideType": "R"
2487+
}
2488+
}
24822489
}
24832490
},
24842491
"ReadableStreamBYOBReader": {

src/build.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,26 @@ async function emitFlavor(
4242
const exposed = getExposedTypes(webidl, options.global, forceKnownTypes);
4343
mergeNamesakes(exposed);
4444

45-
const result = emitWebIdl(exposed, options.global[0], false);
45+
const result = emitWebIdl(exposed, options.global[0], "");
4646
await fs.writeFile(
4747
new URL(`${options.name}.generated.d.ts`, options.outputFolder),
4848
result
4949
);
5050

51-
const iterators = emitWebIdl(exposed, options.global[0], true);
51+
const iterators = emitWebIdl(exposed, options.global[0], "sync");
5252
await fs.writeFile(
5353
new URL(`${options.name}.iterable.generated.d.ts`, options.outputFolder),
5454
iterators
5555
);
56+
57+
const asyncIterators = emitWebIdl(exposed, options.global[0], "async");
58+
await fs.writeFile(
59+
new URL(
60+
`${options.name}.asynciterable.generated.d.ts`,
61+
options.outputFolder
62+
),
63+
asyncIterators
64+
);
5665
}
5766

5867
async function emitDom() {

src/build/bcd/mapper.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,26 @@ function mapInterfaceLike(
8484
};
8585
const methods = filterMapRecord(i.methods?.method, recordMapper);
8686
const properties = filterMapRecord(i.properties?.property, recordMapper);
87+
88+
if (i.iterator) {
89+
const iteratorKey = i.iterator.async ? "@@asyncIterator" : "@@iterator";
90+
// BCD often doesn't have an @@iterator entry, but it does usually have an entry
91+
// for iterable methods such as values(). Use that as a fallback.
92+
// See also: https://github.com/mdn/browser-compat-data/issues/6367
93+
const iteratorCompat = mergeCompatStatements(
94+
data[iteratorKey] ?? data["values"]
95+
);
96+
const iteratorMapped = mapper({
97+
key: iteratorKey,
98+
parentKey: name,
99+
compat: iteratorCompat,
100+
mixin: !!i.mixin,
101+
});
102+
if (iteratorMapped !== undefined) {
103+
result.iterator = iteratorMapped;
104+
}
105+
}
106+
87107
if (!isEmptyRecord(methods)) {
88108
result.methods = { method: methods! };
89109
}

src/build/emitter.ts

Lines changed: 132 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ function isEventHandler(p: Browser.Property) {
135135
export function emitWebIdl(
136136
webidl: Browser.WebIdl,
137137
global: string,
138-
iterator: boolean
138+
iterator: "" | "sync" | "async"
139139
): string {
140140
// Global print target
141141
const printer = createTextWriter("\n");
@@ -225,7 +225,14 @@ export function emitWebIdl(
225225
getParentsWithConstant
226226
);
227227

228-
return iterator ? emitES6DomIterators() : emit();
228+
switch (iterator) {
229+
case "sync":
230+
return emitES6DomIterators();
231+
case "async":
232+
return emitES2018DomAsyncIterators();
233+
default:
234+
return emit();
235+
}
229236

230237
function getTagNameToElementNameMap() {
231238
const htmlResult: Record<string, string> = {};
@@ -402,7 +409,7 @@ export function emitWebIdl(
402409
}
403410

404411
function convertDomTypeToTsTypeSimple(objDomType: string): string {
405-
if (objDomType === "sequence" && iterator) {
412+
if (objDomType === "sequence" && iterator !== "") {
406413
return "Iterable";
407414
}
408415
if (baseTypeConversionMap.has(objDomType)) {
@@ -1012,7 +1019,7 @@ export function emitWebIdl(
10121019

10131020
// Emit forEach for iterators
10141021
function emitIteratorForEach(i: Browser.Interface) {
1015-
if (!i.iterator) {
1022+
if (!i.iterator || i.iterator.async) {
10161023
return;
10171024
}
10181025
const subtype = i.iterator.type.map(convertDomTypeToTsType);
@@ -1521,8 +1528,15 @@ export function emitWebIdl(
15211528
return printer.getResult();
15221529
}
15231530

1531+
function stringifySingleOrTupleTypes(types: string[]) {
1532+
if (types.length === 1) {
1533+
return types[0];
1534+
}
1535+
return `[${types.join(", ")}]`;
1536+
}
1537+
15241538
function emitIterator(i: Browser.Interface) {
1525-
// https://heycam.github.io/webidl/#dfn-indexed-property-getter
1539+
// https://webidl.spec.whatwg.org/#dfn-indexed-property-getter
15261540
const isIndexedPropertyGetter = (m: Browser.AnonymousMethod) =>
15271541
m.getter &&
15281542
m.signature[0]?.param?.length === 1 &&
@@ -1541,7 +1555,7 @@ export function emitWebIdl(
15411555
}
15421556

15431557
function getIteratorSubtypes() {
1544-
if (i.iterator) {
1558+
if (i.iterator && !i.iterator.async) {
15451559
if (i.iterator.type.length === 1) {
15461560
return [convertDomTypeToTsType(i.iterator.type[0])];
15471561
}
@@ -1559,13 +1573,6 @@ export function emitWebIdl(
15591573
}
15601574
}
15611575

1562-
function stringifySingleOrTupleTypes(types: string[]) {
1563-
if (types.length === 1) {
1564-
return types[0];
1565-
}
1566-
return `[${types.join(", ")}]`;
1567-
}
1568-
15691576
function emitIterableDeclarationMethods(
15701577
i: Browser.Interface,
15711578
subtypes: string[]
@@ -1678,31 +1685,112 @@ export function emitWebIdl(
16781685
.filter((m) => m.signature.length)
16791686
.sort(compareName);
16801687

1681-
if (subtypes || methodsWithSequence.length) {
1682-
const iteratorExtends = getIteratorExtends(i.iterator, subtypes);
1683-
const name = getNameWithTypeParameter(
1684-
i.typeParameters,
1685-
extendConflictsBaseTypes[i.name] ? `${i.name}Base` : i.name
1686-
);
1687-
printer.printLine("");
1688-
printer.printLine(`interface ${name} ${iteratorExtends}{`);
1689-
printer.increaseIndent();
1688+
if (!subtypes && !methodsWithSequence.length) {
1689+
return;
1690+
}
16901691

1691-
methodsWithSequence.forEach((m) => emitMethod("", m, new Set()));
1692+
const iteratorExtends = getIteratorExtends(i.iterator, subtypes);
1693+
const name = getNameWithTypeParameter(
1694+
i.typeParameters,
1695+
extendConflictsBaseTypes[i.name] ? `${i.name}Base` : i.name
1696+
);
1697+
printer.printLine("");
1698+
printer.printLine(`interface ${name} ${iteratorExtends}{`);
1699+
printer.increaseIndent();
16921700

1693-
if (subtypes && !iteratorExtends) {
1694-
printer.printLine(
1695-
`[Symbol.iterator](): IterableIterator<${stringifySingleOrTupleTypes(
1696-
subtypes
1697-
)}>;`
1698-
);
1701+
methodsWithSequence.forEach((m) => emitMethod("", m, new Set()));
1702+
1703+
if (subtypes && !iteratorExtends) {
1704+
printer.printLine(
1705+
`[Symbol.iterator](): IterableIterator<${stringifySingleOrTupleTypes(
1706+
subtypes
1707+
)}>;`
1708+
);
1709+
}
1710+
if (i.iterator?.kind === "iterable" && subtypes) {
1711+
emitIterableDeclarationMethods(i, subtypes);
1712+
}
1713+
printer.decreaseIndent();
1714+
printer.printLine("}");
1715+
}
1716+
1717+
function emitAsyncIterator(i: Browser.Interface) {
1718+
function getAsyncIteratorSubtypes() {
1719+
if (i.iterator && i.iterator.kind === "iterable" && i.iterator.async) {
1720+
if (i.iterator.type.length === 1) {
1721+
return [convertDomTypeToTsType(i.iterator.type[0])];
1722+
}
1723+
return i.iterator.type.map(convertDomTypeToTsType);
16991724
}
1700-
if (i.iterator?.kind === "iterable" && subtypes) {
1701-
emitIterableDeclarationMethods(i, subtypes);
1725+
}
1726+
1727+
function emitAsyncIterableDeclarationMethods(
1728+
i: Browser.Interface,
1729+
subtypes: string[],
1730+
paramsString: string
1731+
) {
1732+
let methods;
1733+
if (subtypes.length === 1) {
1734+
// https://webidl.spec.whatwg.org/#value-asynchronously-iterable-declaration
1735+
const [valueType] = subtypes;
1736+
methods = [
1737+
{
1738+
name: "values",
1739+
definition: `AsyncIterableIterator<${valueType}>`,
1740+
},
1741+
];
1742+
} else {
1743+
// https://webidl.spec.whatwg.org/#pair-asynchronously-iterable-declaration
1744+
const [keyType, valueType] = subtypes;
1745+
methods = [
1746+
{
1747+
name: "entries",
1748+
definition: `AsyncIterableIterator<[${keyType}, ${valueType}]>`,
1749+
},
1750+
{
1751+
name: "keys",
1752+
definition: `AsyncIterableIterator<${keyType}>`,
1753+
},
1754+
{
1755+
name: "values",
1756+
definition: `AsyncIterableIterator<${valueType}>`,
1757+
},
1758+
];
17021759
}
1703-
printer.decreaseIndent();
1704-
printer.printLine("}");
1760+
1761+
const comments = i.iterator!.comments?.comment;
1762+
1763+
methods.forEach((m) => {
1764+
emitComments({ comment: comments?.[m.name] }, printer.printLine);
1765+
printer.printLine(`${m.name}(${paramsString}): ${m.definition};`);
1766+
});
1767+
}
1768+
1769+
const subtypes = getAsyncIteratorSubtypes();
1770+
if (!subtypes) {
1771+
return;
17051772
}
1773+
1774+
const name = getNameWithTypeParameter(
1775+
i.typeParameters,
1776+
extendConflictsBaseTypes[i.name] ? `${i.name}Base` : i.name
1777+
);
1778+
const paramsString = i.iterator!.param
1779+
? paramsToString(i.iterator!.param)
1780+
: "";
1781+
printer.printLine("");
1782+
printer.printLine(`interface ${name} {`);
1783+
printer.increaseIndent();
1784+
1785+
printer.printLine(
1786+
`[Symbol.asyncIterator](${paramsString}): AsyncIterableIterator<${stringifySingleOrTupleTypes(
1787+
subtypes
1788+
)}>;`
1789+
);
1790+
emitAsyncIterableDeclarationMethods(i, subtypes, paramsString);
1791+
1792+
printer.decreaseIndent();
1793+
printer.printLine("}");
17061794
}
17071795

17081796
function emitES6DomIterators() {
@@ -1715,4 +1803,15 @@ export function emitWebIdl(
17151803

17161804
return printer.getResult();
17171805
}
1806+
1807+
function emitES2018DomAsyncIterators() {
1808+
printer.reset();
1809+
printer.printLine("/////////////////////////////");
1810+
printer.printLine(`/// ${global} Async Iterable APIs`);
1811+
printer.printLine("/////////////////////////////");
1812+
1813+
allInterfaces.sort(compareName).forEach(emitAsyncIterator);
1814+
1815+
return printer.getResult();
1816+
}
17181817
}

src/build/types.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,14 @@ export interface Interface {
200200
export interface Iterator {
201201
kind: "iterable" | "setlike" | "maplike";
202202
readonly: boolean;
203+
async: boolean;
203204
type: Typed[];
205+
param?: Param[];
204206
comments?: {
205207
comment: Record<string, string>;
206208
};
209+
exposed?: string;
210+
deprecated?: boolean | string;
207211
}
208212

209213
export interface Enum {

0 commit comments

Comments
 (0)