Skip to content

Commit 9969062

Browse files
Bashamegasaschanaz
andauthored
KDL support for removals (#2250)
Co-authored-by: saschanaz <saschanaz@users.noreply.github.com>
1 parent 67ceacc commit 9969062

File tree

4 files changed

+105
-58
lines changed

4 files changed

+105
-58
lines changed

inputfiles/patches/webauthn.kdl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
removals {
2+
enum AuthenticatorTransport {
3+
smart-card // WebKit only as of 2023-05
4+
}
5+
dictionary AuthenticationExtensionsClientInputs {
6+
// https://searchfox.org/mozilla-central/source/dom/webidl/WebAuthentication.webidl
7+
// https://searchfox.org/wubkat/source/Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientInputs.idl
8+
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/credentialmanagement/authentication_extensions_client_inputs.idl
9+
member appidExclude
10+
member credBlob
11+
member getCredBlob
12+
member hmacGetSecret // No implementation as of 2025-05
13+
member payment
14+
}
15+
dictionary AuthenticationExtensionsClientInputsJSON {
16+
member appidExclude
17+
}
18+
dictionary AuthenticationExtensionsClientOutputs {
19+
// (same as *Inputs)
20+
member appidExclude // No implementation as of 2025-05
21+
member hmacGetSecret // No implementation as of 2025-05
22+
member payment // Blink only as of 2025-06
23+
}
24+
}

inputfiles/removedTypes.jsonc

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@
1313
},
1414
"enums": {
1515
"enum": {
16-
"AuthenticatorTransport": {
17-
"value": ["smart-card"] // WebKit only as of 2023-05
18-
},
1916
"ConnectionType": {
2017
"value": ["wimax"]
2118
},
@@ -264,37 +261,6 @@
264261
}
265262
}
266263
},
267-
"AuthenticationExtensionsClientInputs": {
268-
"members": {
269-
"member": {
270-
// https://searchfox.org/mozilla-central/source/dom/webidl/WebAuthentication.webidl
271-
// https://searchfox.org/wubkat/source/Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientInputs.idl
272-
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/credentialmanagement/authentication_extensions_client_inputs.idl
273-
"appidExclude": null,
274-
"credBlob": null,
275-
"getCredBlob": null,
276-
"hmacGetSecret": null, // No implementation as of 2025-05
277-
"payment": null
278-
}
279-
}
280-
},
281-
"AuthenticationExtensionsClientInputsJSON": {
282-
"members": {
283-
"member": {
284-
"appidExclude": null
285-
}
286-
}
287-
},
288-
"AuthenticationExtensionsClientOutputs": {
289-
"members": {
290-
"member": {
291-
// (same as *Inputs)
292-
"appidExclude": null, // No implementation as of 2025-05
293-
"hmacGetSecret": null, // No implementation as of 2025-05
294-
"payment": null // Blink only as of 2025-06
295-
}
296-
}
297-
},
298264
"CanvasRenderingContext2DSettings": {
299265
"members": {
300266
"member": {

src/build.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ async function emitDom() {
8989

9090
const overriddenItems = await readInputJSON("overridingTypes.jsonc");
9191
const addedItems = await readInputJSON("addedTypes.jsonc");
92-
const patches = await readPatches();
92+
const { patches, removalPatches } = await readPatches();
9393
const comments = await readInputJSON("comments.json");
9494
const documentationFromMDN = await generateDescriptions();
9595
const removedItems = await readInputJSON("removedTypes.jsonc");
@@ -204,6 +204,7 @@ async function emitDom() {
204204
webidl = merge(webidl, getRemovalData(webidl));
205205
webidl = merge(webidl, getDocsData(webidl));
206206
webidl = prune(webidl, removedItems);
207+
webidl = prune(webidl, removalPatches);
207208
webidl = merge(webidl, addedItems);
208209
webidl = merge(webidl, overriddenItems);
209210
webidl = merge(webidl, patches);

src/build/patches.ts

Lines changed: 79 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { parse, type Value, type Node } from "kdljs";
1+
import { parse, type Value, type Node, Document } from "kdljs";
22
import type {
33
Enum,
44
Event,
@@ -76,33 +76,34 @@ function handleTypeParameters(value: Value) {
7676
};
7777
}
7878

79+
function undefinedIfEmpty(object: object, output: object) {
80+
return Object.entries(object).length ? output : undefined;
81+
}
82+
7983
/**
80-
* Converts patch files in KDL to match the [types](types.d.ts).
84+
* Converts parsed KDL Document nodes to match the [types](types.d.ts).
8185
*/
82-
function parseKDL(kdlText: string): DeepPartial<WebIdl> {
83-
const { output, errors } = parse(kdlText);
84-
85-
if (errors.length) {
86-
throw new Error("KDL parse errors", { cause: errors });
87-
}
88-
89-
const nodes = output!;
86+
function convertKDLNodes(nodes: Node[]): DeepPartial<WebIdl> {
9087
const enums: Record<string, Enum> = {};
9188
const mixin: Record<string, DeepPartial<Interface>> = {};
9289
const interfaces: Record<string, DeepPartial<Interface>> = {};
9390
const dictionary: Record<string, DeepPartial<Dictionary>> = {};
9491

9592
for (const node of nodes) {
93+
// Note: no "removals" handling here; caller is responsible for splitting
9694
const name = string(node.values[0]);
9795
switch (node.name) {
9896
case "enum":
9997
enums[name] = handleEnum(node);
10098
break;
10199
case "interface-mixin":
102-
mixin[name] = handleMixinandInterfaces(node, "mixin");
100+
mixin[name] = merge(
101+
mixin[name],
102+
handleMixinAndInterfaces(node, "mixin"),
103+
);
103104
break;
104105
case "interface":
105-
interfaces[name] = handleMixinandInterfaces(node, "interface");
106+
interfaces[name] = handleMixinAndInterfaces(node, "interface");
106107
break;
107108
case "dictionary":
108109
dictionary[name] = handleDictionary(node);
@@ -113,10 +114,10 @@ function parseKDL(kdlText: string): DeepPartial<WebIdl> {
113114
}
114115

115116
return {
116-
enums: { enum: enums },
117-
mixins: { mixin },
118-
interfaces: { interface: interfaces },
119-
dictionaries: { dictionary },
117+
enums: undefinedIfEmpty(enums, { enum: enums }),
118+
mixins: undefinedIfEmpty(mixin, { mixin }),
119+
interfaces: undefinedIfEmpty(interfaces, { interface: interfaces }),
120+
dictionaries: undefinedIfEmpty(dictionary, { dictionary }),
120121
};
121122
}
122123

@@ -152,7 +153,7 @@ function handleEnum(node: Node): Enum {
152153
* @param node The mixin node to handle.
153154
* @param mixins The record of mixins to update.
154155
*/
155-
function handleMixinandInterfaces(
156+
function handleMixinAndInterfaces(
156157
node: Node,
157158
type: "mixin" | "interface",
158159
): DeepPartial<Interface> {
@@ -368,21 +369,76 @@ async function getAllFileURLs(folder: URL): Promise<URL[]> {
368369
}
369370

370371
/**
371-
* Read and parse a single KDL file.
372+
* Read and parse a single KDL file into its KDL Document structure.
372373
*/
373-
export async function readPatch(fileUrl: URL): Promise<any> {
374+
async function readPatchDocument(fileUrl: URL): Promise<Document> {
374375
const text = await readFile(fileUrl, "utf8");
375-
return parseKDL(text);
376+
const { output, errors } = parse(text);
377+
if (errors.length) {
378+
throw new Error(`KDL parse errors in ${fileUrl.toString()}`, {
379+
cause: errors,
380+
});
381+
}
382+
return output!;
383+
}
384+
/**
385+
* Recursively remove all 'name' fields from the object and its children, and
386+
* replace any empty objects ({} or []) with null.
387+
*/
388+
function convertForRemovals(obj: unknown): unknown {
389+
if (Array.isArray(obj)) {
390+
const result = obj.map(convertForRemovals).filter((v) => v !== undefined);
391+
return result.length === 0 ? null : result;
392+
}
393+
if (obj && typeof obj === "object") {
394+
const newObj: Record<string, unknown> = {};
395+
for (const [key, value] of Object.entries(obj)) {
396+
if (key !== "name") {
397+
const cleaned = convertForRemovals(value);
398+
if (cleaned !== undefined) {
399+
newObj[key] = cleaned;
400+
}
401+
}
402+
}
403+
// Replace empty objects with null
404+
return Object.keys(newObj).length === 0 ? null : newObj;
405+
}
406+
return obj;
376407
}
377408

378409
/**
379410
* Read, parse, and merge all KDL files under the input folder.
411+
* Splits the main patch content and the removals from each file for combined processing.
412+
*
413+
* Returns:
414+
* {
415+
* patches: merged patch contents (excluding removals),
416+
* removalPatches: merged removals, with names stripped
417+
* }
380418
*/
381-
export default async function readPatches(): Promise<any> {
419+
export default async function readPatches(): Promise<{
420+
patches: any;
421+
removalPatches: any;
422+
}> {
382423
const patchDirectory = new URL("../../inputfiles/patches/", import.meta.url);
383424
const fileUrls = await getAllFileURLs(patchDirectory);
384425

385-
const parsedContents = await Promise.all(fileUrls.map(readPatch));
426+
// Stage 1: Parse all file KDLs into Documents
427+
const documents = await Promise.all(fileUrls.map(readPatchDocument));
428+
429+
// Stage 2: Group by patches or removals
430+
const merged = documents.flat();
431+
const patchNodes = merged.filter((node) => node.name !== "removals");
432+
const removalNodes = merged
433+
.filter((node) => node.name === "removals")
434+
.map((node) => node.children)
435+
.flat();
436+
437+
// Stage 3: Convert the nodes for patches and removals respectively
438+
const patches = convertKDLNodes(patchNodes);
439+
const removalPatches = convertForRemovals(
440+
convertKDLNodes(removalNodes),
441+
) as DeepPartial<WebIdl>;
386442

387-
return parsedContents.reduce((acc, current) => merge(acc, current), {});
443+
return { patches, removalPatches };
388444
}

0 commit comments

Comments
 (0)