From 7056fead12e40647f1b99942eeadc9c64a2a5d5b Mon Sep 17 00:00:00 2001 From: Bashamega Date: Thu, 25 Sep 2025 13:07:48 +0300 Subject: [PATCH 1/3] Migrate interface that we are forced to declare --- inputfiles/addedTypes.jsonc | 128 -------------------------- inputfiles/patches/forced-declare.kdl | 48 ++++++++++ src/build/patches.ts | 38 ++++++-- 3 files changed, 80 insertions(+), 134 deletions(-) create mode 100644 inputfiles/patches/forced-declare.kdl diff --git a/inputfiles/addedTypes.jsonc b/inputfiles/addedTypes.jsonc index ad7a7458c..97b91891a 100644 --- a/inputfiles/addedTypes.jsonc +++ b/inputfiles/addedTypes.jsonc @@ -1,40 +1,6 @@ { "interfaces": { "interface": { - // ImportMeta is not a true DOM interface, but we are forced to declare it as one in order to emit method definitions. - // We cannot define methods as dictionary properties with function types, - // as this would cause conflicts with ImportMeta method overrides in places like @types/node. - "ImportMeta": { - "name": "ImportMeta", - "exposed": "Window Worker", - "noInterfaceObject": true, - "properties": { - "property": { - "url": { - "name": "url", - "type": "DOMString" - } - } - }, - "methods": { - "method": { - "resolve": { - "name": "resolve", - "signature": [ - { - "param": [ - { - "name": "specifier", - "type": "DOMString" - } - ], - "type": "DOMString" - } - ] - } - } - } - }, "AudioWorkletProcessorImpl": { "name": "AudioWorkletProcessorImpl", "extends": "AudioWorkletProcessor", @@ -332,48 +298,6 @@ } } }, - // This is used in many DT libraries, via ckeditor - "ClientRect": { - "name": "ClientRect", - "exposed": "Window", - "deprecated": true, - "extends": "DOMRect", - "noInterfaceObject": true - }, - /* - Keeping EventListener and EventListenerObject isn't the most elegant way to handle - the event listeners, but we need to keep the EventListener as an extendable interface - for libraries like angular. - */ - "EventListener": { - "name": "EventListener", - "noInterfaceObject": true, - "methods": { - "method": { - // This is a hack to add a call signature, but I think it's reasonable - // as it means we don't have to add a call signatures section to the - // emitter for this one case. - "callable": { - "overrideSignatures": [ - "(evt: Event): void" - ] - } - } - } - }, - "EventListenerObject": { - "name": "EventListenerObject", - "noInterfaceObject": true, - "methods": { - "method": { - "handleEvent": { - "overrideSignatures": [ - "handleEvent(object: Event): void" - ] - } - } - } - }, "Document": { "methods": { "method": { @@ -415,32 +339,6 @@ ] } }, - // This is used in the React d.ts files, and not including - // it would force an update for anyone using React. - "StyleMedia": { - "name": "StyleMedia", - "exposed": "Window", - "noInterfaceObject": true, - "deprecated": true, - "properties": { - "property": { - "type": { - "name": "type", - "type": "DOMString" - } - } - }, - "methods": { - "method": { - "matchMedium": { - "name": "matchMedium", - "overrideSignatures": [ - "matchMedium(mediaquery: string): boolean" - ] - } - } - } - }, "Navigator": { "name": "Navigator", "properties": { @@ -610,32 +508,6 @@ "OVR_multiview2": { "overrideExposed": "Window Worker" }, - // The spec removed `timestamp` but browsers still have it. - // https://github.com/w3c/webrtc-encoded-transform/pull/204 - "RTCEncodedAudioFrame": { - "properties": { - "property": { - "timestamp": { - "mdnUrl": "https://developer.mozilla.org/docs/Web/API/RTCEncodedAudioFrame/timestamp", - "name": "timestamp", - "type": "long long", - "readonly": true - } - } - } - }, - "RTCEncodedVideoFrame": { - "properties": { - "property": { - "timestamp": { - "mdnUrl": "https://developer.mozilla.org/docs/Web/API/RTCEncodedVideoFrame/timestamp", - "name": "timestamp", - "type": "long long", - "readonly": true - } - } - } - }, "RTCDTMFSender": { "events": { "event": [ diff --git a/inputfiles/patches/forced-declare.kdl b/inputfiles/patches/forced-declare.kdl new file mode 100644 index 000000000..7ae8ab21e --- /dev/null +++ b/inputfiles/patches/forced-declare.kdl @@ -0,0 +1,48 @@ +// This is used in many DT libraries, via ckeditor +interface ClientRect exposed=Window deprecated=#true extends=DOMRect noInterfaceObject=#true + +// Keeping EventListener and EventListenerObject isn't the most elegant way to handle the event listeners, but we need to keep the EventListener as an extendable interface for libraries like angular. +interface EventListener noInterfaceObject=#true { + method callable { + type void + param evt type=Event + } +} + +interface EventListenerObject noInterfaceObject=#true { + method handleEvent { + type void + param object type=Event + } +} + +// ImportMeta is not a true DOM interface, but we are forced to declare it as one in order to emit method definitions. +// We cannot define methods as dictionary properties with function types, as this would cause conflicts with ImportMeta method overrides in places like @types/node. +interface ImportMeta exposed="Window Worker" noInterfaceObject=#true { + property url type=DOMString + method resolve { + type DOMString + param specifier type=DOMString + } +} + +// The spec removed `timestamp` but browsers still have it. +// https://github.com/w3c/webrtc-encoded-transform/pull/204 +interface RTCEncodedAudioFrame { + property timestamp type="long long" readonly=#true mdnUrl="https://developer.mozilla.org/docs/Web/API/RTCEncodedAudioFrame/timestamp" +} + +interface RTCEncodedVideoFrame { + property timestamp type="long long" readonly=#true mdnUrl="https://developer.mozilla.org/docs/Web/API/RTCEncodedVideoFrame/timestamp" +} + + +// This is used in the React.d.ts files, and not includin +// it would force an update for anyone using React. +interface StyleMedia exposed=Window noInterfaceObject=#true deprecated=#true { + property type type=DOMString + method matchMedium { + type boolean + param mediaquery type=DOMString + } +} diff --git a/src/build/patches.ts b/src/build/patches.ts index f7e932847..9de6e5966 100644 --- a/src/build/patches.ts +++ b/src/build/patches.ts @@ -15,13 +15,25 @@ type DeepPartial = T extends object ? { [K in keyof T]?: DeepPartial } : T; -function optionalMember(prop: string, type: T, value?: Value) { +function optionalMember( + prop: string, + type: T, + value?: unknown, +) { if (value === undefined) { return {}; } - if (typeof value !== type) { - throw new Error(`Expected type ${value} for ${prop}`); + + // Normalize to array + const types = Array.isArray(type) ? type : [type]; + + // Check if value matches one of the types + if (!types.some((t) => typeof value === t)) { + throw new Error( + `Expected ${types.join(" or ")} for ${prop}, got ${typeof value}`, + ); } + return { [prop]: value as T extends "string" ? string @@ -29,7 +41,15 @@ function optionalMember(prop: string, type: T, value?: Value) { ? number : T extends "boolean" ? boolean - : never, + : T extends (infer U)[] + ? U extends "string" + ? string + : U extends "number" + ? number + : U extends "boolean" + ? boolean + : never + : never, }; } @@ -169,7 +189,11 @@ function handleMixinandInterfaces( const interfaceObject = type === "interface" && { ...optionalMember("exposed", "string", node.properties?.exposed), - ...optionalMember("deprecated", "string", node.properties?.deprecated), + ...optionalMember( + "deprecated", + ["string", "boolean"], + node.properties?.deprecated, + ), ...optionalMember( "noInterfaceObject", "boolean", @@ -210,6 +234,8 @@ function handleProperty(child: Node): Partial { ...optionalMember("optional", "boolean", child.properties?.optional), ...optionalMember("overrideType", "string", child.properties?.overrideType), ...optionalMember("type", "string", child.properties?.type), + ...optionalMember("readonly", "boolean", child.properties?.readonly), + ...optionalMember("mdnUrl", "string", child.properties?.mdnUrl), }; } @@ -254,7 +280,7 @@ function handleMethod(child: Node): Partial { ...handleTyped(typeNode), }, ]; - return { name, signature }; + return { name: name === "callable" ? undefined : name, signature }; } /** From 832b40935220f9184ae021a27b64390be8335fcd Mon Sep 17 00:00:00 2001 From: Bashamega Date: Thu, 25 Sep 2025 13:10:18 +0300 Subject: [PATCH 2/3] Add documentation --- src/build/patches.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/build/patches.ts b/src/build/patches.ts index 9de6e5966..150b8a296 100644 --- a/src/build/patches.ts +++ b/src/build/patches.ts @@ -280,6 +280,7 @@ function handleMethod(child: Node): Partial { ...handleTyped(typeNode), }, ]; + // I have added special handling for "callable" methods to omit the name for EventListener return { name: name === "callable" ? undefined : name, signature }; } From 0ef4471bf6106832ec31cdb67d5e642b12a3d83f Mon Sep 17 00:00:00 2001 From: Adam Naji <110662505+Bashamega@users.noreply.github.com> Date: Fri, 26 Sep 2025 08:35:19 +0300 Subject: [PATCH 3/3] Update patches.ts --- src/build/patches.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build/patches.ts b/src/build/patches.ts index 150b8a296..97d9da5c3 100644 --- a/src/build/patches.ts +++ b/src/build/patches.ts @@ -280,7 +280,7 @@ function handleMethod(child: Node): Partial { ...handleTyped(typeNode), }, ]; - // I have added special handling for "callable" methods to omit the name for EventListener + // Added special handling for "callable" methods to omit the name for EventListener return { name: name === "callable" ? undefined : name, signature }; }