Skip to content

Reduce symbol instantiations in lib.d.ts #166

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

Merged
merged 6 commits into from
Nov 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 22 additions & 16 deletions Shared.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ module JsonItems =
| SignatureOverload
| TypeDef
| Extends
override x.ToString() =
override x.ToString() =
match x with
| Property _ -> "property"
| Method _ -> "method"
Expand Down Expand Up @@ -205,7 +205,7 @@ type Param =
/// Function overload
type Overload =
{ ParamCombinations : Param list
ReturnTypes : string list
ReturnTypes : string list
Nullable : Boolean }
member this.IsEmpty = this.ParamCombinations.IsEmpty && (this.ReturnTypes = [ "void" ] || this.ReturnTypes = [ "" ])

Expand Down Expand Up @@ -320,8 +320,8 @@ let allCallbackFuncs =

let GetInterfaceByName = allInterfacesMap.TryFind
let knownWorkerInterfaces =
[ "Algorithm"; "AlgorithmIdentifier"; "KeyAlgorithm"; "CryptoKey"; "AbstractWorker"; "AudioBuffer"; "Blob";
"CloseEvent"; "Console"; "Coordinates"; "DecodeSuccessCallback";
[ "Algorithm"; "AlgorithmIdentifier"; "KeyAlgorithm"; "CryptoKey"; "AbstractWorker"; "AudioBuffer"; "Blob";
"CloseEvent"; "Console"; "Coordinates"; "DecodeSuccessCallback";
"DecodeErrorCallback"; "DOMError"; "DOMException"; "DOMStringList"; "ErrorEvent"; "Event"; "ErrorEventHandler";
"EventException"; "EventInit"; "EventListener"; "EventTarget"; "File"; "FileList"; "FileReader";
"FunctionStringCallback"; "IDBCursor"; "IDBCursorWithValue"; "IDBDatabase"; "IDBFactory"; "IDBIndex";
Expand Down Expand Up @@ -400,7 +400,7 @@ let getEventTypeInInterface eName iName =
-> "Event"
| "XMLHttpRequest", _
-> "ProgressEvent"
| _ ->
| _ ->
match eNameToEType.TryFind eName with
| Some eType' -> eType'
| _ -> "Event"
Expand Down Expand Up @@ -519,25 +519,31 @@ let iNameToEhList =
else None)
|> List.ofArray
| None -> []
if ownEventHandler.Length > 0 then ownEventHandler else []

allInterfaces
|> Array.map (fun i -> (i.Name, GetEventHandler i))
|> Map.ofArray

let iNameToEhParents =
let hasHandler (i : Browser.Interface) =
iNameToEhList.ContainsKey i.Name && not iNameToEhList.[i.Name].IsEmpty

// Get all the event handlers from an interface and also from its inherited / implemented interfaces
let rec GetEventHandler(i : Browser.Interface) =
let extendedEventHandler =
match GetInterfaceByName i.Extends with
| Some i -> GetEventHandler i
| None -> []
| Some i when hasHandler i -> [i]
| _ -> []

let implementedEventHandler =
let implementis = i.Implements |> Array.map GetInterfaceByName
[ for i' in implementis do
yield! match i' with
| Some i -> GetEventHandler i
| Some i -> if hasHandler i then [i] else []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this returns the interface itself instead of the handlers it has?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. i could make it the interface name. but seemed the same

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then the name of the variable should be changed to implementedInterface and other places, otherwise it can be confusing later

| None -> [] ]

// Reason is if an interface doesn't have its own string overload for the addEventListener method,
// the inherited overloads will be carried along; otherwise all of them will be overriten by its
// own overloads, therefore re-declaration is needed
if ownEventHandler.Length > 0 then
List.concat [ ownEventHandler; extendedEventHandler; implementedEventHandler ]
else []
List.concat [ extendedEventHandler; implementedEventHandler ]

allInterfaces
|> Array.map (fun i -> (i.Name, GetEventHandler i))
Expand Down Expand Up @@ -635,11 +641,11 @@ let GetOverloads (f : Function) (decomposeMultipleTypes : bool) =
if decomposeMultipleTypes then
[ for pComb in pCombList do
yield { ParamCombinations = pComb
ReturnTypes = rTypes
ReturnTypes = rTypes
Nullable = isNullable } ]
else
[ { ParamCombinations = getParams f
ReturnTypes = rTypes
ReturnTypes = rTypes
Nullable = isNullable } ]

/// Define the subset of events that dedicated workers will use
Expand Down
145 changes: 90 additions & 55 deletions TS.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,32 +117,55 @@ let matchSingleParamMethodSignature (m: Browser.Method) expectedMName expectedMT
/// Emit overloads for the createElement method
let EmitCreateElementOverloads (m: Browser.Method) =
if matchSingleParamMethodSignature m "createElement" "Element" "string" then
for e in tagNameToEleName do
if iNameToIDependList.ContainsKey e.Value && Seq.contains "HTMLElement" iNameToIDependList.[e.Value] then
Pt.printl "createElement(tagName: \"%s\"): %s;" e.Key e.Value
Pt.printl "createElement<K extends keyof HTMLElementTagNameMap>(tagName: K): HTMLElementTagNameMap[K];"
Pt.printl "createElement(tagName: string): HTMLElement;"

/// Emit overloads for the getElementsByTagName method
let EmitGetElementsByTagNameOverloads (m: Browser.Method) =
if matchSingleParamMethodSignature m "getElementsByTagName" "NodeList" "string" then
for e in tagNameToEleName do
Pt.printl "getElementsByTagName(%s: \"%s\"): NodeListOf<%s>;" m.Params.[0].Name (e.Key.ToLower()) e.Value
Pt.printl "getElementsByTagName<K extends keyof ElementListTagNameMap>(%s: K): ElementListTagNameMap[K];" m.Params.[0].Name
Pt.printl "getElementsByTagName(%s: string): NodeListOf<Element>;" m.Params.[0].Name

/// Emit overloads for the querySelector method
let EmitQuerySelectorOverloads (m: Browser.Method) =
if matchSingleParamMethodSignature m "querySelector" "Element" "string" then
for e in tagNameToEleName do
Pt.printl "querySelector(selectors: \"%s\"): %s | null;" (e.Key.ToLower()) e.Value
Pt.printl "querySelector<K extends keyof ElementTagNameMap>(selectors: K): ElementTagNameMap[K] | null;"
Pt.printl "querySelector(selectors: string): Element | null;"

/// Emit overloads for the querySelectorAll method
let EmitQuerySelectorAllOverloads (m: Browser.Method) =
if matchSingleParamMethodSignature m "querySelectorAll" "NodeList" "string" then
for e in tagNameToEleName do
Pt.printl "querySelectorAll(selectors: \"%s\"): NodeListOf<%s>;" (e.Key.ToLower()) e.Value
Pt.printl "querySelectorAll<K extends keyof ElementListTagNameMap>(selectors: K): ElementListTagNameMap[K];"
Pt.printl "querySelectorAll(selectors: string): NodeListOf<Element>;"

let EmitHTMLElementTagNameMap () =
Pt.printl "interface HTMLElementTagNameMap {"
Pt.increaseIndent()
for e in tagNameToEleName do
if iNameToIDependList.ContainsKey e.Value && Seq.contains "HTMLElement" iNameToIDependList.[e.Value] then
Pt.printl "\"%s\": %s;" (e.Key.ToLower()) e.Value
Pt.decreaseIndent()
Pt.printl "}"
Pt.printl ""

let EmitElementTagNameMap () =
Pt.printl "interface ElementTagNameMap {"
Pt.increaseIndent()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like ElementTagNameMap interface is always the same as HTMLElementTagNameMap? Then make is an alias instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no they are different. not sure why though. i wanted to ask you about that.

Copy link
Contributor

@zhengbli zhengbli Nov 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha now I remember, the EmitHTMLElementTagNameMap only prints tag elements that either extends or implements the HTMLElement type.

for e in tagNameToEleName do
Pt.printl "\"%s\": %s;" (e.Key.ToLower()) e.Value
Pt.decreaseIndent()
Pt.printl "}"
Pt.printl ""

let EmitElementListTagNameMap () =
Pt.printl "interface ElementListTagNameMap {"
Pt.increaseIndent()
for e in tagNameToEleName do
Pt.printl "\"%s\": NodeListOf<%s>;" (e.Key.ToLower()) e.Value
Copy link
Contributor

@zhengbli zhengbli Nov 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do

type ElementListTagNameMap = { [K in ElementTagNameMap]: NodeListOf<ElementTagNameMap[K]> }

and avoid the loop

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is what i had earlier, then changed it. the issue is that NodeListOf has a constraint that T extends Node. we can not verify this constraint on ElementTagNameMap[K] unless i add a string indexer. adding the indexer makes the language service experience of the signature help much worse. so this is a work around for that.

Pt.decreaseIndent()
Pt.printl "}"
Pt.printl ""

/// Emit overloads for the createEvent method
let EmitCreateEventOverloads (m: Browser.Method) =
if matchSingleParamMethodSignature m "createEvent" "Event" "string" then
Expand Down Expand Up @@ -205,7 +228,7 @@ let EmitMethod flavor prefix (i:Browser.Interface) (m:Browser.Method) =
let overloads = GetOverloads (Function.Method m) false
for { ParamCombinations = pCombList; ReturnTypes = rTypes; Nullable = isNullable } in overloads do
let paramsString = ParamsToString pCombList
let returnString =
let returnString =
let returnType = rTypes |> List.map DomTypeToTsType |> String.concat " | "
if isNullable then makeNullable returnType else returnType
Pt.printl "%s%s(%s): %s;" prefix (if m.Name.IsSome then m.Name.Value else "") paramsString returnString
Expand Down Expand Up @@ -243,12 +266,11 @@ let EmitEnums () =
let emitEnum (e: Browser.Enum) = Pt.printl "declare var %s: string;" e.Name
browser.Enums |> Array.iter emitEnum

let EmitEventHandlerThis flavor (prefix: string) =
if prefix = "" then "this: this, "
let EmitEventHandlerThis flavor (prefix: string) (i: Browser.Interface) =
if prefix = "" then "this: " + i.Name + ", "
else match GetGlobalPollutor flavor with
| Some pollutor -> "this: " + pollutor.Name + ", "
| _ -> ""

let EmitProperties flavor prefix (emitScope: EmitScope) (i: Browser.Interface)=
let emitPropertyFromJson (p: ItemsType.Root) =
let readOnlyModifier =
Expand All @@ -272,15 +294,15 @@ let EmitProperties flavor prefix (emitScope: EmitScope) (i: Browser.Interface)=
let pType =
match p.Type with
| "EventHandler" ->
// Sometimes event handlers with the same name may actually handle different
// events in different interfaces. For example, "onerror" handles "ErrorEvent"
// Sometimes event handlers with the same name may actually handle different
// events in different interfaces. For example, "onerror" handles "ErrorEvent"
// normally, but in "SVGSVGElement" it handles "SVGError" event instead.
let eType =
let eType =
if p.EventHandler.IsSome then
getEventTypeInInterface p.EventHandler.Value i.Name
else
else
"Event"
String.Format("({0}ev: {1}) => any", EmitEventHandlerThis flavor prefix, eType)
String.Format("({0}ev: {1}) => any", EmitEventHandlerThis flavor prefix i, eType)
| _ -> DomTypeToTsType p.Type
let pTypeAndNull = if p.Nullable.IsSome then makeNullable pType else pType
let readOnlyModifier = if p.ReadOnly.IsSome && prefix = "" then "readonly " else ""
Expand All @@ -307,15 +329,11 @@ let EmitMethods flavor prefix (emitScope: EmitScope) (i: Browser.Interface) =
let emitMethodFromJson (m: ItemsType.Root) =
m.Signatures |> Array.iter (Pt.printl "%s%s;" prefix)

// Because eventhandler overload are not inherited between interfaces,
// they need to be taken care of seperately
let hasEventHandlers =
iNameToEhList.ContainsKey i.Name &&
not iNameToEhList.[i.Name].IsEmpty

// If prefix is not empty, then this is the global declare function addEventListener, we want to override this
// Otherwise, this is EventTarget.addEventListener, we want to keep that.
let mFilter (m:Browser.Method) =
matchScope emitScope m &&
not (hasEventHandlers && OptionCheckValue "addEventListener" m.Name)
not (prefix <> "" && OptionCheckValue "addEventListener" m.Name)

if i.Methods.IsSome then
i.Methods.Value.Methods
Expand Down Expand Up @@ -349,36 +367,30 @@ let rec EmitAllMembers flavor (i:Browser.Interface) =
| _ -> ()

let EmitEventHandlers (flavor: Flavor) (prefix: string) (i:Browser.Interface) =
let emitEventHandler prefix (eHandler: EventHandler) =
let eventType =
getEventTypeInInterface eHandler.EventName i.Name
let fPrefix =
if prefix.StartsWith "declare var" then "declare function " else ""

let emitEventHandler prefix (i:Browser.Interface) =
Pt.printl
"%saddEventListener(type: \"%s\", listener: (%sev: %s) => any, useCapture?: boolean): void;"
prefix eHandler.EventName (EmitEventHandlerThis flavor prefix) eventType

let fPrefix = if prefix.StartsWith "declare var" then "declare function " else ""

// Inheritance of "addEventListener" has two cases:
// 1. No own eventhandlers -> it inherits all the eventhandlers from base interfaces
// 2. Has own eventhandlers -> TypeScript's inherit mechanism erases all inherited eventhandler overloads
// so they need to be reprinted.
if iNameToEhList.ContainsKey i.Name then
iNameToEhList.[i.Name] |> List.sortBy (fun eh -> eh.EventName) |> List.iter (emitEventHandler fPrefix)
let shouldPrintAddEventListener =
if iNameToEhList.[i.Name].Length > 0 then true
else
match i.Extends, i.Implements.Length with
| _, 0 -> false
| "Object", 1 -> false
| _ ->
let allParents = Array.append [|i.Extends|] i.Implements
match allParents |> Array.filter iNameToEhList.ContainsKey |> Array.length with
// only one of the implemented interface has EventHandlers
| 0 | 1 -> false
// multiple implemented interfaces have EventHandlers
| _ -> true
if shouldPrintAddEventListener then
Pt.printl "%saddEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;" fPrefix
"%saddEventListener<K extends keyof %sEventMap>(type: K, listener: (this: %s, ev: %sEventMap[K]) => any, useCapture?: boolean): void;"
prefix i.Name i.Name i.Name

let shouldEmitStringEventHandler =
if iNameToEhList.ContainsKey i.Name && not iNameToEhList.[i.Name].IsEmpty then
emitEventHandler fPrefix i
true
elif iNameToEhParents.ContainsKey i.Name && not iNameToEhParents.[i.Name].IsEmpty then
iNameToEhParents.[i.Name]
|> List.sortBy (fun i -> i.Name)
|> List.iter (emitEventHandler fPrefix)
true
else
false

if shouldEmitStringEventHandler then
Pt.printl
"%saddEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;"
fPrefix

let EmitConstructorSignature (i:Browser.Interface) =
let emitConstructorSigFromJson (c: ItemsType.Root) =
Expand Down Expand Up @@ -430,7 +442,7 @@ let EmitNamedConstructors () =

let EmitInterfaceDeclaration (i:Browser.Interface) =
Pt.printl "interface %s" i.Name
let finalExtends =
let finalExtends =
let overridenExtendsFromJson =
JsonItems.getOverriddenItemsByInterfaceName ItemKind.Extends Flavor.All i.Name
|> Array.map (fun e -> e.BaseInterface.Value) |> List.ofArray
Expand Down Expand Up @@ -516,7 +528,27 @@ let EmitIndexers emitScope (i: Browser.Interface) =
|> Array.filter (matchInterface i.Name)
|> Array.iter emitIndexerFromJson

let EmitInterfaceEventMap flavor (i:Browser.Interface) =
let EmitInterfaceEventMapEntry (eHandler: EventHandler) =
let eventType =
getEventTypeInInterface eHandler.EventName i.Name
Pt.printl "\"%s\": %s;" eHandler.EventName eventType

let ownEventHandles = if iNameToEhList.ContainsKey i.Name && not iNameToEhList.[i.Name].IsEmpty then iNameToEhList.[i.Name] else []
if ownEventHandles.Length > 0 then
Pt.printl "interface %sEventMap" i.Name
if iNameToEhParents.ContainsKey i.Name && not iNameToEhParents.[i.Name].IsEmpty then
let extends = iNameToEhParents.[i.Name] |> List.map (fun i -> i.Name + "EventMap")
Pt.print " extends %s" (String.Join(", ", extends))
Pt.print " {"
Pt.increaseIndent()
ownEventHandles |> List.iter EmitInterfaceEventMapEntry
Pt.decreaseIndent()
Pt.printl "}"
Pt.printl ""
let EmitInterface flavor (i:Browser.Interface) =
EmitInterfaceEventMap flavor i

Pt.resetIndent()
EmitInterfaceDeclaration i
Pt.increaseIndent()
Expand Down Expand Up @@ -718,6 +750,9 @@ let EmitTheWholeThing flavor (target:TextWriter) =
EmitCallBackFunctions flavor

if flavor <> Worker then
EmitHTMLElementTagNameMap()
EmitElementTagNameMap()
EmitElementListTagNameMap()
EmitNamedConstructors()

match GetGlobalPollutor flavor with
Expand Down
Loading