import * as Browser from "./types.js" ; import { mapToArray , distinct , mapValues , toNameMap , mapDefined , arrayToMap , integerTypes , baseTypeConversionMap , assertUnique , } from "./helpers.js" ; import { collectLegacyNamespaceTypes } from "./legacy-namespace.js" ; enum EmitScope { StaticOnly , InstanceOnly , All } const tsKeywords = new Set ( [ "default" , "delete" , "continue" ] ) ; const extendConflictsBaseTypes = { HTMLCollection : { extendType : [ "HTMLFormControlsCollection" ] , memberNames : new Set ( [ "namedItem" ] ) , } , } ; const namespacesAsInterfaces = [ "console" ] ; function matchScope ( scope , x ) { return ( scope === EmitScope . All || ( scope === EmitScope . StaticOnly ) === ! ! x . static ) ; } function adjustParamName ( name ) { return tsKeywords . has ( name ) ? `_${ name }` : name ; } function getElements < K extends string , T > ( a , k ) { return a ? mapToArray ( a [ k ] ) : [ ] ; } function createTextWriter ( newLine ) { let output ; let indent ; let lineStart ; /** print declarations conflicting with base interface to a side list to write them under a different name later */ let stack = [ ] ; function getIndentString ( level ) { return "    " . repeat ( level ) ; } function write ( s ) { if ( s && lineStart ) { output += getIndentString ( indent ) ; lineStart = false ; } output += s ; } function reset ( ) { output = "" ; indent = 0 ; lineStart = true ; stack = [ ] ; } function endLine ( ) { output += newLine ; lineStart = true ; } reset ( ) ; return { reset , increaseIndent ( ) { indent ++ ; } , decreaseIndent ( ) { indent -- ; } , endLine , print : write , printLine ( c ) { write ( c ) ; endLine ( ) ; } , clearStack ( ) { stack = [ ] ; } , stackIsEmpty ( ) { return stack . length === 0 ; } , printLineToStack ( content ) { stack . push ( { content , indent } ) ; } , printStackContent ( ) { stack . forEach ( ( e ) => { const oldIndent = indent ; indent = e . indent ; this . printLine ( e . content ) ; indent = oldIndent ; } ) ; } , getResult ( ) { return output ; } , } ; } function isEventHandler ( p ) { return typeof p . eventHandler === "string" ; } export function emitWebIdl ( webidl , global , iterator ) { const printer = createTextWriter ( "\n" ) ; const polluter = getElements ( webidl . interfaces , "interface" ) . find ( ( i ) => ! ! i . global ) ; const allNonCallbackInterfaces = getElements ( webidl . interfaces , "interface" ) . concat ( getElements ( webidl . mixins , "mixin" ) ) ; const allInterfaces = getElements ( webidl . interfaces , "interface" ) . concat ( getElements ( webidl . callbackInterfaces , "interface" ) , getElements ( webidl . mixins , "mixin" ) ) ; const allInterfacesMap = toNameMap ( allInterfaces ) ; const allLegacyWindowAliases = allInterfaces . flatMap ( ( i ) => i . legacyWindowAlias ) ; const allDictionariesMap = webidl . dictionaries ?. dictionary ?? { } ; const allEnumsMap = webidl . enums ? webidl . enums . enum : { } ; const allCallbackFunctionsMap = webidl . callbackFunctions ?. callbackFunction ?? { } ; const allTypedefsMap = toNameMap ( webidl . typedefs ?. typedef ?? [ ] ) ; const tagNameToEleName = getTagNameToElementNameMap ( ) ; const iNameToIDependList = arrayToMap ( allNonCallbackInterfaces , ( i ) => i . name , ( i ) => getExtendList ( i . name ) . concat ( getImplementList ( i . name ) ) ) ; const distinctETypeList = distinct ( allNonCallbackInterfaces . flatMap ( ( i ) => ( i . events ? i . events . event . map ( ( e ) => e . type ) : [ ] ) ) . concat ( allNonCallbackInterfaces . filter ( ( i ) => i . extends ?. endsWith ( "Event" ) && i . name . endsWith ( "Event" ) ) . map ( ( i ) => i . name ) ) ) . sort ( ) ; const iNameToEhList = arrayToMap ( allInterfaces , ( i ) => i . name , ( i ) => { const fromProperties = mapDefined ( mapToArray ( i . properties ?. property ) , ( p ) => p . eventHandler ) ; const fromEvents = ( i . events ?. event ?? [ ] ) . map ( ( e ) => e . name ) ; return distinct ( [ ... fromProperties , ... fromEvents ] ) . sort ( ) ; } ) ; const iNameToConstList = arrayToMap ( allInterfaces , ( i ) => i . name , ( i ) => ( ! i . constants ? [ ] : mapToArray ( i . constants . constant ) ) ) ; const iNameToEhParents = arrayToMap ( allInterfaces , ( i ) => i . name , getParentsWithEventHandler ) ; const iNameToConstParents = arrayToMap ( allInterfaces , ( i ) => i . name , getParentsWithConstant ) ; return iterator ? emitES6DomIterators ( ) : emit ( ) ; function getTagNameToElementNameMap ( ) { const htmlResult = { } ; const htmlDeprecatedResult = { } ; const svgResult = { } ; for ( const i of allNonCallbackInterfaces ) { if ( i . element ) { for ( const e of i . element ) { if ( e . namespace === "SVG" ) { svgResult [ e . name ] = i . name ; } else if ( e . deprecated ) { htmlDeprecatedResult [ e . name ] = i . name ; } else { htmlResult [ e . name ] = i . name ; } } } } return { htmlResult , htmlDeprecatedResult , svgResult } ; } function getExtendList ( iName ) { const i = allInterfacesMap [ iName ] ; if ( ! i || ! i . extends || i . extends === "Object" ) return [ ] ; else return getExtendList ( i . extends ) . concat ( i . extends ) ; } function getImplementList ( iName ) { const i = allInterfacesMap [ iName ] ; return i ?. implements ?. sort ( ) || [ ] ; } function getParentsWithEventHandler ( i ) { function getParentEventHandler ( i ) { const hasEventListener = iNameToEhList [ i . name ] ?. length ; if ( hasEventListener ) { return [ i ] ; } const ehParents = getParentsWithEventHandler ( i ) ; if ( ehParents . length > 1 ) { return [ i ] ; } return ehParents ; } if ( ! i . extends ) { return [ ] ; } const iExtends = i . extends ?. replace ( /<.*>$/ , "" ) ; const parentWithEventHandler = ( allInterfacesMap [ iExtends ] && getParentEventHandler ( allInterfacesMap [ iExtends ] ) ) || [ ] ; const mixinsWithEventHandler = getImplementList ( i . name ) . flatMap ( ( i ) => getParentEventHandler ( allInterfacesMap [ i ] ) ) ; return distinct ( parentWithEventHandler . concat ( mixinsWithEventHandler ) ) ; } function getParentsWithConstant ( i ) { function getParentConstant ( i ) { const hasConst = iNameToConstList [ i . name ] ?. length ; return ( hasConst ? [ i ] : [ ] ) . concat ( getParentsWithConstant ( i ) ) ; } const mixinsWithConstant = getImplementList ( i . name ) . flatMap ( ( i ) => getParentConstant ( allInterfacesMap [ i ] ) ) ; return distinct ( mixinsWithConstant ) ; } function getEventTypeInInterface ( eName , i ) { function getGenericEventType ( baseName ) { if ( baseName === "ProgressEvent" && ! i . mixin ) { return `${ baseName }<${ i . name }>` ; } return baseName ; } if ( i . events ) { const event = i . events . event . find ( ( e ) => e . name === eName ) ; if ( event ?. type ) { return getGenericEventType ( event . type ) ; } } if ( i . attributelessEvents ) { const event = i . attributelessEvents . event . find ( ( e ) => e . name === eName ) ; if ( event ?. type ) { return getGenericEventType ( event . type ) ; } } return "Event" ; } function convertDomTypeToTsType ( obj ) { if ( obj . overrideType ) { return obj . nullable ? makeNullable ( obj . overrideType ) : obj . overrideType ; } if ( ! obj . type ) throw new Error ( "Missing 'type' field in " + JSON . stringify ( obj ) ) ; const type = convertDomTypeToTsTypeWorker ( obj ) ; return obj . nullable ? makeNullable ( type ) : type ; } function convertDomTypeToTsReturnType ( obj ) { const type = convertDomTypeToTsType ( obj ) ; if ( type === "undefined" ) { return "void" ; } if ( type === "Promise<undefined>" ) { return "Promise<void>" ; } if ( type === "undefined | PromiseLike<undefined>" ) { return "void | PromiseLike<void>" ; } return type ; } function convertDomTypeToTsTypeWorker ( obj ) { function convertBaseType ( ) { if ( ! obj . additionalTypes && typeof obj . type === "string" ) { return convertDomTypeToTsTypeSimple ( obj . type ) ; } else { const types = typeof obj . type === "string" ? [ { ... obj , additionalTypes : undefined } ] : obj . type ; types . push ( ... ( obj . additionalTypes ?? [ ] ) . map ( ( t ) => ( { type : t } ) ) ) ; const converted = types . map ( convertDomTypeToTsTypeWorker ) ; const isAny = converted . some ( ( t ) => t === "any" ) ; return isAny ? "any" : converted . join ( " | " ) ; } } const type = convertBaseType ( ) ; const subtypeString = arrayify ( obj . subtype ) . map ( convertDomTypeToTsType ) . join ( ", " ) ; return type === "Array" && subtypeString ? makeArrayType ( subtypeString , obj ) : `${ type }${ subtypeString ? `<${ subtypeString }>` : "" }` ; } function makeArrayType ( elementType , obj ) { if ( obj . subtype && ! Array . isArray ( obj . subtype ) && obj . subtype . type === "float" ) { return "number[] | Float32Array" ; } return elementType . includes ( "|" ) ? `(${ elementType })[]` : `${ elementType }[]` ; } function arrayify ( obj ) { if ( ! obj ) { return [ ] ; } if ( ! Array . isArray ( obj ) ) { return [ obj ] ; } return obj ; } function convertDomTypeToTsTypeSimple ( objDomType ) { if ( objDomType === "sequence" && iterator ) { return "Iterable" ; } if ( baseTypeConversionMap . has ( objDomType ) ) { return baseTypeConversionMap . get ( objDomType ) ! ; } if ( allInterfacesMap [ objDomType ] || allLegacyWindowAliases . includes ( objDomType ) || allCallbackFunctionsMap [ objDomType ] || allDictionariesMap [ objDomType ] || allEnumsMap [ objDomType ] ) return objDomType ; if ( allTypedefsMap [ objDomType ] ) return objDomType ; throw new Error ( "Unknown DOM type: " + objDomType ) ; } function makeNullable ( originalType ) { switch ( originalType ) { case "any" : return "any" ; case "void" : return "void" ; } if ( originalType . includes ( "=>" ) || originalType . includes ( "&" ) ) { return "(" + originalType + ") | null" ; } return originalType + " | null" ; } function nameWithForwardedTypes ( i ) { const typeParameters = i . typeParameters ; if ( ! typeParameters ) return i . name ; if ( ! typeParameters . length ) return i . name ; return `${ i . name }<${ typeParameters . map ( ( t ) => t . name ) }>` ; } function emitConstant ( c ) { emitComments ( c , printer . printLine ) ; printer . printLine ( `readonly ${ c . name }: ${ convertDomTypeToTsType ( c ) };` ) ; } function emitConstants ( i ) { if ( i . constants ) { mapToArray ( i . constants . constant ) . sort ( compareName ) . forEach ( emitConstant ) ; } } function matchParamMethodSignature ( m , expectedMName , expectedMType , expectedParamType ) { if ( ! Array . isArray ( expectedParamType ) ) { expectedParamType = [ expectedParamType ] ; } return ( expectedMName === m . name && m . signature ?. length === 1 && convertDomTypeToTsType ( m . signature [ 0 ] ) === expectedMType && m . signature [ 0 ] . param ?. length === expectedParamType . length && expectedParamType . every ( ( pt , idx ) => convertDomTypeToTsType ( m . signature [ 0 ] . param ! [ idx ] ) === pt ) ) ; } function getNameWithTypeParameter ( typeParameters , name ) { function typeParameterWithDefault ( type ) { return ( type . name + ( type . extends ? ` extends ${ type . extends }` : "" ) + ( type . default ? ` = ${ type . default }` : "" ) ) ; } if ( ! typeParameters ) { return name ; } return `${ name }<${ typeParameters . map ( typeParameterWithDefault ) . join ( ", " ) }>` ; } function emitCreateElementOverloads ( m ) { if ( matchParamMethodSignature ( m , "createElement" , "Element" , [ "string" , "string | ElementCreationOptions" , ] ) ) { printer . printLine ( "createElement<K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K];" ) ; printer . printLine ( "/** @deprecated */" ) ; printer . printLine ( "createElement<K extends keyof HTMLElementDeprecatedTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K];" ) ; printer . printLine ( "createElement(tagName: string, options?: ElementCreationOptions): HTMLElement;" ) ; } } function emitGetElementsByTagNameOverloads ( m ) { if ( matchParamMethodSignature ( m , "getElementsByTagName" , "HTMLCollection" , "string" ) ) { printer . printLine ( `getElementsByTagName<K extends keyof HTMLElementTagNameMap>(${ m . signature [ 0 ] . param ! [ 0 ] . name }: K): HTMLCollectionOf<HTMLElementTagNameMap[K]>;` ) ; printer . printLine ( `getElementsByTagName<K extends keyof SVGElementTagNameMap>(${ m . signature [ 0 ] . param ! [ 0 ] . name }: K): HTMLCollectionOf<SVGElementTagNameMap[K]>;` ) ; printer . printLine ( `getElementsByTagName(${ m . signature [ 0 ] . param ! [ 0 ] . name }: string): HTMLCollectionOf<Element>;` ) ; } } function emitQuerySelectorOverloads ( m ) { if ( matchParamMethodSignature ( m , "querySelector" , "Element | null" , "string" ) ) { printer . printLine ( "querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;" ) ; printer . printLine ( "querySelector<K extends keyof SVGElementTagNameMap>(selectors: K): SVGElementTagNameMap[K] | null;" ) ; printer . printLine ( "querySelector<E extends Element = Element>(selectors: string): E | null;" ) ; } } function emitQuerySelectorAllOverloads ( m ) { if ( matchParamMethodSignature ( m , "querySelectorAll" , "NodeList" , "string" ) ) { printer . printLine ( "querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;" ) ; printer . printLine ( "querySelectorAll<K extends keyof SVGElementTagNameMap>(selectors: K): NodeListOf<SVGElementTagNameMap[K]>;" ) ; printer . printLine ( "querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;" ) ; } } function emitHTMLElementTagNameMap ( ) { printer . printLine ( "interface HTMLElementTagNameMap {" ) ; printer . increaseIndent ( ) ; for ( const [ e , value ] of Object . entries ( tagNameToEleName . htmlResult ) . sort ( ) ) { printer . printLine ( `"${ e . toLowerCase ( ) }": ${ value };` ) ; } printer . decreaseIndent ( ) ; printer . printLine ( "}" ) ; printer . printLine ( "" ) ; } function emitHTMLElementDeprecatedTagNameMap ( ) { printer . printLine ( "interface HTMLElementDeprecatedTagNameMap {" ) ; printer . increaseIndent ( ) ; for ( const [ e , value ] of Object . entries ( tagNameToEleName . htmlDeprecatedResult ) . sort ( ) ) { printer . printLine ( `"${ e . toLowerCase ( ) }": ${ value };` ) ; } printer . decreaseIndent ( ) ; printer . printLine ( "}" ) ; printer . printLine ( "" ) ; } function emitSVGElementTagNameMap ( ) { printer . printLine ( "interface SVGElementTagNameMap {" ) ; printer . increaseIndent ( ) ; for ( const [ e , value ] of Object . entries ( tagNameToEleName . svgResult ) . sort ( ) ) { printer . printLine ( `"${ e }": ${ value };` ) ; } printer . decreaseIndent ( ) ; printer . printLine ( "}" ) ; printer . printLine ( "" ) ; } function emitElementTagNameMap ( ) { printer . printLine ( "/** @deprecated Directly use HTMLElementTagNameMap or SVGElementTagNameMap as appropriate, instead. */" ) ; printer . printLine ( "type ElementTagNameMap = HTMLElementTagNameMap & Pick<SVGElementTagNameMap, Exclude<keyof SVGElementTagNameMap, keyof HTMLElementTagNameMap>>;" ) ; printer . printLine ( "" ) ; } function emitCreateEventOverloads ( m ) { if ( matchParamMethodSignature ( m , "createEvent" , "Event" , "string" ) ) { const hasPlurals = [ "Event" , "MutationEvent" , "MouseEvent" , "SVGZoomEvent" , "UIEvent" , ] ; for ( const x of distinctETypeList ) { printer . printLine ( `createEvent(eventInterface: "${ x }"): ${ x };` ) ; if ( hasPlurals . includes ( x ) ) { printer . printLine ( `createEvent(eventInterface: "${ x }s"): ${ x };` ) ; } } printer . printLine ( "createEvent(eventInterface: string): Event;" ) ; } } function resolvePromise < T extends Browser . Typed > ( t ) { const typedef = typeof t . type === "string" ? allTypedefsMap [ t . type ] : undefined ; const typeOwner = typedef ?? t ; if ( typeOwner . type !== "Promise" ) { if ( t . subtype ) { return { ... t , subtype : Array . isArray ( t . subtype ) ? t . subtype . map ( resolvePromise ) : resolvePromise ( t . subtype ) , } ; } return t ; } const type = [ typeOwner . subtype ! ] . flat ( ) ; type . push ( { ... typeOwner , type : "PromiseLike" } ) ; return { ... t , subtype : undefined , type } ; } function paramsToString ( ps ) { function paramToString ( p ) { p = resolvePromise ( p ) ; if ( p . name . toLowerCase ( ) . includes ( "url" ) && p . type === "USVString" ) { p = { ... p , additionalTypes : [ ... ( p . additionalTypes ?? [ ] ) ] } ; p . additionalTypes ! . push ( "URL" ) ; } const pType = convertDomTypeToTsType ( p ) ; const isOptional = ! p . variadic && p . optional ; const variadicParams = p . variadic && pType . indexOf ( "|" ) !== - 1 ; return ( ( p . variadic ? "..." : "" ) + adjustParamName ( p . name ) + ( isOptional ? "?: " : ": " ) + ( variadicParams ? "(" : "" ) + pType + ( variadicParams ? ")" : "" ) + ( p . variadic ? "[]" : "" ) ) ; } return ps . map ( paramToString ) . join ( ", " ) ; } function emitCallBackInterface ( i ) { const methods = mapToArray ( i . methods . method ) ; const m = methods [ 0 ] ; const overload = m . signature [ 0 ] ; const paramsString = overload . param ? paramsToString ( overload . param ) : "" ; const returnType = overload . type ? convertDomTypeToTsReturnType ( overload ) : "void" ; printer . printLine ( `type ${ i . name } = ((${ paramsString }) => ${ returnType }) | { ${ m . name }(${ paramsString }): ${ returnType }; };` ) ; printer . printLine ( "" ) ; if ( ! mapToArray ( i . constants ?. constant ?? { } ) . length ) { return ; } printer . printLine ( `declare var ${ i . name }: {` ) ; printer . increaseIndent ( ) ; emitConstants ( i ) ; printer . decreaseIndent ( ) ; printer . printLine ( "};" ) ; printer . printLine ( "" ) ; } function emitCallBackFunction ( cb ) { printer . printLine ( `interface ${ getNameWithTypeParameter ( cb . typeParameters , cb . name ) } {` ) ; printer . increaseIndent ( ) ; emitSignatures ( cb , "" , "" , printer . printLine , true ) ; printer . decreaseIndent ( ) ; printer . printLine ( "}" ) ; printer . printLine ( "" ) ; } function emitCallBackFunctions ( ) { getElements ( webidl . callbackFunctions , "callbackFunction" ) . sort ( compareName ) . forEach ( emitCallBackFunction ) ; } function emitEnum ( e ) { const values = e . value . slice ( ) . sort ( ) ; printer . printLine ( `type ${ e . name } = ${ values . map ( ( v ) => `"${ v }"` ) . join ( " | " ) };` ) ; } function emitEnums ( ) { getElements ( webidl . enums , "enum" ) . sort ( compareName ) . filter ( ( i ) => ! i . legacyNamespace ) . forEach ( emitEnum ) ; } function emitEventHandlerThis ( prefix , i ) { if ( prefix === "" ) { return `this: ${ nameWithForwardedTypes ( i ) }, ` ; } else { return polluter ? `this: ${ polluter . name }, ` : "" ; } } function isCovariantEventHandler ( i , p ) { return ( isEventHandler ( p ) && iNameToEhParents [ i . name ] . some ( ( parent ) => parent . properties ?. property . hasOwnProperty ( p . name ) ) ) ; } function emitProperty ( prefix , i , emitScope , p ) { emitComments ( p , printer . printLine ) ; if ( p . name === "name" && i . name === "Window" && emitScope === EmitScope . All ) { printer . printLine ( "/** @deprecated */" ) ; printer . printLine ( "declare const name: void;" ) ; } else { let pType ; if ( ! p . overrideType && isEventHandler ( p ) ) { const eType = p . eventHandler ? getEventTypeInInterface ( p . eventHandler ! , i ) : "Event" ; pType = `(${ emitEventHandlerThis ( prefix , i ) }ev: ${ eType }) => any` ; if ( typeof p . type === "string" && ! p . type . endsWith ( "NonNull" ) ) { pType = `(${ pType }) | null` ; } } else { pType = convertDomTypeToTsType ( p ) ; } if ( p . optional ) { pType += " | undefined" ; } const optionalModifier = ! p . optional || prefix ? "" : "?" ; if ( ! prefix && ! p . readonly && p . putForwards ) { printer . printLine ( `get ${ p . name }${ optionalModifier }(): ${ pType };` ) ; const forwardingProperty = allInterfacesMap [ pType ] . properties ?. property [ p . putForwards ] ; if ( ! forwardingProperty ) { throw new Error ( "Couldn't find [PutForwards]" ) ; } const setterType = `${ convertDomTypeToTsType ( forwardingProperty ) } | ${ pType }` ; printer . printLine ( `set ${ p . name }${ optionalModifier }(${ p . putForwards }: ${ setterType });` ) ; } else { const readOnlyModifier = p . readonly && prefix === "" ? "readonly " : "" ; printer . printLine ( `${ prefix }${ readOnlyModifier }${ p . name }${ optionalModifier }: ${ pType };` ) ; } } if ( p . stringifier ) { printer . printLine ( "toString(): string;" ) ; } } function emitComments ( entity , print ) { const comments = entity . comment ?. split ( "\n" ) ?? [ ] ; const deprecated = typeof entity . deprecated === "string" ? `@deprecated ${ entity . deprecated }` : entity . deprecated ? "@deprecated" : null ; if ( deprecated ) { comments . push ( deprecated ) ; } if ( entity . secureContext ) { comments . push ( "Available only in secure contexts." ) ; } if ( comments . length > 1 ) { print ( "/**" ) ; comments . forEach ( ( l ) => print ( ` * ${ l }` . trimEnd ( ) ) ) ; print ( " */" ) ; } else if ( comments . length == 1 ) { print ( `/** ${ comments [ 0 ] } */` ) ; } } function emitProperties ( prefix , emitScope , i ) { if ( i . properties ) { mapToArray ( i . properties . property ) . filter ( ( m ) => matchScope ( emitScope , m ) ) . filter ( ( p ) => ! isCovariantEventHandler ( i , p ) ) . sort ( compareName ) . forEach ( ( p ) => emitProperty ( prefix , i , emitScope , p ) ) ; } } function emitMethod ( prefix , m , conflictedMembers ) { function printLine ( content ) { if ( m . name && conflictedMembers . has ( m . name ) ) { printer . printLineToStack ( content ) ; } else { printer . printLine ( content ) ; } } emitComments ( m , printLine ) ; switch ( m . name ) { case "createElement" : return emitCreateElementOverloads ( m ) ; case "createEvent" : return emitCreateEventOverloads ( m ) ; case "getElementsByTagName" : return emitGetElementsByTagNameOverloads ( m ) ; case "querySelector" : return emitQuerySelectorOverloads ( m ) ; case "querySelectorAll" : return emitQuerySelectorAllOverloads ( m ) ; } if ( m . name !== "toString" ) { emitSignatures ( m , prefix , m . name , printLine ) ; if ( m . stringifier ) { printLine ( "toString(): string;" ) ; } } } function emitSignature ( s , prefix , name , printLine , shouldResolvePromise ? ) { const paramsString = s . param ? paramsToString ( s . param ) : "" ; const resolved = shouldResolvePromise ? resolvePromise ( s ) : s ; const returnType = convertDomTypeToTsReturnType ( resolved ) ; emitComments ( s , printLine ) ; printLine ( `${ prefix || "" }${ getNameWithTypeParameter ( s . typeParameters , name || "" ) }(${ paramsString }): ${ returnType };` ) ; } function emitSignatures ( method , prefix , name , printLine , shouldResolvePromise ? ) { if ( method . overrideSignatures ) { method . overrideSignatures ! . forEach ( ( s ) => printLine ( `${ prefix }${ s };` ) ) ; } else if ( method . signature ) { method . additionalSignatures ?. forEach ( ( s ) => printLine ( `${ prefix }${ s };` ) ) ; method . signature . forEach ( ( sig ) => emitSignature ( sig , prefix , name , printLine , shouldResolvePromise ) ) ; } } function emitMethods ( prefix , emitScope , i , conflictedMembers ) { if ( i . methods ) { mapToArray ( i . methods . method ) . filter ( ( m ) => matchScope ( emitScope , m ) && ! ( prefix !== "" && ( m . name === "addEventListener" || m . name === "removeEventListener" ) ) ) . filter ( ( m ) => { switch ( i . iterator ?. kind ) { case "maplike" : return ! [ "set" , "clear" , "delete" ] . includes ( m . name ) ; case "setlike" : return ! [ "add" , "clear" , "delete" ] . includes ( m . name ) ; default : return true ; } } ) . sort ( compareName ) . forEach ( ( m ) => emitMethod ( prefix , m , conflictedMembers ) ) ; } if ( i . anonymousMethods ) { const stringifier = i . anonymousMethods . method . find ( ( m ) => m . stringifier ) ; if ( stringifier ) { printer . printLine ( "toString(): string;" ) ; } } if ( i . name === "Window" && prefix === "declare function " ) { printer . printLine ( "declare function toString(): string;" ) ; } } function emitIteratorForEach ( i ) { if ( ! i . iterator ) { return ; } const subtype = i . iterator . type . map ( convertDomTypeToTsType ) ; const value = subtype [ subtype . length - 1 ] ; const key = subtype . length > 1 ? subtype [ 0 ] : i . iterator . kind === "iterable" ? "number" : value ; const name = i . typeParameters ? `${ i . name }<${ i . typeParameters ! . map ( ( p ) => p . name ) . join ( ", " ) }>` : i . name ; printer . printLine ( `forEach(callbackfn: (value: ${ value }, key: ${ key }, parent: ${ name }) => void, thisArg?: any): void;` ) ; } function emitMembers ( prefix , emitScope , i ) { const conflictedMembers = extendConflictsBaseTypes [ i . name ] ? extendConflictsBaseTypes [ i . name ] . memberNames : new Set < string > ( ) ; emitProperties ( prefix , emitScope , i ) ; const methodPrefix = prefix . startsWith ( "declare var" ) ? "declare function " : "" ; emitMethods ( methodPrefix , emitScope , i , conflictedMembers ) ; if ( emitScope === EmitScope . InstanceOnly ) { emitIteratorForEach ( i ) ; } } function emitAllMembers ( i ) { emitMembers ( "declare var " , EmitScope . All , i ) ; for ( const relatedIName of iNameToIDependList [ i . name ] ) { const i = allInterfacesMap [ relatedIName ] ; if ( i ) { emitAllMembers ( i ) ; } } } function emitEventHandlers ( prefix , i ) { const fPrefix = prefix . startsWith ( "declare var" ) ? "declare function " : "" ; for ( const addOrRemove of [ "add" , "remove" ] ) { const optionsType = addOrRemove === "add" ? "AddEventListenerOptions" : "EventListenerOptions" ; if ( tryEmitTypedEventHandlerForInterface ( addOrRemove , optionsType ) ) { if ( i . name === "EventSource" ) { printer . printLine ( `${ fPrefix }${ addOrRemove }EventListener(type: string, listener: (this: EventSource, event: MessageEvent) => any, options?: boolean | ${ optionsType }): void;` ) ; } printer . printLine ( `${ fPrefix }${ addOrRemove }EventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | ${ optionsType }): void;` ) ; } } return ; function emitTypedEventHandler ( prefix , addOrRemove , iParent , optionsType ) { printer . printLine ( `${ prefix }${ addOrRemove }EventListener<K extends keyof ${ iParent . name }EventMap>(type: K, listener: (this: ${ nameWithForwardedTypes ( i ) }, ev: ${ iParent . name }EventMap[K]) => any, options?: boolean | ${ optionsType }): void;` ) ; } function tryEmitTypedEventHandlerForInterface ( addOrRemove , optionsType ) { const hasEventListener = iNameToEhList [ i . name ] ?. length ; const ehParentCount = iNameToEhParents [ i . name ] ?. length ; let target ; if ( hasEventListener || ehParentCount > 1 ) { target = i ; } else if ( ehParentCount === 1 ) { target = iNameToEhParents [ i . name ] [ 0 ] ; } else { return false ; } emitTypedEventHandler ( fPrefix , addOrRemove , target , optionsType ) ; return true ; } } function emitConstructorSignature ( i ) { const constructor = typeof i . constructor === "object" ? i . constructor : undefined ; if ( constructor ) { emitComments ( constructor , printer . print ) ; emitSignatures ( constructor , "" , "new" , printer . printLine ) ; } else { printer . printLine ( `new(): ${ i . name };` ) ; } } function emitConstructor ( i , prefix = "" ) { if ( i . deprecated ) { printer . printLine ( `/** @deprecated */` ) ; } printer . printLine ( `${ prefix }var ${ i . name }: {` ) ; printer . increaseIndent ( ) ; printer . printLine ( `prototype: ${ i . name };` ) ; emitConstructorSignature ( i ) ; emitConstants ( i ) ; if ( iNameToConstParents [ i . name ] ?. length ) { for ( const parent of iNameToConstParents [ i . name ] ) { emitConstants ( parent ) ; } } emitMembers ( "" , EmitScope . StaticOnly , i ) ; printer . decreaseIndent ( ) ; printer . printLine ( "};" ) ; printer . printLine ( "" ) ; if ( global === "Window" && i . legacyWindowAlias ) { for ( const alias of i . legacyWindowAlias ! ) { printer . printLine ( `type ${ alias } = ${ i . name };` ) ; printer . printLine ( `declare var ${ alias }: typeof ${ i . name };` ) ; printer . printLine ( "" ) ; } } } function emitNamedConstructor ( i ) { const nc = i . namedConstructor ; if ( nc ) { printer . printLine ( `declare var ${ nc . name }: {` ) ; printer . increaseIndent ( ) ; nc . signature . forEach ( ( s ) => printer . printLine ( `new(${ s . param ? paramsToString ( s . param ) : "" }): ${ i . name };` ) ) ; printer . decreaseIndent ( ) ; printer . printLine ( `};` ) ; } } function emitNamedConstructors ( ) { getElements ( webidl . interfaces , "interface" ) . sort ( compareName ) . forEach ( emitNamedConstructor ) ; } function emitInterfaceDeclaration ( i ) { function processIName ( iName ) { return extendConflictsBaseTypes [ iName ] ? `${ iName }Base` : iName ; } const processedIName = processIName ( i . name ) ; if ( processedIName !== i . name ) { printer . printLineToStack ( `interface ${ getNameWithTypeParameter ( i . typeParameters , i . name ) } extends ${ processedIName } {` ) ; } emitComments ( i , printer . printLine ) ; printer . print ( `interface ${ getNameWithTypeParameter ( i . typeParameters , processedIName ) }` ) ; const finalExtends = [ i . extends || "Object" ] . concat ( getImplementList ( i . name ) ) . filter ( ( i ) => i !== "Object" ) . map ( processIName ) ; if ( finalExtends . length ) { printer . print ( ` extends ${ assertUnique ( finalExtends ) . join ( ", " ) }` ) ; } printer . print ( " {" ) ; printer . endLine ( ) ; } function shouldEmitIndexerSignature ( i , m ) { if ( m . getter && m . signature [ 0 ] ?. param ?. length === 1 ) { switch ( convertDomTypeToTsType ( m . signature [ 0 ] . param ! [ 0 ] ) ) { case "number" : return true ; case "string" : { if ( convertDomTypeToTsType ( m . signature [ 0 ] ) === "any" ) { return true ; } const sig = m . signature [ 0 ] ; const mTypes = distinct ( mapValues ( i . methods ?. method , ( m ) => m . signature ?. [ 0 ] . type || "void" ) . filter ( ( t ) => t !== "void" ) || [ ] ) ; const amTypes = distinct ( i . anonymousMethods ?. method . map ( ( m ) => m . signature [ 0 ] . type ) . filter ( ( t ) => t !== "void" ) || [ ] ) ; const pTypes = distinct ( mapValues ( i . properties ?. property , ( m ) => m . type ) . filter ( ( t ) => t !== "void" ) || [ ] ) ; if ( mTypes . length === 0 && amTypes . length === 1 && pTypes . length === 0 ) return amTypes [ 0 ] === sig . type ; if ( mTypes . length === 1 && amTypes . length === 1 && pTypes . length === 0 ) return mTypes [ 0 ] === amTypes [ 0 ] && amTypes [ 0 ] === sig . type ; if ( mTypes . length === 0 && amTypes . length === 1 && pTypes . length === 1 ) return amTypes [ 0 ] === pTypes [ 0 ] && amTypes [ 0 ] === sig . type ; if ( mTypes . length === 1 && amTypes . length === 1 && pTypes . length === 1 ) return ( mTypes [ 0 ] === amTypes [ 0 ] && amTypes [ 0 ] === pTypes [ 0 ] && amTypes [ 0 ] === sig . type ) ; } } } return false ; } function emitIndexers ( emitScope , i ) { if ( i . overrideIndexSignatures ) { i . overrideIndexSignatures ! . forEach ( ( s ) => printer . printLine ( `${ s };` ) ) ; } else { mapToArray < Browser . AnonymousMethod > ( i . methods ?. method ) . concat ( i . anonymousMethods ?. method || [ ] ) . filter ( ( m ) => shouldEmitIndexerSignature ( i , m ) && matchScope ( emitScope , m ) ) . forEach ( ( m ) => { const indexer = m . signature ?. [ 0 ] . param ?. [ 0 ] ; if ( indexer ) { printer . printLine ( `[${ indexer . name }: ${ convertDomTypeToTsType ( indexer ) }]: ${ convertDomTypeToTsType ( { type : m . signature [ 0 ] . type , overrideType : m . signature [ 0 ] . overrideType , subtype : m . signature [ 0 ] . subtype , nullable : undefined , } ) };` ) ; } } ) ; } } function emitInterfaceEventMap ( i ) { function emitInterfaceEventMapEntry ( eventName ) { printer . printLine ( `"${ eventName }": ${ getEventTypeInInterface ( eventName , i ) };` ) ; } const hasEventHandlers = iNameToEhList [ i . name ] ?. length ; const ehParentCount = iNameToEhParents [ i . name ] ?. length ; if ( hasEventHandlers || ehParentCount > 1 ) { printer . print ( `interface ${ i . name }EventMap` ) ; if ( ehParentCount ) { const extend = iNameToEhParents [ i . name ] . map ( ( i ) => i . name + "EventMap" ) ; printer . print ( ` extends ${ assertUnique ( extend ) . join ( ", " ) }` ) ; } printer . print ( " {" ) ; printer . endLine ( ) ; printer . increaseIndent ( ) ; iNameToEhList [ i . name ] . forEach ( emitInterfaceEventMapEntry ) ; printer . decreaseIndent ( ) ; printer . printLine ( "}" ) ; printer . printLine ( "" ) ; } } function emitInterface ( i ) { printer . clearStack ( ) ; emitInterfaceEventMap ( i ) ; emitInterfaceDeclaration ( i ) ; printer . increaseIndent ( ) ; emitMembers ( "" , EmitScope . InstanceOnly , i ) ; emitConstants ( i ) ; emitEventHandlers ( "" , i ) ; emitIndexers ( EmitScope . InstanceOnly , i ) ; printer . decreaseIndent ( ) ; printer . printLine ( "}" ) ; printer . printLine ( "" ) ; if ( ! printer . stackIsEmpty ( ) ) { printer . printStackContent ( ) ; printer . printLine ( "}" ) ; printer . printLine ( "" ) ; } } function emitNonCallbackInterfaces ( ) { for ( const i of allNonCallbackInterfaces . sort ( compareName ) ) { if ( i . legacyNamespace ) { continue ; } else if ( i . noInterfaceObject ) { emitInterface ( i ) ; } else { emitInterface ( i ) ; emitConstructor ( i , "declare " ) ; } } } function emitNamespace ( namespace ) { if ( namespace . comment ) { printer . printLine ( `/** ${ namespace . comment } */` ) ; } if ( namespacesAsInterfaces . includes ( namespace . name ) ) { const name = namespace . name [ 0 ] . toUpperCase ( ) + namespace . name . slice ( 1 ) ; emitInterface ( { ... namespace , name } ) ; printer . printLine ( `declare var ${ namespace . name }: ${ name };` ) ; printer . printLine ( "" ) ; return ; } printer . printLine ( `declare namespace ${ namespace . name } {` ) ; printer . increaseIndent ( ) ; if ( namespace . nested ) { namespace . nested . interfaces . sort ( compareName ) . forEach ( ( i ) => { emitInterface ( i ) ; emitConstructor ( i ) ; } ) ; namespace . nested . dictionaries . sort ( compareName ) . forEach ( emitDictionary ) ; namespace . nested . enums . sort ( compareName ) . forEach ( emitEnum ) ; namespace . nested . typedefs . sort ( compareName ) . forEach ( emitTypeDef ) ; } emitProperties ( "var " , EmitScope . InstanceOnly , namespace ) ; emitMethods ( "function " , EmitScope . InstanceOnly , namespace , new Set ( ) ) ; printer . decreaseIndent ( ) ; printer . printLine ( "}" ) ; printer . printLine ( "" ) ; } function emitDictionary ( dict ) { if ( ! dict . extends || dict . extends === "Object" ) { printer . printLine ( `interface ${ getNameWithTypeParameter ( dict . typeParameters , dict . name ) } {` ) ; } else { printer . printLine ( `interface ${ getNameWithTypeParameter ( dict . typeParameters , dict . name ) } extends ${ dict . extends } {` ) ; } printer . increaseIndent ( ) ; if ( dict . members ) { mapToArray ( dict . members . member ) . sort ( compareName ) . forEach ( ( m ) => { emitComments ( m , printer . printLine ) ; printer . printLine ( `${ m . name }${ m . required ? "" : "?" }: ${ convertDomTypeToTsType ( m ) };` ) ; } ) ; } if ( dict . overrideIndexSignatures ) { dict . overrideIndexSignatures ! . forEach ( ( s ) => printer . printLine ( `${ s };` ) ) ; } printer . decreaseIndent ( ) ; printer . printLine ( "}" ) ; printer . printLine ( "" ) ; } function emitDictionaries ( ) { getElements ( webidl . dictionaries , "dictionary" ) . sort ( compareName ) . filter ( ( i ) => ! i . legacyNamespace ) . forEach ( emitDictionary ) ; } function emitTypeDef ( typeDef ) { emitComments ( typeDef , printer . printLine ) ; printer . printLine ( `type ${ getNameWithTypeParameter ( typeDef . typeParameters , typeDef . name ) } = ${ convertDomTypeToTsType ( typeDef ) };` ) ; } function emitTypeDefs ( ) { if ( webidl . typedefs ) { webidl . typedefs . typedef . filter ( ( i ) => ! i . legacyNamespace ) . sort ( compareName ) . forEach ( emitTypeDef ) ; } } function compareName ( c1 , c2 ) { return c1 . name < c2 . name ? - 1 : c1 . name > c2 . name ? 1 : 0 ; } function emit ( ) { printer . reset ( ) ; printer . printLine ( "/////////////////////////////" ) ; printer . printLine ( `/// ${ global } APIs` ) ; printer . printLine ( "/////////////////////////////" ) ; printer . printLine ( "" ) ; emitDictionaries ( ) ; getElements ( webidl . callbackInterfaces , "interface" ) . sort ( compareName ) . forEach ( ( i ) => emitCallBackInterface ( i ) ) ; emitNonCallbackInterfaces ( ) ; collectLegacyNamespaceTypes ( webidl ) . forEach ( emitNamespace ) ; emitCallBackFunctions ( ) ; if ( global === "Window" ) { emitHTMLElementTagNameMap ( ) ; emitHTMLElementDeprecatedTagNameMap ( ) ; emitSVGElementTagNameMap ( ) ; emitElementTagNameMap ( ) ; emitNamedConstructors ( ) ; } if ( polluter ) { emitAllMembers ( polluter ) ; emitEventHandlers ( "declare var " , polluter ) ; } emitTypeDefs ( ) ; emitEnums ( ) ; return printer . getResult ( ) ; } function emitIterator ( i ) { const isIndexedPropertyGetter = ( m ) => m . getter && m . signature [ 0 ] ?. param ?. length === 1 && typeof m . signature [ 0 ] . param [ 0 ] . type === "string" && integerTypes . has ( m . signature [ 0 ] . param [ 0 ] . type ) ; function findIterableGetter ( ) { const anonymousGetter = i . anonymousMethods ?. method . find ( isIndexedPropertyGetter ) ; if ( anonymousGetter ) return anonymousGetter ; else if ( i . methods ) return mapToArray ( i . methods . method ) . find ( isIndexedPropertyGetter ) ; else return undefined ; } function getIteratorSubtypes ( ) { if ( i . iterator ) { if ( i . iterator . type . length === 1 ) { return [ convertDomTypeToTsType ( i . iterator . type [ 0 ] ) ] ; } return i . iterator . type . map ( convertDomTypeToTsType ) ; } else if ( i . name !== "Window" ) { const iterableGetter = findIterableGetter ( ) ; if ( iterableGetter ) { return [ convertDomTypeToTsType ( { type : iterableGetter . signature [ 0 ] . type , overrideType : iterableGetter . signature [ 0 ] . overrideType , } ) , ] ; } } } function stringifySingleOrTupleTypes ( types ) { if ( types . length === 1 ) { return types [ 0 ] ; } return `[${ types . join ( ", " ) }]` ; } function emitIterableDeclarationMethods ( i , subtypes ) { let [ keyType , valueType ] = subtypes ; if ( ! valueType ) { valueType = keyType ; keyType = "number" ; } const methods = [ { name : "entries" , definition : `IterableIterator<[${ keyType }, ${ valueType }]>` , } , { name : "keys" , definition : `IterableIterator<${ keyType }>` , } , { name : "values" , definition : `IterableIterator<${ valueType }>` , } , ] ; const comments = i . iterator ?. comments ?. comment ; methods . forEach ( ( m ) => { emitComments ( { comment : comments ?. [ m . name ] } , printer . printLine ) ; printer . printLine ( `${ m . name }(): ${ m . definition };` ) ; } ) ; } function getIteratorExtends ( iterator ? , subtypes ? ) { if ( ! iterator || ! subtypes ) { return "" ; } const base = iterator . kind === "maplike" ? `Map<${ subtypes [ 0 ] }, ${ subtypes [ 1 ] }>` : iterator . kind === "setlike" ? `Set<${ subtypes [ 0 ] }>` : undefined ; if ( ! base ) { return "" ; } const result = iterator . readonly ? `Readonly${ base }` : base ; return `extends ${ result } ` ; } function hasSequenceArgument ( s ) { function typeIncludesSequence ( type ) { if ( Array . isArray ( type ) ) { return type . some ( ( t ) => typeIncludesSequence ( t . type ) ) ; } return type === "sequence" || ! ! sequenceTypedefMap [ type ] ; } return s . param ?. some ( ( p ) => ! p . overrideType && typeIncludesSequence ( p . type ) ) ; } function replaceTypedefsInSignatures ( signatures ) { return signatures . map ( ( s ) => { const params = s . param ! . map ( ( p ) => { const typedef = typeof p . type === "string" ? sequenceTypedefMap [ p . type ] : undefined ; if ( ! typedef ) { return p ; } return { ... p , type : typedef . type } ; } ) ; return { ... s , param : params } ; } ) ; } const sequenceTypedefs = ! webidl . typedefs ? [ ] : webidl . typedefs . typedef . filter ( ( typedef ) => Array . isArray ( typedef . type ) ) . map ( ( typedef ) => ( { ... typedef , type : ( typedef . type as Browser . Typed [ ] ) . filter ( ( t ) => t . type === "sequence" ) , } ) ) . filter ( ( typedef ) => typedef . type . length ) ; const sequenceTypedefMap = arrayToMap ( sequenceTypedefs , ( t ) => t . name , ( t ) => t ) ; const subtypes = getIteratorSubtypes ( ) ; const methodsWithSequence = mapToArray ( i . methods ? i . methods . method : { } ) . filter ( ( m ) => m . signature && ! m . overrideSignatures ) . map ( ( m ) => ( { ... m , signature : replaceTypedefsInSignatures ( m . signature . filter ( hasSequenceArgument ) ) , } ) ) . filter ( ( m ) => m . signature . length ) . sort ( compareName ) ; if ( subtypes || methodsWithSequence . length ) { const iteratorExtends = getIteratorExtends ( i . iterator , subtypes ) ; const name = getNameWithTypeParameter ( i . typeParameters , extendConflictsBaseTypes [ i . name ] ? `${ i . name }Base` : i . name ) ; printer . printLine ( "" ) ; printer . printLine ( `interface ${ name } ${ iteratorExtends }{` ) ; printer . increaseIndent ( ) ; methodsWithSequence . forEach ( ( m ) => emitMethod ( "" , m , new Set ( ) ) ) ; if ( subtypes && ! iteratorExtends ) { printer . printLine ( `[Symbol.iterator](): IterableIterator<${ stringifySingleOrTupleTypes ( subtypes ) }>;` ) ; } if ( i . iterator ?. kind === "iterable" && subtypes ) { emitIterableDeclarationMethods ( i , subtypes ) ; } printer . decreaseIndent ( ) ; printer . printLine ( "}" ) ; } } function emitES6DomIterators ( ) { printer . reset ( ) ; printer . printLine ( "/////////////////////////////" ) ; printer . printLine ( `/// ${ global } Iterable APIs` ) ; printer . printLine ( "/////////////////////////////" ) ; allInterfaces . sort ( compareName ) . forEach ( emitIterator ) ; return printer . getResult ( ) ; } }