-
Notifications
You must be signed in to change notification settings - Fork 457
KDL support for removals #2250
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
KDL support for removals #2250
Changes from 12 commits
743c7a3
8b611f7
5184d40
94ee9a9
d656a14
117e76b
790f3ef
8ffb44c
b61ee71
184eb7e
6b779dd
6e76de9
8350852
d2ce62e
a92bc94
26e7af8
162b92c
e227deb
2a4aa6a
54ec2f5
fdd8294
729d58a
894101d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| removals { | ||
| enum AuthenticatorTransport { | ||
| smart-card // WebKit only as of 2023-05 | ||
| } | ||
| dictionary AuthenticationExtensionsClientInputs { | ||
| // https://searchfox.org/mozilla-central/source/dom/webidl/WebAuthentication.webidl | ||
| // https://searchfox.org/wubkat/source/Source/WebCore/Modules/webauthn/AuthenticationExtensionsClientInputs.idl | ||
| // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/credentialmanagement/authentication_extensions_client_inputs.idl | ||
| member appidExclude | ||
| member credBlob | ||
| member getCredBlob | ||
| member hmacGetSecret // No implementation as of 2025-05 | ||
| member payment | ||
| } | ||
| dictionary AuthenticationExtensionsClientInputsJSON { | ||
| member appidExclude | ||
| } | ||
| dictionary AuthenticationExtensionsClientOutputs { | ||
| // (same as *Inputs) | ||
| member appidExclude // No implementation as of 2025-05 | ||
| member hmacGetSecret // No implementation as of 2025-05 | ||
| member payment // Blink only as of 2025-06 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,22 +23,38 @@ | |
| signature: DeepPartial<Signature>[] | Record<number, DeepPartial<Signature>>; | ||
| } | ||
|
|
||
| function optionalMember<const T>(prop: string, type: T, value?: Value) { | ||
| function optionalMember<const T>( | ||
| prop: string, | ||
| type: T, | ||
| value?: Value | DeepPartial<WebIdl>, | ||
| ) { | ||
| if (value === undefined) { | ||
| return {}; | ||
| } | ||
| // Support deep property assignment, e.g. prop = "a.b.c" | ||
| const propPath = prop.split("."); | ||
| if (typeof value !== type) { | ||
| throw new Error(`Expected type ${value} for ${prop}`); | ||
| } | ||
| return { | ||
| [prop]: value as T extends "string" | ||
| ? string | ||
| : T extends "number" | ||
| ? number | ||
| : T extends "boolean" | ||
| ? boolean | ||
| : never, | ||
| }; | ||
| // If value is an object, ensure it is not empty (has at least one key) | ||
| if (type === "object" && typeof value === "object" && value !== null) { | ||
| if (Object.keys(value as object).length === 0) { | ||
| return {}; | ||
| } | ||
| } | ||
|
|
||
| // Build the nested object dynamically | ||
| let nested: any = value as T extends "string" | ||
| ? string | ||
| : T extends "number" | ||
| ? number | ||
| : T extends "boolean" | ||
| ? boolean | ||
| : never; | ||
| for (let i = propPath.length - 1; i >= 0; i--) { | ||
| nested = { [propPath[i]]: nested }; | ||
| } | ||
| return nested; | ||
| } | ||
|
|
||
| function string(arg: unknown): string { | ||
|
|
@@ -77,22 +93,21 @@ | |
| } | ||
|
|
||
| /** | ||
| * Converts patch files in KDL to match the [types](types.d.ts). | ||
| * Converts parsed KDL Document nodes to match the [types](types.d.ts). | ||
| */ | ||
| function parseKDL(kdlText: string): DeepPartial<WebIdl> { | ||
| const { output, errors } = parse(kdlText); | ||
| function convertKDLNodes(nodes: Node[]): DeepPartial<WebIdl> { | ||
| // Accept either Document or array of nodes | ||
| const actualNodes: Node[] = Array.isArray(nodes) | ||
| ? nodes | ||
| : nodes; | ||
|
||
|
|
||
| if (errors.length) { | ||
| throw new Error("KDL parse errors", { cause: errors }); | ||
| } | ||
|
|
||
| const nodes = output!; | ||
| const enums: Record<string, Enum> = {}; | ||
| const mixin: Record<string, DeepPartial<Interface>> = {}; | ||
| const interfaces: Record<string, DeepPartial<Interface>> = {}; | ||
| const dictionary: Record<string, DeepPartial<Dictionary>> = {}; | ||
|
|
||
| for (const node of nodes) { | ||
| for (const node of actualNodes) { | ||
| // Note: no "removals" handling here; caller is responsible for splitting | ||
| const name = string(node.values[0]); | ||
| switch (node.name) { | ||
| case "enum": | ||
|
|
@@ -113,10 +128,12 @@ | |
| } | ||
|
|
||
| return { | ||
| enums: { enum: enums }, | ||
| mixins: { mixin }, | ||
| interfaces: { interface: interfaces }, | ||
| dictionaries: { dictionary }, | ||
| ...optionalMember("enums.enum", "object", enums), | ||
|
||
| ...optionalMember("mixins.mixin", "object", mixin), | ||
|
||
| ...optionalMember("interfaces.interface", "object", interfaces), | ||
| dictionaries: { | ||
| dictionary | ||
| } | ||
| }; | ||
| } | ||
|
|
||
|
|
@@ -368,21 +385,92 @@ | |
| } | ||
|
|
||
| /** | ||
| * Read and parse a single KDL file. | ||
| * Read and parse a single KDL file into its KDL Document structure. | ||
| */ | ||
| export async function readPatch(fileUrl: URL): Promise<any> { | ||
| async function readPatchDocument(fileUrl: URL): Promise<Node[]> { | ||
Bashamega marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const text = await readFile(fileUrl, "utf8"); | ||
| return parseKDL(text); | ||
| const { output, errors } = parse(text); | ||
| if (errors.length) { | ||
| throw new Error(`KDL parse errors in ${fileUrl.toString()}`, { | ||
| cause: errors, | ||
| }); | ||
| } | ||
| return output!; | ||
| } | ||
| /** | ||
| * Recursively remove all 'name' fields from the object and its children, and | ||
| * replace any empty objects ({} or []) with null. | ||
| */ | ||
| function sanitizeRemovals(obj: unknown): unknown { | ||
|
||
| if (Array.isArray(obj)) { | ||
| const result = obj.map(sanitizeRemovals).filter((v) => v !== undefined); | ||
| return result.length === 0 ? null : result; | ||
| } | ||
| if (obj && typeof obj === "object") { | ||
| const newObj: { [key: string]: unknown } = {}; | ||
|
||
| for (const [key, value] of Object.entries(obj as Record<string, unknown>)) { | ||
Bashamega marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (key !== "name") { | ||
| const cleaned = sanitizeRemovals(value); | ||
| if (cleaned !== undefined) { | ||
| newObj[key] = cleaned; | ||
| } | ||
| } | ||
| } | ||
| // Replace empty objects with null | ||
| return Object.keys(newObj).length === 0 ? null : newObj; | ||
| } | ||
| return obj; | ||
| } | ||
|
|
||
| /** | ||
| * Read, parse, and merge all KDL files under the input folder. | ||
| * Splits the main patch content and the removals from each file for combined processing. | ||
| * | ||
| * Returns: | ||
| * { | ||
| * patches: merged patch contents (excluding removals), | ||
| * removalPatches: merged removals, with names stripped | ||
| * } | ||
| */ | ||
| export default async function readPatches(): Promise<any> { | ||
| export default async function readPatches(): Promise<{ | ||
| patches: any; | ||
| removalPatches: any; | ||
| }> { | ||
| const patchDirectory = new URL("../../inputfiles/patches/", import.meta.url); | ||
| const fileUrls = await getAllFileURLs(patchDirectory); | ||
|
|
||
| const parsedContents = await Promise.all(fileUrls.map(readPatch)); | ||
| // Stage 1: Parse all file KDLs into Documents | ||
| const documents = await Promise.all(fileUrls.map(readPatchDocument)); | ||
|
|
||
| // Stage 2: For each document, split main nodes and removals nodes | ||
| const patchNodeGroups: Node[][] = []; | ||
| const removalsNodeGroups: Node[][] = []; | ||
|
|
||
| for (const doc of documents) { | ||
| const mainNodes: Node[] = []; | ||
| let localRemovalsNodes: Node[] = []; | ||
| for (const node of doc) { | ||
| if (node.name === "removals") { | ||
| // Each removals node may itself contain multiple root nodes | ||
| localRemovalsNodes = localRemovalsNodes.concat(node.children); | ||
| } else { | ||
| mainNodes.push(node); | ||
| } | ||
| } | ||
| patchNodeGroups.push(mainNodes); | ||
| if (localRemovalsNodes.length > 0) { | ||
| removalsNodeGroups.push(localRemovalsNodes); | ||
| } | ||
| } | ||
|
|
||
| // Stage 3: Merge all main patches and removals separately using convertKDLNodes | ||
| const patchObjs = patchNodeGroups.map((nodes) => convertKDLNodes(nodes)); | ||
| const removalObjs = removalsNodeGroups.map((nodes) => convertKDLNodes(nodes)); | ||
|
|
||
| const patches = patchObjs.reduce((acc, cur) => merge(acc, cur), {}); | ||
| const removalPatches = sanitizeRemovals( | ||
| removalObjs.reduce((acc, cur) => merge(acc, cur), {}), | ||
| ); | ||
|
|
||
| return parsedContents.reduce((acc, current) => merge(acc, current), {}); | ||
| return { patches, removalPatches }; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(this should be restored)