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..97d9da5c3 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,8 @@ function handleMethod(child: Node): Partial { ...handleTyped(typeNode), }, ]; - return { name, signature }; + // Added special handling for "callable" methods to omit the name for EventListener + return { name: name === "callable" ? undefined : name, signature }; } /**