From 1efa85e9610ca666b620016b770aee73f1a385e5 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Tue, 22 Jul 2025 21:31:17 +0200 Subject: [PATCH 1/2] feat: implement new async_iterable syntax --- lib/productions/iterable.js | 36 +++++++++++++++++-- lib/tokeniser.js | 1 + test/autofix.js | 16 +++++++++ .../baseline/argument-dict-default.txt | 2 +- .../baseline/argument-dict-nullable.txt | 2 +- .../baseline/argument-dict-optional.txt | 2 +- .../baseline/async-iterable-readonly.txt | 2 +- .../invalid/baseline/async-space-iterable.txt | 3 ++ test/invalid/idl/argument-dict-default.webidl | 2 +- .../invalid/idl/argument-dict-nullable.webidl | 2 +- .../invalid/idl/argument-dict-optional.webidl | 2 +- .../idl/async-iterable-readonly.webidl | 2 +- .../async-iterable-unterminated-args.webidl | 2 +- test/invalid/idl/async-space-iterable.webidl | 4 +++ test/syntax/baseline/async-iterable.json | 28 +++++++-------- test/syntax/idl/async-iterable.webidl | 14 ++++---- 16 files changed, 87 insertions(+), 33 deletions(-) create mode 100644 test/invalid/baseline/async-space-iterable.txt create mode 100644 test/invalid/idl/async-space-iterable.webidl diff --git a/lib/productions/iterable.js b/lib/productions/iterable.js index cfa93c54..22041800 100644 --- a/lib/productions/iterable.js +++ b/lib/productions/iterable.js @@ -1,3 +1,4 @@ +import { validationError } from "../error.js"; import { Base } from "./base.js"; import { type_with_extended_attributes, @@ -23,7 +24,7 @@ export class IterableLike extends Base { ? tokeniser.consume("maplike", "setlike") : tokens.async ? tokeniser.consume("iterable") - : tokeniser.consume("iterable", "maplike", "setlike"); + : tokeniser.consume("iterable", "async_iterable", "maplike", "setlike"); if (!tokens.base) { tokeniser.unconsume(start_position); return; @@ -31,8 +32,10 @@ export class IterableLike extends Base { const { type } = ret; const secondTypeRequired = type === "maplike"; - const secondTypeAllowed = secondTypeRequired || type === "iterable"; - const argumentAllowed = ret.async && type === "iterable"; + const secondTypeAllowed = + secondTypeRequired || type === "iterable" || type === "async_iterable"; + const argumentAllowed = + type === "async_iterable" || (ret.async && type === "iterable"); tokens.open = tokeniser.consume("<") || @@ -86,6 +89,18 @@ export class IterableLike extends Base { } *validate(defs) { + if (this.async && this.type === "iterable") { + const message = "`async iterable` is now changed to `async_iterable`."; + yield validationError( + this.tokens.async, + this, + "obsolete-async-iterable-syntax", + message, + { + autofix: autofixAsyncIterableSyntax(this), + }, + ); + } for (const type of this.idlType) { yield* type.validate(defs); } @@ -114,3 +129,18 @@ export class IterableLike extends Base { ); } } + +/** + * @param {IterableLike} iterableLike + */ +function autofixAsyncIterableSyntax(iterableLike) { + return () => { + const async = iterableLike.tokens.async; + iterableLike.tokens.base = { + ...async, + type: "async_iterable", + value: "async_iterable", + }; + delete iterableLike.tokens.async; + }; +} diff --git a/lib/tokeniser.js b/lib/tokeniser.js index e497644f..7c787a1a 100644 --- a/lib/tokeniser.js +++ b/lib/tokeniser.js @@ -72,6 +72,7 @@ const nonRegexTerminals = [ "NaN", "ObservableArray", "Promise", + "async_iterable", "bigint", "boolean", "byte", diff --git a/test/autofix.js b/test/autofix.js index d61f6a66..8b358e7d 100644 --- a/test/autofix.js +++ b/test/autofix.js @@ -360,4 +360,20 @@ describe("Writer template functions", () => { `; expect(autofix(input)).toBe(output); }); + + it("should replace `async iterable` to `async_iterable`", () => { + const input = ` + [Exposed=Window] + interface AsyncIterable { + async iterable; + }; + `; + const output = ` + [Exposed=Window] + interface AsyncIterable { + async_iterable; + }; + `; + expect(autofix(input)).toBe(output); + }); }); diff --git a/test/invalid/baseline/argument-dict-default.txt b/test/invalid/baseline/argument-dict-default.txt index fd714514..dd40590d 100644 --- a/test/invalid/baseline/argument-dict-default.txt +++ b/test/invalid/baseline/argument-dict-default.txt @@ -10,6 +10,6 @@ (dict-arg-default) Validation error at line 18 in argument-dict-default.webidl, inside `interface X -> operation z -> argument union`: undefined z(optional Union union); ^ Optional dictionary arguments must have a default value of `{}`. -(dict-arg-default) Validation error at line 22 in argument-dict-default.webidl, inside `interface X -> iterable -> argument union`: +(dict-arg-default) Validation error at line 22 in argument-dict-default.webidl, inside `interface X -> async_iterable -> argument union`: DOMString>(optional Union union); ^ Optional dictionary arguments must have a default value of `{}`. diff --git a/test/invalid/baseline/argument-dict-nullable.txt b/test/invalid/baseline/argument-dict-nullable.txt index 9ade1218..787c6642 100644 --- a/test/invalid/baseline/argument-dict-nullable.txt +++ b/test/invalid/baseline/argument-dict-nullable.txt @@ -19,6 +19,6 @@ boolean or Dict)? union = {}) (no-nullable-dict-arg) Validation error at line 17 in argument-dict-nullable.webidl, inside `interface X -> operation r -> argument req`: undefined r(Required? req); ^ Dictionary arguments cannot be nullable. -(no-nullable-dict-arg) Validation error at line 19 in argument-dict-nullable.webidl, inside `interface X -> iterable -> argument dict`: +(no-nullable-dict-arg) Validation error at line 19 in argument-dict-nullable.webidl, inside `interface X -> async_iterable -> argument dict`: >(optional Dict? dict); ^ Dictionary arguments cannot be nullable. diff --git a/test/invalid/baseline/argument-dict-optional.txt b/test/invalid/baseline/argument-dict-optional.txt index 35b5b244..80c24f96 100644 --- a/test/invalid/baseline/argument-dict-optional.txt +++ b/test/invalid/baseline/argument-dict-optional.txt @@ -13,6 +13,6 @@ (dict-arg-optional) Validation error at line 38 in argument-dict-optional.webidl, inside `interface mixin Container -> operation op8 -> argument lastRequired`: undefined op8(Optional lastRequired, optional DOMString yay ^ Dictionary argument must be optional if it has no required fields -(dict-arg-optional) Validation error at line 44 in argument-dict-optional.webidl, inside `interface ContainerInterface -> iterable -> argument shouldBeOptional`: +(dict-arg-optional) Validation error at line 44 in argument-dict-optional.webidl, inside `interface ContainerInterface -> async_iterable -> argument shouldBeOptional`: (Optional shouldBeOptional); ^ Dictionary argument must be optional if it has no required fields diff --git a/test/invalid/baseline/async-iterable-readonly.txt b/test/invalid/baseline/async-iterable-readonly.txt index 7b169420..955fe486 100644 --- a/test/invalid/baseline/async-iterable-readonly.txt +++ b/test/invalid/baseline/async-iterable-readonly.txt @@ -1,3 +1,3 @@ Syntax error at line 3 in async-iterable-readonly.webidl, since `interface AsyncIterable`: - readonly async iterable(optional Union union); + async_iterable(optional Union union); }; diff --git a/test/invalid/idl/argument-dict-nullable.webidl b/test/invalid/idl/argument-dict-nullable.webidl index 087fe8ea..98fc47a0 100644 --- a/test/invalid/idl/argument-dict-nullable.webidl +++ b/test/invalid/idl/argument-dict-nullable.webidl @@ -16,5 +16,5 @@ interface X { undefined z2(optional Union? union = {}); undefined r(Required? req); - async iterable(optional Dict? dict); + async_iterable(optional Dict? dict); }; diff --git a/test/invalid/idl/argument-dict-optional.webidl b/test/invalid/idl/argument-dict-optional.webidl index 4e550c42..f75bdf99 100644 --- a/test/invalid/idl/argument-dict-optional.webidl +++ b/test/invalid/idl/argument-dict-optional.webidl @@ -41,5 +41,5 @@ interface mixin Container { [Exposed=Window] interface ContainerInterface { - async iterable(Optional shouldBeOptional); + async_iterable(Optional shouldBeOptional); }; diff --git a/test/invalid/idl/async-iterable-readonly.webidl b/test/invalid/idl/async-iterable-readonly.webidl index 269e5338..96a2dbee 100644 --- a/test/invalid/idl/async-iterable-readonly.webidl +++ b/test/invalid/idl/async-iterable-readonly.webidl @@ -1,4 +1,4 @@ [Exposed=Window] interface AsyncIterable { - readonly async iterable; + readonly async_iterable; }; diff --git a/test/invalid/idl/async-iterable-unterminated-args.webidl b/test/invalid/idl/async-iterable-unterminated-args.webidl index 008928d7..09e189c4 100644 --- a/test/invalid/idl/async-iterable-unterminated-args.webidl +++ b/test/invalid/idl/async-iterable-unterminated-args.webidl @@ -1,3 +1,3 @@ interface X { - async iterable( + async_iterable( }; diff --git a/test/invalid/idl/async-space-iterable.webidl b/test/invalid/idl/async-space-iterable.webidl new file mode 100644 index 00000000..285ebfa0 --- /dev/null +++ b/test/invalid/idl/async-space-iterable.webidl @@ -0,0 +1,4 @@ +[Exposed=Window] +interface AsyncIterable { + async iterable; +}; diff --git a/test/syntax/baseline/async-iterable.json b/test/syntax/baseline/async-iterable.json index 50611317..ab43a645 100644 --- a/test/syntax/baseline/async-iterable.json +++ b/test/syntax/baseline/async-iterable.json @@ -5,7 +5,7 @@ "inheritance": null, "members": [ { - "type": "iterable", + "type": "async_iterable", "idlType": [ { "type": null, @@ -27,7 +27,7 @@ "arguments": [], "extAttrs": [], "readonly": false, - "async": true + "async": false } ], "extAttrs": [], @@ -39,7 +39,7 @@ "inheritance": null, "members": [ { - "type": "iterable", + "type": "async_iterable", "idlType": [ { "type": null, @@ -75,7 +75,7 @@ "arguments": [], "extAttrs": [], "readonly": false, - "async": true + "async": false } ], "extAttrs": [], @@ -87,7 +87,7 @@ "inheritance": null, "members": [ { - "type": "iterable", + "type": "async_iterable", "idlType": [ { "type": null, @@ -109,7 +109,7 @@ "arguments": [], "extAttrs": [], "readonly": false, - "async": true + "async": false } ], "extAttrs": [], @@ -121,7 +121,7 @@ "inheritance": null, "members": [ { - "type": "iterable", + "type": "async_iterable", "idlType": [ { "type": null, @@ -160,7 +160,7 @@ ], "extAttrs": [], "readonly": false, - "async": true + "async": false } ], "extAttrs": [], @@ -172,7 +172,7 @@ "inheritance": null, "members": [ { - "type": "iterable", + "type": "async_iterable", "idlType": [ { "type": null, @@ -186,7 +186,7 @@ "arguments": [], "extAttrs": [], "readonly": false, - "async": true + "async": false } ], "extAttrs": [], @@ -198,7 +198,7 @@ "inheritance": null, "members": [ { - "type": "iterable", + "type": "async_iterable", "idlType": [ { "type": null, @@ -212,7 +212,7 @@ "arguments": [], "extAttrs": [], "readonly": false, - "async": true + "async": false } ], "extAttrs": [], @@ -224,7 +224,7 @@ "inheritance": null, "members": [ { - "type": "iterable", + "type": "async_iterable", "idlType": [ { "type": null, @@ -271,7 +271,7 @@ ], "extAttrs": [], "readonly": false, - "async": true + "async": false } ], "extAttrs": [], diff --git a/test/syntax/idl/async-iterable.webidl b/test/syntax/idl/async-iterable.webidl index 132acb3b..2b4ff413 100644 --- a/test/syntax/idl/async-iterable.webidl +++ b/test/syntax/idl/async-iterable.webidl @@ -1,27 +1,27 @@ interface AsyncIterable { - async iterable; + async_iterable; }; interface AsyncIterableWithExtAttr { - async iterable<[XAttr2] DOMString, [XAttr3] long>; + async_iterable<[XAttr2] DOMString, [XAttr3] long>; }; interface AsyncIterableWithNoParam { - async iterable(); + async_iterable(); }; interface AsyncIterableWithParam { - async iterable(USVString str); + async_iterable(USVString str); }; interface AsyncValueIterable { - async iterable; + async_iterable; }; interface AsyncValueIterableWithNoParam { - async iterable(); + async_iterable(); }; interface AsyncValueIterableWithParams { - async iterable(DOMString str, short s); + async_iterable(DOMString str, short s); }; From 62bed343a5e9c6d1b3b38a4db62ba12e8f7ff843 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Tue, 22 Jul 2025 21:34:56 +0200 Subject: [PATCH 2/2] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f45ee123..cef60fac 100644 --- a/README.md +++ b/README.md @@ -745,7 +745,7 @@ For `"Infinity"`: * `negative`: Boolean indicating whether this is negative Infinity or not. -### `iterable<>`, `async iterable<>`, `maplike<>`, and `setlike<>` declarations +### `iterable<>`, `async_iterable<>`, `maplike<>`, and `setlike<>` declarations These appear as members of interfaces that look like this: @@ -755,7 +755,7 @@ These appear as members of interfaces that look like this: "idlType": /* One or two types */ , "readonly": false, // only for maplike and setlike "async": false, // iterable can be async - "arguments": [], // only for async iterable + "arguments": [], // only for async_iterable "extAttrs": [], "parent": { ... } } @@ -766,8 +766,8 @@ The fields are as follows: * `type`: Always one of "iterable", "maplike" or "setlike". * `idlType`: An array with one or more [IDL Types](#idl-type) representing the declared type arguments. * `readonly`: `true` if the maplike or setlike is declared as read only. -* `async`: `true` if the type is async iterable. -* `arguments`: An array of arguments if exists, empty otherwise. Currently only `async iterable` supports the syntax. +* `async`: `true` if the type is `async iterable`. Note that it's false for the new `async_iterable`. +* `arguments`: An array of arguments if exists, empty otherwise. Currently only `async_iterable` supports the syntax. * `extAttrs`: An array of [extended attributes](#extended-attributes). * `parent`: The container of this type as an Object.