diff --git a/JS.fs b/JS.fs index be9d42977..70d6ef8ad 100644 --- a/JS.fs +++ b/JS.fs @@ -9,7 +9,7 @@ open Shared // Global Pt.print target let Pt = StringPrinter() -// When dump webworker interfaces dom types are ignored +// When emit webworker interfaces dom types are ignored let mutable ignoreDomType = false /// Return a stringbuilder @@ -117,8 +117,8 @@ let GetDefaultValue jsType = let GetJsDefaultValueForDomType (domType:string) = domType |> DomTypeToJsType |> GetDefaultValue -/// Dump event handlers that associated with an interface -let DumpEvents (i:Browser.Interface) = +/// Emit event handlers that associated with an interface +let EmitEvents (i:Browser.Interface) = match iNameToEhList.TryFind i.Name with | Some ehList -> ehList @@ -132,7 +132,7 @@ let DumpEvents (i:Browser.Interface) = else ()) | None -> () -let DumpProperties flavor (i:Browser.Interface) = +let EmitProperties flavor (i:Browser.Interface) = let propNameToElmentMap = function | "images" -> Some "img" | "rows" -> Some "tr" @@ -146,7 +146,7 @@ let DumpProperties flavor (i:Browser.Interface) = | "tBodies" -> Some "tbody" | _ -> None - let DumpProperty (p: Browser.Property) = + let EmitProperty (p: Browser.Property) = let value = p.Type |> DomTypeToJsType |> GetDefaultValue match p with | _ when p.Type = "EventHandler" -> () @@ -179,10 +179,10 @@ let DumpProperties flavor (i:Browser.Interface) = | Some propCollection -> propCollection.Properties |> Array.filter (ShouldKeep flavor) - |> Array.iter DumpProperty + |> Array.iter EmitProperty | None -> () -let DumpConstants suffix (i:Browser.Interface) = +let EmitConstants suffix (i:Browser.Interface) = match i.Constants with | Some cCollection -> for c in cCollection.Constants do @@ -192,8 +192,8 @@ let DumpConstants suffix (i:Browser.Interface) = | _ -> Pt.printl "%s%s.%s = %s;" i.Name suffix c.Name c.Value.Value | None -> () -let DumpSignatureCommentDocs (jsFunction:Function) = - let DumpSignatureDocForSingleParam (p: Param) = +let EmitSignatureCommentDocs (jsFunction:Function) = + let EmitSignatureDocForSingleParam (p: Param) = let pJsType = DomTypeToJsType p.Type (sprintf "/// " |> Pt.printl "%s" - let DumpSignatureDocForSingleOverload (ol: Overload) = + let EmitSignatureDocForSingleOverload (ol: Overload) = if not ol.IsEmpty then Pt.increaseIndent() match ol.ReturnTypes.Length with | 0 -> Pt.printl "/// " - ol.ParamCombinations |> List.iter DumpSignatureDocForSingleParam + ol.ParamCombinations |> List.iter EmitSignatureDocForSingleParam Pt.printl "/// " | 1 -> Pt.printl "/// " - ol.ParamCombinations |> List.iter DumpSignatureDocForSingleParam + ol.ParamCombinations |> List.iter EmitSignatureDocForSingleParam match ol.ReturnTypes.[0] with | "void" | "" -> () | arrayType when arrayType.StartsWith("sequence<") -> Pt.printl "/// " (GetElementTypeForArray arrayType) @@ -222,7 +222,7 @@ let DumpSignatureCommentDocs (jsFunction:Function) = |> List.iter (fun r -> Pt.printl "/// " - ol.ParamCombinations |> List.iter DumpSignatureDocForSingleParam + ol.ParamCombinations |> List.iter EmitSignatureDocForSingleParam match r with | "void" | "" -> () | arrayType when arrayType.StartsWith("sequence<") -> Pt.printl "/// " (GetElementTypeForArray arrayType) @@ -233,15 +233,15 @@ let DumpSignatureCommentDocs (jsFunction:Function) = else () let overloads = GetOverloads jsFunction true - if not overloads.IsEmpty then List.iter DumpSignatureDocForSingleOverload overloads + if not overloads.IsEmpty then List.iter EmitSignatureDocForSingleOverload overloads -let DumpMethods (i:Browser.Interface) = - let DumpMethod (m:Browser.Method) = +let EmitMethods (i:Browser.Interface) = + let EmitMethod (m:Browser.Method) = // print declaration let paramsStr = String.concat ", " [for p in m.Params do yield AdjustParamName p.Name] Pt.printl "%s.%s = function(%s) {" i.Name m.Name.Value paramsStr // print comment docs - DumpSignatureCommentDocs (Method m) + EmitSignatureCommentDocs (Method m) // print body match i.Name, m.Name.Value with | "EventTarget", "addEventListener" -> Pt.printWithAddedIndent "_eventManager.add(this, type, listener);" @@ -337,7 +337,7 @@ let DumpMethods (i:Browser.Interface) = Pt.printl "};" match i.Methods with - | Some ms -> Seq.iter DumpMethod ms.Methods + | Some ms -> Seq.iter EmitMethod ms.Methods | _ -> () // Explicitly expose 'toString' method for 'window' @@ -353,7 +353,7 @@ let DumpMethods (i:Browser.Interface) = return ''; };" -let DumpInterfaceInit (i:Browser.Interface) = +let EmitInterfaceInit (i:Browser.Interface) = let nodeType, nodeName = match i.Name with | "Text" -> "TEXT_NODE", "#text" @@ -431,16 +431,16 @@ let RegisterPublicInterfaces flavor = i.NoInterfaceObject.IsNone then Pt.printl "_publicInterface('%s', {" i.Name - // Dump constants - let cDump = + // Emit constants + let cEmit = match i.Constants with | Some (cs) -> [for c in cs.Constants do yield "'" + c.Name + "' : " + c.Value.String.Value] | _ -> [] - // Dump static methods - let mDump = + // Emit static methods + let mEmit = match i.Methods with | Some (ms) -> [for m in ms.Methods do @@ -448,7 +448,7 @@ let RegisterPublicInterfaces flavor = yield String.Format("'{0}' : {1}.{0}", m.Name.Value, i.Name)] | _ -> [] - let combined = String.concat "," (List.append cDump mDump) + let combined = String.concat "," (List.append cEmit mEmit) Pt.print "%s" (combined.Trim(',')) Pt.print "}, %s);" i.Name @@ -459,29 +459,29 @@ let RegisterConstructors flavor = | Some _ -> Pt.printl "_publicInterface('%s', %sCtor , %s);" i.Name i.Name i.Name | _ -> () -let DumpConstructor (i: Browser.Interface) = +let EmitConstructor (i: Browser.Interface) = match i.Constructor with - | Some _ -> DumpConstants "Ctor" i + | Some _ -> EmitConstants "Ctor" i | None -> () -let DumpInterface flavor (i:Browser.Interface) = +let EmitInterface flavor (i:Browser.Interface) = Pt.printl "" Pt.printl "/* -- type: %s -- */" i.Name Pt.printl "" - // Dump impletented interfaces + // Emit impletented interfaces i.Implements |> Array.iter (fun im -> Pt.printl "_$implement(%s, %s);" i.Name im) if i.Name = GetGlobalPollutorName flavor then // if the interface is the global pollutor, inherits becomes implements Pt.printl "_$implement(%s, %s);" i.Name i.Extends - // Dump other contents - DumpConstructor i - DumpProperties flavor i - DumpConstants "" i - DumpMethods i - DumpInterfaceInit i - DumpEvents i + // Emit other contents + EmitConstructor i + EmitProperties flavor i + EmitConstants "" i + EmitMethods i + EmitInterfaceInit i + EmitEvents i // Deal with array types if i.Name.EndsWith("List") || i.Name.EndsWith("Collection") then @@ -497,15 +497,15 @@ let DumpInterface flavor (i:Browser.Interface) = | None -> () | None -> () -let DumpCallBackFunctions flavor = - let DumpCallBackFunction (cb: Browser.CallbackFunction) = +let EmitCallBackFunctions flavor = + let EmitCallBackFunction (cb: Browser.CallbackFunction) = let paramsStr = cb.Params |> Array.map (fun p -> p.Name) |> String.concat ", " Pt.printl "var %s = function(%s) {" cb.Name paramsStr - DumpSignatureCommentDocs (CallBackFun cb) + EmitSignatureCommentDocs (CallBackFun cb) if cb.Type <> "void" then Pt.printWithAddedIndent "return %s;" (DomTypeToJsType cb.Type) Pt.printl "};" GetCallbackFuncsByFlavor flavor - |> Array.iter DumpCallBackFunction + |> Array.iter EmitCallBackFunction let RegisterCallBackFunctions flavor = let RegisterCallBackFunction (cb: Browser.CallbackFunction) = @@ -521,26 +521,26 @@ let RegisterDictionaries () = browser.Dictionaries |> Array.iter RegisterDictionary -let DumpDictionaries () = - let DumpDictionary (d:Browser.Dictionary) = +let EmitDictionaries () = + let EmitDictionary (d:Browser.Dictionary) = Pt.printl "" Pt.printl "/* -- type: %s -- */" d.Name Pt.printl "" - // Dump members + // Emit members for m in d.Members do let defaultValue = match m.Default.String with | Some dv -> dv | None -> GetJsDefaultValueForDomType m.Type Pt.printl "%s.%s = %s;" d.Name m.Name defaultValue - browser.Dictionaries |> Array.iter DumpDictionary + browser.Dictionaries |> Array.iter EmitDictionary -let DumpInterfaces flavor = +let EmitInterfaces flavor = let sortedTypes = SortInterfaces (GetAllInterfacesByFlavor flavor) - for t in sortedTypes do DumpInterface flavor t + for t in sortedTypes do EmitInterface flavor t -let DumpEventTypeToObjSwitchStatement flavor ignoreCase = +let EmitEventTypeToObjSwitchStatement flavor ignoreCase = Pt.printl "switch (type) {" Pt.increaseIndent() @@ -570,7 +570,7 @@ let DumpEventTypeToObjSwitchStatement flavor ignoreCase = Pt.decreaseIndent() Pt.printl "}" -let DumpGetElementByTagNameSwitchStatement () = +let EmitGetElementByTagNameSwitchStatement () = Pt.printl "switch (tagName.toLowerCase()) {" Pt.increaseIndent() @@ -581,9 +581,9 @@ let DumpGetElementByTagNameSwitchStatement () = Pt.print "}" -/// Dump the _createEvent function -let DumpCreateEventSwitchStatement () = - // Dump the switch statements +/// Emit the _createEvent function +let EmitCreateEventSwitchStatement () = + // Emit the switch statements Pt.printl "switch(eventType.toLowerCase()) {" distinctETypeList @@ -603,8 +603,8 @@ let DumpCreateEventSwitchStatement () = Pt.printl "}" -let DumpDeclarations flavor = - let DumpInterfaceDeclaration (i:Browser.Interface) = +let EmitDeclarations flavor = + let EmitInterfaceDeclaration (i:Browser.Interface) = let init = match i.Name with | name when name = GetGlobalPollutorName flavor -> "this" @@ -623,7 +623,7 @@ let DumpDeclarations flavor = "function(" + pList + ")" Pt.printl "var %sCtor = %s { " i.Name functionDeclare if ctor.Params.Length > 0 then - DumpSignatureCommentDocs (Ctor ctor) + EmitSignatureCommentDocs (Ctor ctor) Pt.printWithAddedIndent "return Object.create(%s);" i.Name Pt.printl "};" else @@ -633,30 +633,30 @@ let DumpDeclarations flavor = GetAllInterfacesByFlavor flavor |> SortInterfaces - |> Array.iter DumpInterfaceDeclaration + |> Array.iter EmitInterfaceDeclaration if flavor <> Worker then - let DumpDictDeclaration (d: Browser.Dictionary) = + let EmitDictDeclaration (d: Browser.Dictionary) = match d.Extends with | "Object" -> Pt.printl "var %s = {};" d.Name | _ -> Pt.printl "var %s = _$inherit(%s);" d.Name d.Extends browser.Dictionaries |> SortDicts - |> Array.iter DumpDictDeclaration + |> Array.iter EmitDictDeclaration -let DumpXmlContent flavor = - DumpDeclarations flavor - DumpCallBackFunctions flavor - DumpInterfaces flavor +let EmitXmlContent flavor = + EmitDeclarations flavor + EmitCallBackFunctions flavor + EmitInterfaces flavor if flavor <> Worker then - DumpDictionaries () + EmitDictionaries () let RegisterPublicObjs flavor = RegisterPublicInterfaces flavor RegisterConstructors flavor -/// Adjust the indention of the printer, and dump the indented content in the printer, +/// Adjust the indention of the printer, and emit the indented content in the printer, /// and then replace the place holder text with the content in printer let ReplaceWithIndentedFuncResult (placeHolder: String) func (sb: StringBuilder) = let curText = sb.ToString() @@ -667,7 +667,7 @@ let ReplaceWithIndentedFuncResult (placeHolder: String) func (sb: StringBuilder) func() |> ignore sb.Replace(placeHolder, Pt.getResult()) -let DumpTheWholeThing flavor (target: TextWriter) = +let EmitTheWholeThing flavor (target: TextWriter) = Pt.reset() let template = LoadTemplate ( __SOURCE_DIRECTORY__ + @"\inputfiles\jsTemplate.js") @@ -675,15 +675,15 @@ let DumpTheWholeThing flavor (target: TextWriter) = let content = template |> ReplaceWithIndentedFuncResult "<@ EventTypeToObjSwitchStatements @>" - (fun () -> DumpEventTypeToObjSwitchStatement flavor false) + (fun () -> EmitEventTypeToObjSwitchStatement flavor false) |> ReplaceWithIndentedFuncResult "<@ EventTypeToObjSwitchStatementsIgnoreCase @>" - (fun () -> DumpEventTypeToObjSwitchStatement flavor true) + (fun () -> EmitEventTypeToObjSwitchStatement flavor true) |> ReplaceWithIndentedFuncResult "<@ CreateEventSwitchStatements @>" - DumpCreateEventSwitchStatement + EmitCreateEventSwitchStatement |> ReplaceWithIndentedFuncResult "<@ GetElementsByTagNameSwitchStatements @>" - DumpGetElementByTagNameSwitchStatement + EmitGetElementByTagNameSwitchStatement |> ReplaceWithIndentedFuncResult "<@ XMLContents @>" - (fun () -> DumpXmlContent flavor) + (fun () -> EmitXmlContent flavor) |> ReplaceWithIndentedFuncResult "<@ Public Interfaces @>" (fun () -> RegisterPublicObjs flavor) |> (fun sb -> sb.Replace("<@ GlobalPolluter @>", GetGlobalPollutorName flavor)) @@ -692,13 +692,13 @@ let DumpTheWholeThing flavor (target: TextWriter) = fprintf target "%s" content target.Flush() -let DumpDomWeb () = - DumpTheWholeThing Web GlobalVars.jsWebOutput +let EmitDomWeb () = + EmitTheWholeThing Flavor.Web GlobalVars.jsWebOutput -let DumpDomWin () = - DumpTheWholeThing Windows GlobalVars.jsWinOutput +let EmitDomWin () = + EmitTheWholeThing Flavor.All GlobalVars.jsWinOutput -let DumpDomWorker () = +let EmitDomWorker () = Pt.reset() ignoreDomType <- true @@ -707,9 +707,9 @@ let DumpDomWorker () = let content = template |> ReplaceWithIndentedFuncResult "<@ EventTypeToObjSwitchStatements @>" - (fun () -> DumpEventTypeToObjSwitchStatement Worker false) + (fun () -> EmitEventTypeToObjSwitchStatement Worker false) |> ReplaceWithIndentedFuncResult "<@ XMLContents @>" - (fun () -> DumpXmlContent Worker) + (fun () -> EmitXmlContent Worker) |> ReplaceWithIndentedFuncResult "<@ Public Interfaces @>" (fun () -> RegisterPublicObjs Worker) |> (fun sb -> sb.Replace("<@ GlobalPolluter @>", GetGlobalPollutorName Worker)) diff --git a/Program.fs b/Program.fs index f77f17baa..13847604d 100644 --- a/Program.fs +++ b/Program.fs @@ -1,10 +1,10 @@  [] let main argv = - JS.DumpDomWeb() - JS.DumpDomWin() - JS.DumpDomWorker() + JS.EmitDomWeb() + JS.EmitDomWin() + JS.EmitDomWorker() // For typescript only generate for Dom - TS.DumpDomWeb() - TS.DumpDomWorker() + TS.EmitDomWeb() + TS.EmitDomWorker() 0 \ No newline at end of file diff --git a/README.md b/README.md index 6c8f8a841..4d6b5cf7b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# CreateDomJSTS -This tool is used to generate the DOM related part of `lib.d.ts` for TypeScript, and `domWeb.js` and `domWindows.js` for Visual Studio JavaScript language service. The input file is the XML spec file generated by the Microsoft Edge browser. +# TypeScript and JavaScript lib generator +This tool is used to generate `dom.generated.d.ts` and `webworker.generated.d.ts` for TypeScript, and `domWeb.js` and `domWindows.js` for Visual Studio JavaScript language service. The input file is the XML spec file generated by the Microsoft Edge browser. ## Build Instruction **Required Software**: Visual Studio 2015 Community Version (with Visual F# installed) @@ -14,9 +14,13 @@ Press `F5` in Visual Studio to run the tool. If it runs successfully, the output - `JS.fs`: handles the emitting of the `domWeb.js` and `domWindows.js` files used for Visual Studio JavaScript language service; - `Program.fs`: entry point of the tool; -- Inside the `inputfiles` folder: - - `browser.webidl.xml`: the XML spec file generated by Microsoft Edge (due to the different updating schedules between Edge and TypeScript, this is **not** the most up-to-date version of the spec); - - `jsTemplate.js`: the initial templates for `domWeb.js` and `domWindows.js`, which contains the necessary helper functions; - - `additionalSharedTypes.ts`: types should exist in both browser and webworker that are missing from the Edge spec. - - `additionalDomTypes.ts`: types should exist in only browser that are missing from the Edge spec. - - `additionalWorkerTypes.ts`: types should exist in only webworker that are missing from the Edge spec. + +## Input Files: +- `browser.webidl.xml`: the XML spec file generated by Microsoft Edge (due to the different updating schedules between Edge and TypeScript, this may **not** be the most up-to-date version of the spec.); +- `webworker.webidl.xml`: contains additional types for webworker. +- `addedTypes.json`: types that should exist in either browser or webworker but are missing from the Edge spec. The type can be `property`, `method`, `interface`, `constructor`, or `indexer`. +- `overridingTypes.json`: types that are defined in the spec file but has a better or more up-to-date definitions in the json files. +- `removedTypes.json`: types that are defined in the spec file but should be removed. +- `comments.json`: comment strings to be embedded in the generated .js files +- `jsTemplate.js`: the initial templates for `domWeb.js` and `domWindows.js`, which contains the necessary helper functions; +- `sample.json`: sample json file used to tell F# json type provider that structure of the json files. The content of it is not used anywhere. diff --git a/Shared.fs b/Shared.fs index c3aa60127..5b2138a6c 100644 --- a/Shared.fs +++ b/Shared.fs @@ -13,7 +13,7 @@ open Microsoft.FSharp.Reflection module GlobalVars = if not (Directory.Exists(__SOURCE_DIRECTORY__ + @"\generated")) then Directory.CreateDirectory(__SOURCE_DIRECTORY__ + @"\generated") |> ignore - + let inputFolder = __SOURCE_DIRECTORY__ + @"\inputfiles" let makeTextWriter fileName = File.CreateText(__SOURCE_DIRECTORY__ + @"\generated\" + fileName) :> TextWriter let jsWebOutput = makeTextWriter "domWeb.js" @@ -26,62 +26,91 @@ module GlobalVars = /// =========================================== /// Types /// =========================================== + +/// Quick checker for option type values +let OptionCheckValue value = function + | Some v when v = value -> true + | _ -> false + +let unionToString (x: 'a) = + match FSharpValue.GetUnionFields(x, typeof<'a>) with + | case, _ -> case.Name + type Flavor = | Worker | Web - | Windows - override x.ToString() = - match FSharpValue.GetUnionFields(x, typeof) with - | case, _ -> case.Name + | All + override x.ToString() = unionToString x + +type Browser = XmlProvider<"sample.xml", Global=true> + +module JsonItems = + type ItemsType = JsonProvider<"inputfiles/sample.json"> + + let overriddenItems = + File.ReadAllText(GlobalVars.inputFolder + @"\overridingTypes.json") |> ItemsType.Parse + + let removedItems = + File.ReadAllText(GlobalVars.inputFolder + @"\removedTypes.json") |> ItemsType.Parse -type Browser = XmlProvider< "sample.xml", Global=true > + let addedItems = + File.ReadAllText(GlobalVars.inputFolder + @"\addedTypes.json") |> ItemsType.Parse -type CommentType = JsonProvider<"inputfiles/comments.json"> + // This is the kind of items in the external json files that are used as a + // correction for the spec. + type ItemKind = + Property | Method | Constant | Constructor | Interface | Callback | Indexer + override x.ToString() = (unionToString x).ToLower() -type TypesFromJsonFile = JsonProvider<"inputfiles/sample.json"> + let findItem (allItems: ItemsType.Root []) (itemName: string) (kind: ItemKind) otherFilter = + let filter (item: ItemsType.Root) = + OptionCheckValue itemName item.Name && + item.Kind.ToLower() = kind.ToString() && + otherFilter item + allItems |> Array.tryFind filter -let overridingTypes = - File.ReadAllText(__SOURCE_DIRECTORY__ + @"\inputfiles\overridingTypes.json") |> TypesFromJsonFile.Parse + let matchInterface iName (item: ItemsType.Root) = + item.Interface.IsNone || item.Interface.Value = iName -let removedTypes = - File.ReadAllText(__SOURCE_DIRECTORY__ + @"\inputfiles\removedTypes.json") |> TypesFromJsonFile.Parse + let findOverriddenItem itemName (kind: ItemKind) iName = + findItem overriddenItems itemName kind (matchInterface iName) -let addedTypes = - File.ReadAllText(__SOURCE_DIRECTORY__ + @"\inputfiles\addedTypes.json") |> TypesFromJsonFile.Parse + let findRemovedItem itemName (kind: ItemKind) iName = + findItem removedItems itemName kind (matchInterface iName) -type MemberKind = - Property | Method - member this.ToString = if this = Property then "property" else "method" + let findAddedItem itemName (kind: ItemKind) iName = + findItem addedItems itemName kind (matchInterface iName) -let findTypeFromJsonArray (jsonArray: TypesFromJsonFile.Root []) mName iName (kind: MemberKind) = - jsonArray - |> Array.tryFind (fun t -> - t.Name = mName && (t.Interface.IsNone || t.Interface.Value = iName) && t.Kind = kind.ToString) + let getItems (allItems: ItemsType.Root []) (kind: ItemKind) (flavor: Flavor) = + allItems + |> Array.filter (fun t -> + t.Kind.ToLower() = kind.ToString() && + (t.Flavor.IsNone || t.Flavor.Value = flavor.ToString() || flavor = All)) -let findOverridingType mName iName (kind: MemberKind) = findTypeFromJsonArray overridingTypes mName iName kind -let findRemovedType mName iName (kind: MemberKind) = findTypeFromJsonArray removedTypes mName iName kind -let findAddedType mName iName (kind: MemberKind) = findTypeFromJsonArray addedTypes mName iName kind + let getOverriddenItems kind flavor = getItems overriddenItems kind flavor + let getAddedItems kind flavor = getItems addedItems kind flavor + let getRemovedItems kind flavor = getItems removedItems kind flavor -let getAllAddedInterfaces (flavor: Flavor) = - addedTypes |> Array.filter (fun t -> t.Kind = "interface" && (t.Flavor.IsNone || t.Flavor.Value = flavor.ToString() || flavor = Windows)) +module Comments = + type CommentType = JsonProvider<"inputfiles/comments.json"> -let comments = File.ReadAllText(__SOURCE_DIRECTORY__ + @"\inputfiles\comments.json") |> CommentType.Parse + let comments = File.ReadAllText(__SOURCE_DIRECTORY__ + @"\inputfiles\comments.json") |> CommentType.Parse -let GetCommentForProperty iName pName = - match comments.Interfaces |> Array.tryFind (fun i -> i.Name = iName) with - | Some i -> - match i.Members.Property |> Array.tryFind (fun p -> p.Name = pName) with - | Some p -> Some p.Comment + let GetCommentForProperty iName pName = + match comments.Interfaces |> Array.tryFind (fun i -> i.Name = iName) with + | Some i -> + match i.Members.Property |> Array.tryFind (fun p -> p.Name = pName) with + | Some p -> Some p.Comment + | _ -> None | _ -> None - | _ -> None -let GetCommentForMethod iName mName = - match comments.Interfaces |> Array.tryFind (fun i -> i.Name = iName) with - | Some i -> - match i.Members.Method |> Array.tryFind (fun m -> m.Name = mName) with - | Some m -> Some m.Comment + let GetCommentForMethod iName mName = + match comments.Interfaces |> Array.tryFind (fun i -> i.Name = iName) with + | Some i -> + match i.Members.Method |> Array.tryFind (fun m -> m.Name = mName) with + | Some m -> Some m.Comment + | _ -> None | _ -> None - | _ -> None // Printer for print to file type Printer(target : TextWriter) = @@ -102,12 +131,12 @@ type Printer(target : TextWriter) = member this.endBrace() = this.decreaseIndent() this.printl "}" - + member this.resetIndent() = curTabCount <- 0 member this.printWithAddedIndent content = Printf.kprintf (fun s -> output.Append("\r\n" + this.getCurIndent() + " " + s) |> ignore) content - member this.dump() = + member this.emit() = fprintf this.target "%s" (output.ToString()) this.target.Flush() @@ -163,12 +192,24 @@ type EventHandler = EventName : string EventType : string } -/// Decide which members of a function to dump -type DumpScope = +/// Decide which members of a function to emit +type EmitScope = | StaticOnly | InstanceOnly | All +// Used to decide if a member should be emitted given its static property and +// the intended scope level. +let inline matchScope scope (x: ^a when ^a: (member Static: Option<'b>)) = + if scope = EmitScope.All then true + else + let isStatic = (^a: (member Static: Option<'b>)x) + if isStatic.IsSome then scope = EmitScope.StaticOnly + else scope = EmitScope.InstanceOnly + +let matchInterface iName (x: JsonItems.ItemsType.Root) = + x.Interface.IsNone || x.Interface.Value = iName + /// =========================================== /// Shared data and helper functions /// =========================================== @@ -189,12 +230,6 @@ let AdjustParamName name = | "continue" -> "_continue" | _ -> name -/// Quick checker for option type values -let OptionCheckValue value = - function - | Some v when v = value -> true - | _ -> false - /// Parse the xml input file let browser = (new StreamReader(Path.Combine(GlobalVars.inputFolder, "browser.webidl.xml"))).ReadToEnd() |> Browser.Parse @@ -206,18 +241,18 @@ let worker = /// (Member constraint aka duck typing) /// reason is that ^a can be an interface, property or method, but they /// all share a 'tag' property -let inline ShouldKeep flavor (i : ^a when ^a : (member Tags : string option) and ^a : (member Name : string)) = +let inline ShouldKeep flavor (i : ^a when ^a : (member Tags : string option)) = let filterByTag = match ((((((^a : (member Tags : string option) i)))))) with | Some tags -> // Check if should be included match flavor with - | Web -> + | Flavor.Web -> [ "MSAppOnly"; "WinPhoneOnly" ] |> Seq.exists (fun t -> tags.Contains t) |> not - | Windows -> true - | Worker -> + | Flavor.All -> true + | Flavor.Worker -> [ "IEOnly" ] |> Seq.exists (fun t -> tags.Contains t) |> not @@ -228,9 +263,7 @@ let inline ShouldKeep flavor (i : ^a when ^a : (member Tags : string option) and let allWebNonCallbackInterfaces = Array.concat [| browser.Interfaces; browser.MixinInterfaces.Interfaces |] let allWebInterfaces = - Array.concat [| browser.Interfaces - [| browser.CallbackInterfaces.Interface |] - browser.MixinInterfaces.Interfaces |] + Array.concat [| browser.Interfaces; [| browser.CallbackInterfaces.Interface |]; browser.MixinInterfaces.Interfaces |] let allWorkerAdditionalInterfaces = Array.concat [| worker.Interfaces; worker.MixinInterfaces.Interfaces |] let allInterfaces = Array.concat [| allWebInterfaces; allWorkerAdditionalInterfaces |] @@ -271,29 +304,32 @@ let knownWorkerInterfaces = let GetAllInterfacesByFlavor flavor = match flavor with - | Web -> allWebInterfaces |> Array.filter (ShouldKeep Web) - | Windows -> allWebInterfaces |> Array.filter (ShouldKeep Windows) - | Worker -> + | Flavor.Web -> allWebInterfaces |> Array.filter (ShouldKeep Web) + | Flavor.All -> allWebInterfaces |> Array.filter (ShouldKeep Flavor.All) + | Flavor.Worker -> let isFromBrowserXml = allWebInterfaces |> Array.filter (fun i -> knownWorkerInterfaces.Contains i.Name) Array.append isFromBrowserXml allWorkerAdditionalInterfaces let GetNonCallbackInterfacesByFlavor flavor = match flavor with - | Web -> allWebNonCallbackInterfaces |> Array.filter (ShouldKeep Web) - | Windows -> allWebNonCallbackInterfaces |> Array.filter (ShouldKeep Windows) - | Worker -> + | Flavor.Web -> allWebNonCallbackInterfaces |> Array.filter (ShouldKeep Flavor.Web) + | Flavor.All -> allWebNonCallbackInterfaces |> Array.filter (ShouldKeep Flavor.All) + | Flavor.Worker -> let isFromBrowserXml = allWebNonCallbackInterfaces |> Array.filter (fun i -> knownWorkerInterfaces.Contains i.Name) Array.append isFromBrowserXml allWorkerAdditionalInterfaces let GetPublicInterfacesByFlavor flavor = match flavor with - | Web | Windows -> browser.Interfaces |> Array.filter (ShouldKeep flavor) - | Worker -> + | Flavor.Web | Flavor.All -> browser.Interfaces |> Array.filter (ShouldKeep flavor) + | Flavor.Worker -> let isFromBrowserXml = browser.Interfaces |> Array.filter (fun i -> knownWorkerInterfaces.Contains i.Name) Array.append isFromBrowserXml worker.Interfaces -let GetCallbackFuncsByFlavor flavor = browser.CallbackFunctions |> Array.filter (ShouldKeep flavor) +let GetCallbackFuncsByFlavor flavor = + browser.CallbackFunctions + |> Array.filter (ShouldKeep flavor) + |> Array.filter (fun cb -> flavor <> Flavor.Worker || knownWorkerInterfaces.Contains cb.Name) /// Event name to event type map let eNameToEType = @@ -336,7 +372,7 @@ let tagNameToEleName = | name when Seq.contains name iNames -> name | _ -> raise (Exception("Element conflict occured! Typename: " + tagName)) - [ for i in GetNonCallbackInterfacesByFlavor Windows do + [ for i in GetNonCallbackInterfacesByFlavor Flavor.All do yield! [ for e in i.Elements do yield (e.Name, i.Name) ] ] |> Seq.groupBy fst @@ -374,7 +410,7 @@ let iNameToIDependList = /// Distinct event type list, used in the "createEvent" function let distinctETypeList = let usedEvents = - [ for i in GetNonCallbackInterfacesByFlavor Windows do + [ for i in GetNonCallbackInterfacesByFlavor Flavor.All do match i.Events with | Some es -> yield! es.Events | _ -> () ] @@ -382,7 +418,7 @@ let distinctETypeList = |> List.distinct let unUsedEvents = - GetNonCallbackInterfacesByFlavor Windows + GetNonCallbackInterfacesByFlavor Flavor.All |> Array.filter (fun i -> i.Extends = "Event") |> Array.map (fun i -> i.Name) |> Array.filter (fun n -> n.EndsWith("Event") && not (List.contains n usedEvents)) @@ -469,8 +505,8 @@ let ehNameToEType = let GetGlobalPollutor flavor = match flavor with - | Web | Windows -> browser.Interfaces |> Array.tryFind (fun i -> i.PrimaryGlobal.IsSome) - | Worker -> worker.Interfaces |> Array.tryFind (fun i -> i.Global.IsSome) + | Flavor.Web | Flavor.All -> browser.Interfaces |> Array.tryFind (fun i -> i.PrimaryGlobal.IsSome) + | Flavor.Worker -> worker.Interfaces |> Array.tryFind (fun i -> i.Global.IsSome) let GetGlobalPollutorName flavor = match GetGlobalPollutor flavor with @@ -555,3 +591,14 @@ let workerEventsMap = ("loadend", "ProgressEvent") ("progress", "ProgressEvent") ] |> Map.ofList + +module Option = + let runIfSome f x = + match x with + | Some x' -> f x' + | _ -> () + + let toBool f x = + match x with + | Some x' -> f x' + | _ -> false \ No newline at end of file diff --git a/TS.fs b/TS.fs index 4024f8746..4dce25f91 100644 --- a/TS.fs +++ b/TS.fs @@ -3,12 +3,14 @@ open System open System.Text.RegularExpressions open Shared +open Shared.Comments +open Shared.JsonItems open System.IO // Global print target let Pt = StringPrinter() -// When dump webworker types the dom types are ignored +// When emit webworker types the dom types are ignored let mutable ignoreDOMTypes = false // Extended types used but not defined in the spec @@ -20,34 +22,23 @@ let rec DomTypeToTsType (objDomType: string) = match objDomType.Trim('?') with | "AbortMode" -> "String" | "any" -> "any" - | "bool" -> "boolean" - | "boolean" -> "boolean" - | "Boolean" -> "boolean" + | "bool" | "boolean" | "Boolean" -> "boolean" | "CanvasPixelArray" -> "number[]" | "Date" -> "Date" | "DOMHighResTimeStamp" -> "number" | "DOMString" -> "string" | "DOMTimeStamp" -> "number" - | "double" -> "number" | "EndOfStreamError" -> "number" | "EventListener" -> "EventListenerOrEventListenerObject" - | "float" -> "number" + | "double" | "float" -> "number" | "Function" -> "Function" - | "long long" -> "number" - | "long" -> "number" + | "long" | "long long" | "signed long" | "signed long long" | "unsigned long" | "unsigned long long" -> "number" | "object" -> "any" | "Promise" -> "Promise" | "ReadyState" -> "number" | "sequence" -> "Array" - | "short" -> "number" - | "signed long" -> "number" - | "signed long long" -> "number" - | "signed short" -> "number" + | "short" | "signed short" | "unsigned short" -> "number" | "UnrestrictedDouble" -> "number" - | "unsigned long" -> "number" - | "unsigned long long" -> "number" - | "unsigned long" -> "number" - | "unsigned short" -> "number" | "void" -> "void" | extendedType when List.contains extendedType extendedTypes -> extendedType | _ -> @@ -58,9 +49,9 @@ let rec DomTypeToTsType (objDomType: string) = allCallbackFuncs.ContainsKey objDomType || allDictionariesMap.ContainsKey objDomType then objDomType - elif allEnumsMap.ContainsKey objDomType then - "string" - // Deal with union type + // Enum types are all treated as string + elif allEnumsMap.ContainsKey objDomType then "string" + // Union types elif (objDomType.Contains(" or ")) then let allTypes = objDomType.Trim('(', ')').Split([|" or "|], StringSplitOptions.None) |> Array.map DomTypeToTsType @@ -79,61 +70,60 @@ let rec DomTypeToTsType (objDomType: string) = elif objDomType.EndsWith("[]") then let elementType = objDomType.Replace("[]", "").Trim() |> DomTypeToTsType elementType + "[]" - else - "any" - -let DumpConstants (i:Browser.Interface) = - let dumpConstant (c: Browser.Constant) = - Pt.printl "%s: %s;" c.Name (DomTypeToTsType c.Type) - match i.Constants with - | Some cs -> cs.Constants |> Array.iter dumpConstant - | None -> () - -/// Dump overloads for the createElement method -let DumpCreateElementOverloads (m:Browser.Method) = - if not (OptionCheckValue "createElement" m.Name) || - (DomTypeToTsType m.Type) <> "Element" || - m.Params.Length <> 1 || - (DomTypeToTsType m.Params.[0].Type) <> "string" then - raise (Exception "createElement method signature does not match expected.") - else + else "any" + +let EmitConstants (i: Browser.Interface) = + let emitConstantFromJson (c: ItemsType.Root) = Pt.printl "%s: %s;" c.Name.Value c.Type.Value + + let emitConstant (c: Browser.Constant) = + if Option.isNone (findRemovedItem c.Name ItemKind.Constant i.Name) then + match findOverriddenItem c.Name ItemKind.Constant i.Name with + | Some c' -> emitConstantFromJson c' + | None -> Pt.printl "%s: %s;" c.Name (DomTypeToTsType c.Type) + + // Emit the constants added in the json files + + let addedConstants = getAddedItems ItemKind.Constant Flavor.All + Array.iter emitConstantFromJson addedConstants + + if i.Constants.IsSome then + Array.iter emitConstant i.Constants.Value.Constants + +let matchSingleParamMethodSignature (m: Browser.Method) expectedMName expectedMType expectedParamType = + OptionCheckValue expectedMName m.Name && + (DomTypeToTsType m.Type) = expectedMType && + m.Params.Length = 1 && + (DomTypeToTsType m.Params.[0].Type) = expectedParamType + +/// 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(tagName: string): HTMLElement;" -/// Dump overloads for the getElementsByTagName method -let DumpGetElementsByTagNameOverloads (m:Browser.Method) = - if not (OptionCheckValue "getElementsByTagName" m.Name) || - (DomTypeToTsType m.Type) <> "NodeList" || - m.Params.Length <> 1 || - (DomTypeToTsType m.Params.[0].Type) <> "string" then - raise (Exception "getElementsByTagName method signature does not match expected.") - else +/// 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(%s: string): NodeListOf;" m.Params.[0].Name -/// Dump overloads for the createEvent method -let DumpCreateEventOverloads (m: Browser.Method) = - if not (OptionCheckValue "createEvent" m.Name) || - (DomTypeToTsType m.Type) <> "Event" || - m.Params.Length <> 1 || - (DomTypeToTsType m.Params.[0].Type) <> "string" then - raise (Exception "createEvent method signature does not match expected.") - else - // Dump plurals. For example, "Events", "MutationEvents" +/// Emit overloads for the createEvent method +let EmitCreateEventOverloads (m: Browser.Method) = + if matchSingleParamMethodSignature m "createEvent" "Event" "string" then + // Emit plurals. For example, "Events", "MutationEvents" let hasPlurals = ["Event"; "MutationEvent"; "MouseEvent"; "SVGZoomEvent"; "UIEvent"] - distinctETypeList - |> List.iter ( - fun x -> - if List.contains x hasPlurals then - Pt.printl "createEvent(eventInterface:\"%s\"): %s;" x x - Pt.printl "createEvent(eventInterface:\"%ss\"): %s;" x x - else - Pt.printl "createEvent(eventInterface:\"%s\"): %s;" x x) + for x in distinctETypeList do + if List.contains x hasPlurals then + Pt.printl "createEvent(eventInterface:\"%s\"): %s;" x x + Pt.printl "createEvent(eventInterface:\"%ss\"): %s;" x x + else + Pt.printl "createEvent(eventInterface:\"%s\"): %s;" x x Pt.printl "createEvent(eventInterface: string): Event;" +/// Generate the parameters string for function signatures let ParamsToString (ps: Param list) = let paramToString (p: Param) = (if p.Variadic then "..." else "") + @@ -143,127 +133,112 @@ let ParamsToString (ps: Param list) = (if p.Variadic then "[]" else "") String.Join(", ", (List.map paramToString ps)) -let DumpMethod flavor prefix (i:Browser.Interface) (m:Browser.Method) = +let EmitMethod flavor prefix (i:Browser.Interface) (m:Browser.Method) = // print comment if m.Name.IsSome then match GetCommentForMethod i.Name m.Name.Value with | Some comment -> Pt.printl "%s" comment | _ -> () - // find if there are overriding signatures in the external json file - let removedType = - match m.Name with - | Some mName -> findRemovedType mName i.Name MemberKind.Method - | None -> None - - let overridenType = - match m.Name with - | Some mName -> findOverridingType mName i.Name MemberKind.Method - | None -> None + // Find if there are overriding signatures in the external json file + // - overriddenType: meaning there is a better definition of this type in the json file + // - removedType: meaning the type is marked as removed in the json file + // if there is any conflicts between the two, the "removedType" has a higher priority over + // the "overridenType". + let removedType = Option.bind (fun name -> JsonItems.findRemovedItem name JsonItems.ItemKind.Method i.Name) m.Name + let overridenType = Option.bind (fun mName -> JsonItems.findOverriddenItem mName JsonItems.ItemKind.Method i.Name) m.Name - match removedType with - | Some r -> () - | None -> + if removedType.IsNone then match overridenType with | Some t -> match flavor with - | Windows | Web -> t.WebOnlySignatures |> Array.iter (Pt.printl "%s%s;" prefix) + | Flavor.All | Flavor.Web -> t.WebOnlySignatures |> Array.iter (Pt.printl "%s%s;" prefix) | _ -> () t.Signatures |> Array.iter (Pt.printl "%s%s;" prefix) | None -> match i.Name, m.Name with - | _, Some "createElement" -> DumpCreateElementOverloads m - | _, Some "createEvent" -> DumpCreateEventOverloads m - | _, Some "getElementsByTagName" -> DumpGetElementsByTagNameOverloads m + | _, Some "createElement" -> EmitCreateElementOverloads m + | _, Some "createEvent" -> EmitCreateEventOverloads m + | _, Some "getElementsByTagName" -> EmitGetElementsByTagNameOverloads m | _ -> - let consoleMethodsNeedToReplaceStringWithAny = [|"dir"; "dirxml"; "error"; "info"; "log"; "warn"|] - GetOverloads (Method m) false - |> List.iter - (fun { ParamCombinations = pCombList; ReturnTypes = rTypes } -> - let paramsString = - // Some console methods should accept "any" instead of "string" for convenience, although - // it is said to be string in the spec - if i.Name = "Console" && m.Name.IsSome && Array.contains m.Name.Value consoleMethodsNeedToReplaceStringWithAny then - (ParamsToString pCombList).Replace("string", "any") - else - ParamsToString pCombList - let returnString = rTypes |> List.map DomTypeToTsType |> String.concat " | " - Pt.printl "%s%s(%s): %s;" prefix (if m.Name.IsSome then m.Name.Value else "") paramsString returnString - ) - -let DumpCallBackInterface (i:Browser.Interface) = + let overloads = GetOverloads (Function.Method m) false + for { ParamCombinations = pCombList; ReturnTypes = rTypes } in overloads do + let paramsString = ParamsToString pCombList + let returnString = rTypes |> List.map DomTypeToTsType |> String.concat " | " + Pt.printl "%s%s(%s): %s;" prefix (if m.Name.IsSome then m.Name.Value else "") paramsString returnString + +let EmitCallBackInterface (i:Browser.Interface) = Pt.printl "interface %s {" i.Name - Pt.increaseIndent() - Pt.printl "(evt: Event): void;" - Pt.decreaseIndent() + Pt.printWithAddedIndent "(evt: Event): void;" Pt.printl "}" Pt.printl "" -let DumpCallBackFunctions flavor = - let DumpCallBackFunction (cb: Browser.CallbackFunction) = - Pt.printl "interface %s {" cb.Name - match cb.Name with - | "ErrorEventHandler" -> - Pt.printWithAddedIndent "(message: string, filename?: string, lineno?: number, colno?: number, error?:Error): void;" - | _ -> - for { ParamCombinations = pCombList } in GetOverloads (CallBackFun cb) false do - let paramsString = ParamsToString pCombList - match cb.Type with - | "void" -> - Pt.printWithAddedIndent "(%s): void;" paramsString - | _ -> - Pt.printWithAddedIndent "(%s): %s;" paramsString (DomTypeToTsType cb.Type) +let EmitCallBackFunctions flavor = + let emitCallbackFunctionsFromJson (cb: JsonItems.ItemsType.Root) = + Pt.printl "interface %s {" cb.Name.Value + cb.Signatures |> Array.iter (Pt.printWithAddedIndent "%s;") Pt.printl "}" - - GetCallbackFuncsByFlavor flavor - |> Array.filter (fun cb -> flavor <> Worker || knownWorkerInterfaces.Contains cb.Name) - |> Array.iter DumpCallBackFunction - -let DumpEnums () = - let DumpEnum (e: Browser.Enum) = - Pt.printl "declare var %s: string;" e.Name - browser.Enums |> Array.iter DumpEnum - -/// Dump the properties and methods of a given interface -let DumpMembers flavor prefix (dumpScope: DumpScope) (i:Browser.Interface) = - // -------- Dump properties -------- - // Note: the schema file shows the property doesn't have static attribute - let dumpProperty (p: Browser.Property) = + + let emitCallBackFunction (cb: Browser.CallbackFunction) = + if Option.isNone (findRemovedItem cb.Name ItemKind.Callback "")then + match findOverriddenItem cb.Name ItemKind.Callback "" with + | Some cb' -> emitCallbackFunctionsFromJson cb' + | _ -> + Pt.printl "interface %s {" cb.Name + let overloads = GetOverloads (CallBackFun cb) false + for { ParamCombinations = pCombList } in overloads do + let paramsString = ParamsToString pCombList + Pt.printWithAddedIndent "(%s): %s;" paramsString (DomTypeToTsType cb.Type) + Pt.printl "}" + + getAddedItems ItemKind.Callback flavor + |> Array.iter emitCallbackFunctionsFromJson + + GetCallbackFuncsByFlavor flavor |> Array.iter emitCallBackFunction + +let EmitEnums () = + let emitEnum (e: Browser.Enum) = Pt.printl "declare var %s: string;" e.Name + browser.Enums |> Array.iter emitEnum + +let EmitProperties flavor prefix (emitScope: EmitScope) (i: Browser.Interface)= + let emitPropertyFromJson (p: ItemsType.Root) = + Pt.printl "%s%s: %s;" prefix p.Name.Value p.Type.Value + + let emitProperty (p: Browser.Property) = match GetCommentForProperty i.Name p.Name with | Some comment -> Pt.printl "%s" comment | _ -> () - match findRemovedType p.Name i.Name MemberKind.Property with - | Some _ -> () - | None -> - match findOverridingType p.Name i.Name MemberKind.Property with - | Some t -> Pt.printl "%s%s: %s;" prefix t.Name t.Type.Value + if Option.isNone (findRemovedItem p.Name ItemKind.Property i.Name) then + match findOverriddenItem p.Name ItemKind.Property i.Name with + | Some p' -> emitPropertyFromJson p' | None -> - let pType = match p.Type with + let pType = + match p.Type with | "EventHandler" -> String.Format("(ev: {0}) => any", ehNameToEType.[p.Name]) | _ -> DomTypeToTsType p.Type Pt.printl "%s%s: %s;" prefix p.Name pType - if dumpScope <> DumpScope.StaticOnly then + // Note: the schema file shows the property doesn't have "static" attribute, + // therefore all properties are emited for the instance type. + if emitScope <> StaticOnly then match i.Properties with | Some ps -> ps.Properties |> Array.filter (ShouldKeep flavor) - |> Array.iter dumpProperty + |> Array.iter emitProperty | None -> () - addedTypes - |> Array.filter (fun t -> t.Kind = "property" && (t.Interface.IsNone || t.Interface.Value = i.Name)) - |> Array.iter (fun t -> Pt.printl "%s%s: %s;" prefix t.Name t.Type.Value) + getAddedItems ItemKind.Property flavor + |> Array.filter (matchInterface i.Name) + |> Array.iter emitPropertyFromJson - // -------- Dump methods -------- +let EmitMethods flavor prefix (emitScope: EmitScope) (i: Browser.Interface) = // Note: two cases: - // 1. dump the members inside a interface -> no need to add prefix - // 2. dump the members outside to expose them (for "Window") -> need to add "declare" - let methodPrefix = - match prefix with - | pf when pf.StartsWith("declare var") -> "declare function " - | _ -> "" + // 1. emit the members inside a interface -> no need to add prefix + // 2. emit the members outside to expose them (for "Window") -> need to add "declare" + 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 @@ -272,45 +247,42 @@ let DumpMembers flavor prefix (dumpScope: DumpScope) (i:Browser.Interface) = not iNameToEhList.[i.Name].IsEmpty let mFilter (m:Browser.Method) = - (match dumpScope with - | StaticOnly -> m.Static.IsSome - | InstanceOnly -> m.Static.IsNone - | All -> true) - && + matchScope emitScope m && not (hasEventHandlers && OptionCheckValue "addEventListener" m.Name) - match i.Methods with - | Some ms -> - ms.Methods - |> Array.filter mFilter - |> Array.iter (DumpMethod flavor methodPrefix i) - | _ -> () + if i.Methods.IsSome then + i.Methods.Value.Methods + |> Array.filter mFilter + |> Array.iter (EmitMethod flavor prefix i) + + getAddedItems ItemKind.Method flavor + |> Array.filter (fun m -> matchInterface i.Name m && matchScope emitScope m) + |> Array.iter emitMethodFromJson // The window interface inherited some methods from "Object", // which need to explicitly exposed - if i.Name = "Window" && methodPrefix = "declare function " then - Pt.printl "%stoString(): string;" methodPrefix + if i.Name = "Window" && prefix = "declare function " then + Pt.printl "%stoString(): string;" prefix - // Issue4401: - // Add "getElementsByClassName" to Element - if i.Name = "Element" && dumpScope <> DumpScope.StaticOnly then - Pt.printl "%sgetElementsByClassName(classNames: string): NodeListOf;" methodPrefix +/// Emit the properties and methods of a given interface +let EmitMembers flavor (prefix: string) (emitScope: EmitScope) (i:Browser.Interface) = + EmitProperties flavor prefix emitScope i + let methodPrefix = if prefix.StartsWith("declare var") then "declare function " else "" + EmitMethods flavor methodPrefix emitScope i -/// Dump all members of every interfaces at the root level. +/// Emit all members of every interfaces at the root level. /// Called only once on the global polluter object -let rec DumpAllMembers flavor (i:Browser.Interface) = +let rec EmitAllMembers flavor (i:Browser.Interface) = let prefix = "declare var " - DumpMembers flavor prefix DumpScope.All i + EmitMembers flavor prefix EmitScope.All i - iNameToIDependList.[i.Name] - |> List.iter - (fun relatedIName -> - match GetInterfaceByName relatedIName with - | Some i' -> DumpAllMembers flavor i' - | _ -> ()) + for relatedIName in iNameToIDependList.[i.Name] do + match GetInterfaceByName relatedIName with + | Some i' -> EmitAllMembers flavor i' + | _ -> () -let DumpEventHandlers (prefix: string) (i:Browser.Interface) = - let DumpEventHandler prefix (eHandler: EventHandler) = +let EmitEventHandlers (prefix: string) (i:Browser.Interface) = + let emitEventHandler prefix (eHandler: EventHandler) = let actualEventType = match i.Name, eHandler.EventName with | "IDBDatabase", "abort" @@ -332,16 +304,16 @@ let DumpEventHandlers (prefix: string) (i:Browser.Interface) = // 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 (DumpEventHandler fPrefix) + 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 (fun iName -> iNameToEhList.ContainsKey iName) |> Array.length with + 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 @@ -349,54 +321,44 @@ let DumpEventHandlers (prefix: string) (i:Browser.Interface) = if shouldPrintAddEventListener then Pt.printl "%saddEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void;" fPrefix -let DumpConstructorSignature (i:Browser.Interface) = - //Dump constructor signature - match i.Name, i.Constructor with - // HOTFIX: The spec is not looking correct regarding the 'Blob' constructor. Filed a bug - // and waiting for the IE team to respond - | "Blob", _ -> - Pt.printl "new (blobParts?: any[], options?: BlobPropertyBag): Blob;" - | "FormData", _ -> - Pt.printl "new (form?: HTMLFormElement): FormData;" - | "MessageEvent", _ -> - Pt.printl "new(type: string, eventInitDict?: MessageEventInit): MessageEvent;" - | "ProgressEvent", _ -> - Pt.printl "new(type: string, eventInitDict?: ProgressEventInit): ProgressEvent;" - | "File", _ -> - Pt.printl "new (parts: (ArrayBuffer | ArrayBufferView | Blob | string)[], filename: string, properties?: FilePropertyBag): File;" - | _, Some ctor -> - for { ParamCombinations = pCombList } in GetOverloads (Ctor ctor) false do - let paramsString = ParamsToString pCombList - Pt.printl "new(%s): %s;" paramsString i.Name - | _ -> Pt.printl "new(): %s;" i.Name - -let DumpConstructor flavor (i:Browser.Interface) = - match i.Name with - | "ImageData" -> - Pt.printl "interface ImageDataConstructor {" - Pt.printWithAddedIndent "prototype: ImageData;" - Pt.printWithAddedIndent "new(width: number, height: number): ImageData;" - Pt.printWithAddedIndent "new(array: Uint8ClampedArray, width: number, height: number): ImageData;" - Pt.printl "}" - Pt.printl "" - Pt.printl "declare var ImageData: ImageDataConstructor; " - Pt.printl "" - | _ -> - Pt.printl "declare var %s: {" i.Name - Pt.increaseIndent() +let EmitConstructorSignature (i:Browser.Interface) = + let emitConstructorSigFromJson (c: ItemsType.Root) = + c.Signatures |> Array.iter (Pt.printl "%s;") + + let removedCtor = getRemovedItems ItemKind.Constructor Flavor.All |> Array.tryFind (matchInterface i.Name) + if Option.isNone removedCtor then + let overriddenCtor = getOverriddenItems ItemKind.Constructor Flavor.All |> Array.tryFind (matchInterface i.Name) + match overriddenCtor with + | Some c' -> emitConstructorSigFromJson c' + | _ -> + //Emit constructor signature + match i.Constructor with + | Some ctor -> + for { ParamCombinations = pCombList } in GetOverloads (Ctor ctor) false do + let paramsString = ParamsToString pCombList + Pt.printl "new(%s): %s;" paramsString i.Name + | _ -> Pt.printl "new(): %s;" i.Name + + getAddedItems ItemKind.Constructor Flavor.All + |> Array.filter (matchInterface i.Name) + |> Array.iter emitConstructorSigFromJson + +let EmitConstructor flavor (i:Browser.Interface) = + Pt.printl "declare var %s: {" i.Name + Pt.increaseIndent() - Pt.printl "prototype: %s;" i.Name - DumpConstructorSignature i - DumpConstants i - let prefix = "" - DumpMembers flavor prefix DumpScope.StaticOnly i + Pt.printl "prototype: %s;" i.Name + EmitConstructorSignature i + EmitConstants i + let prefix = "" + EmitMembers flavor prefix EmitScope.StaticOnly i - Pt.decreaseIndent() - Pt.printl "}" - Pt.printl "" + Pt.decreaseIndent() + Pt.printl "}" + Pt.printl "" -/// Dump all the named constructors at root level -let DumpNamedConstructors () = +/// Emit all the named constructors at root level +let EmitNamedConstructors () = browser.Interfaces |> Array.filter (fun i -> i.NamedConstructor.IsSome) |> Array.iter @@ -407,14 +369,15 @@ let DumpNamedConstructors () = yield {Type = p.Type; Name = p.Name; Optional = p.Optional.IsSome; Variadic = p.Variadic.IsSome}] Pt.printl "declare var %s: {new(%s): %s; };" nc.Name (ParamsToString ncParams) i.Name) -let DumpInterfaceDeclaration (i:Browser.Interface) = +let EmitInterfaceDeclaration (i:Browser.Interface) = Pt.printl "interface %s" i.Name match i.Extends::(List.ofArray i.Implements) with | [""] | [] | ["Object"] -> () | allExtends -> Pt.print " extends %s" (String.Join(", ", allExtends)) Pt.print " {" -let ShouldDumpIndexerSignature (i: Browser.Interface) (m: Browser.Method) = +/// To decide if a given method is an indexer and should be emited +let ShouldEmitIndexerSignature (i: Browser.Interface) (m: Browser.Method) = if m.Getter.IsSome && m.Params.Length = 1 then // TypeScript array indexer can only be number or string // for string, it must return a more generic type then all @@ -452,121 +415,140 @@ let ShouldDumpIndexerSignature (i: Browser.Interface) (m: Browser.Method) = else false -let DumpInterface flavor (i:Browser.Interface) = - Pt.resetIndent() - DumpInterfaceDeclaration i - Pt.increaseIndent () +let EmitIndexers emitScope (i: Browser.Interface) = + let emitIndexerFromJson (id: ItemsType.Root) = + id.Signatures |> Array.iter (Pt.printl "%s;") - let prefix = "" - DumpMembers flavor prefix DumpScope.InstanceOnly i - DumpConstants i - DumpEventHandlers prefix i - - let dumpIndexers (ms:Browser.Method []) = - ms - |> Array.filter (ShouldDumpIndexerSignature i) - |> Array.iter - (fun m -> + let removedIndexer = getRemovedItems ItemKind.Indexer Flavor.All |> Array.tryFind (matchInterface i.Name) + if removedIndexer.IsNone then + let overriddenIndexer = getOverriddenItems ItemKind.Indexer Flavor.All |> Array.tryFind (matchInterface i.Name) + match overriddenIndexer with + | Some id -> emitIndexerFromJson id + | _ -> + // The indices could be within either Methods or Anonymous Methods + let ms = if i.Methods.IsSome then i.Methods.Value.Methods else [||] + let ams = if i.AnonymousMethods.IsSome then i.AnonymousMethods.Value.Methods else [||] + + Array.concat [|ms; ams|] + |> Array.filter (fun m -> ShouldEmitIndexerSignature i m && matchScope emitScope m) + |> Array.iter (fun m -> let indexer = m.Params.[0] Pt.printl "[%s: %s]: %s;" indexer.Name (DomTypeToTsType indexer.Type) - (DomTypeToTsType m.Type)) + (DomTypeToTsType m.Type)) - // The indices could be within either Methods or Anonymous Methods - match i.Methods with - | Some ms -> - ms.Methods |> dumpIndexers - if i.Name = "HTMLCollection" then - Pt.printl "[index: number]: Element;" - | None -> () + getAddedItems ItemKind.Indexer Flavor.All + |> Array.filter (matchInterface i.Name) + |> Array.iter emitIndexerFromJson - match i.AnonymousMethods with - | Some ms -> ms.Methods |> dumpIndexers - | None -> () +let EmitInterface flavor (i:Browser.Interface) = + Pt.resetIndent() + EmitInterfaceDeclaration i + Pt.increaseIndent() + + let prefix = "" + EmitMembers flavor prefix EmitScope.InstanceOnly i + EmitConstants i + EmitEventHandlers prefix i + EmitIndexers EmitScope.InstanceOnly i Pt.decreaseIndent() Pt.printl "}" Pt.printl "" -let DumpStaticInterface flavor (i:Browser.Interface) = - Pt.resetIndent() - DumpInterfaceDeclaration i - Pt.increaseIndent () +let EmitStaticInterface flavor (i:Browser.Interface) = + // Some types are static types with non-static members. For example, + // NodeFilter is a static method itself, however it has an "acceptNode" method + // that expects the user to implement. + let hasNonStaticMember = + match i.Methods with + | Some ms -> ms.Methods |> Array.exists (fun m -> m.Static.IsNone) + | _ -> false - let prefix = "" - DumpMembers flavor prefix DumpScope.StaticOnly i - DumpConstants i - DumpEventHandlers prefix i - - let dumpIndexers (ms:Browser.Method []) = - ms - |> Array.filter (fun m -> m.Getter.IsSome) - |> Array.iter - (fun m -> - let indexer = m.Params.[0] - Pt.printl "[%s: %s]: %s;" - indexer.Name - (DomTypeToTsType indexer.Type) - (DomTypeToTsType indexer.Type)) + // For static types with non-static members, we put the non-static members into an + // interface, and put the static members into the object literal type of 'declare var' + // For static types with only static members, we put everything in the interface. + // Because in the two cases the interface contains different things, it might be easier to + // read to seperate them into two functions. + let emitStaticInterfaceWithNonStaticMembers () = + Pt.resetIndent() + EmitInterfaceDeclaration i + Pt.increaseIndent() + + let prefix = "" + EmitMembers flavor prefix EmitScope.InstanceOnly i + EmitEventHandlers prefix i + EmitIndexers EmitScope.InstanceOnly i + + Pt.decreaseIndent() + Pt.printl "}" + Pt.printl "" + Pt.printl "declare var %s: {" i.Name + Pt.increaseIndent() + EmitConstants i + EmitMembers flavor prefix EmitScope.StaticOnly i + Pt.decreaseIndent() + Pt.printl "}" + Pt.printl "" - // The indices could be within either Methods or Anonymous Methods - match i.Methods with - | Some ms -> ms.Methods |> dumpIndexers - | None -> () + let emitPureStaticInterface () = + Pt.resetIndent() + EmitInterfaceDeclaration i + Pt.increaseIndent() - match i.AnonymousMethods with - | Some ms -> ms.Methods |> dumpIndexers - | None -> () + let prefix = "" + EmitMembers flavor prefix EmitScope.StaticOnly i + EmitConstants i + EmitEventHandlers prefix i + EmitIndexers EmitScope.StaticOnly i - Pt.decreaseIndent() - Pt.printl "}" - Pt.printl "declare var %s: %s;" i.Name i.Name - Pt.printl "" + Pt.decreaseIndent() + Pt.printl "}" + Pt.printl "declare var %s: %s;" i.Name i.Name + Pt.printl "" -let DumpNonCallbackInterfaces flavor = - GetNonCallbackInterfacesByFlavor flavor - |> Array.iter - (fun i -> match i with - // Static attribute means singleton object - | i when i.Static.IsSome -> - DumpStaticInterface flavor i - | i when i.NoInterfaceObject.IsSome -> - DumpInterface flavor i - | _ -> - DumpInterface flavor i - DumpConstructor flavor i) - -let DumpDictionaries flavor = - let DumpDictionary (dict:Browser.Dictionary) = + if hasNonStaticMember then emitStaticInterfaceWithNonStaticMembers() else emitPureStaticInterface() + +let EmitNonCallbackInterfaces flavor = + for i in GetNonCallbackInterfacesByFlavor flavor do + // If the static attribute has a value, it means the type doesn't have a constructor + if i.Static.IsSome then + EmitStaticInterface flavor i + elif i.NoInterfaceObject.IsSome then + EmitInterface flavor i + else + EmitInterface flavor i + EmitConstructor flavor i + +let EmitDictionaries flavor = + let emitDictionary (dict:Browser.Dictionary) = match dict.Extends with | "Object" -> Pt.printl "interface %s {" dict.Name | _ -> Pt.printl "interface %s extends %s {" dict.Name dict.Extends Pt.increaseIndent() - - dict.Members - |> Array.iter (fun m -> Pt.printl "%s?: %s;" m.Name (DomTypeToTsType m.Type)) - + dict.Members |> Array.iter (fun m -> Pt.printl "%s?: %s;" m.Name (DomTypeToTsType m.Type)) Pt.decreaseIndent() Pt.printl "}" Pt.printl "" + browser.Dictionaries |> Array.filter (fun dict -> flavor <> Worker || knownWorkerInterfaces.Contains dict.Name) - |> Array.iter DumpDictionary + |> Array.iter emitDictionary -let DumpAddedInterface (t: TypesFromJsonFile.Root) = - match t.Extends with - | Some e -> Pt.printl "interface %s extends %s {" t.Name t.Extends.Value - | None -> Pt.printl "interface %s {" t.Name +let EmitAddedInterface (ai: JsonItems.ItemsType.Root) = + match ai.Extends with + | Some e -> Pt.printl "interface %s extends %s {" ai.Name.Value ai.Extends.Value + | None -> Pt.printl "interface %s {" ai.Name.Value - t.Properties |> Array.iter (fun p -> Pt.printWithAddedIndent "%s: %s;" p.Name p.Type) - t.Methods |> Array.collect (fun m -> m.Signatures) |> Array.iter (Pt.printWithAddedIndent "%s;") - t.Indexer |> Array.collect (fun i -> i.Signatures) |> Array.iter (Pt.printWithAddedIndent "%s;") + ai.Properties |> Array.iter (fun p -> Pt.printWithAddedIndent "%s: %s;" p.Name p.Type) + ai.Methods |> Array.collect (fun m -> m.Signatures) |> Array.iter (Pt.printWithAddedIndent "%s;") + ai.Indexer |> Array.collect (fun i -> i.Signatures) |> Array.iter (Pt.printWithAddedIndent "%s;") Pt.printl "}" Pt.printl "" -let DumpTheWholeThing flavor (target:TextWriter) = +let EmitTheWholeThing flavor (target:TextWriter) = Pt.reset() Pt.printl "/////////////////////////////" match flavor with @@ -575,33 +557,33 @@ let DumpTheWholeThing flavor (target:TextWriter) = Pt.printl "/////////////////////////////" Pt.printl "" - DumpDictionaries flavor - DumpCallBackInterface browser.CallbackInterfaces.Interface - DumpNonCallbackInterfaces flavor + EmitDictionaries flavor + EmitCallBackInterface browser.CallbackInterfaces.Interface + EmitNonCallbackInterfaces flavor // Add missed interface definition from the spec - getAllAddedInterfaces flavor |> Array.iter DumpAddedInterface + JsonItems.getAddedItems JsonItems.Interface flavor |> Array.iter EmitAddedInterface Pt.printl "declare type EventListenerOrEventListenerObject = EventListener | EventListenerObject;" Pt.printl "" - DumpCallBackFunctions flavor + EmitCallBackFunctions flavor if flavor <> Worker then - DumpNamedConstructors() + EmitNamedConstructors() match GetGlobalPollutor flavor with | Some gp -> - DumpAllMembers flavor gp - DumpEventHandlers "declare var " gp + EmitAllMembers flavor gp + EmitEventHandlers "declare var " gp | _ -> () fprintf target "%s" (Pt.getResult()) target.Flush() -let DumpDomWeb () = - DumpTheWholeThing Windows GlobalVars.tsWebOutput +let EmitDomWeb () = + EmitTheWholeThing Flavor.All GlobalVars.tsWebOutput -let DumpDomWorker () = +let EmitDomWorker () = ignoreDOMTypes <- true - DumpTheWholeThing Worker GlobalVars.tsWorkerOutput \ No newline at end of file + EmitTheWholeThing Flavor.Worker GlobalVars.tsWorkerOutput \ No newline at end of file diff --git a/inputfiles/addedTypes.json b/inputfiles/addedTypes.json index 5dbb114a2..1532a23da 100644 --- a/inputfiles/addedTypes.json +++ b/inputfiles/addedTypes.json @@ -133,5 +133,15 @@ "type": "number" } ] + }, + { + "kind": "method", + "interface": "Element", + "signatures": ["getElementsByClassName(classNames: string): NodeListOf"] + }, + { + "kind": "indexer", + "interface": "HTMLCollection", + "signatures": ["[index: number]: Element"] } ] diff --git a/inputfiles/overridingTypes.json b/inputfiles/overridingTypes.json index ba1d2dadf..b7e03a055 100644 --- a/inputfiles/overridingTypes.json +++ b/inputfiles/overridingTypes.json @@ -131,5 +131,79 @@ "interface": "Window", "name": "orientation", "type": "string | number" + }, + { + "kind": "method", + "interface": "Console", + "name": "dir", + "signatures": ["dir(value?: any, ...optionalParams: any[]): void"] + }, + { + "kind": "method", + "interface": "Console", + "name": "dirxml", + "signatures": ["dirxml(value: any): void"] + }, + { + "kind": "method", + "interface": "Console", + "name": "error", + "signatures": ["error(message?: any, ...optionalParams: any[]): void"] + }, + { + "kind": "method", + "interface": "Console", + "name": "info", + "signatures": ["info(message?: any, ...optionalParams: any[]): void"] + }, + { + "kind": "method", + "interface": "Console", + "name": "log", + "signatures": ["log(message?: any, ...optionalParams: any[]): void"] + }, + { + "kind": "method", + "interface": "Console", + "name": "warn", + "signatures": ["warn(message?: any, ...optionalParams: any[]): void"] + }, + { + "kind": "callback", + "name": "ErrorEventHandler", + "signatures": ["(message: string, filename?: string, lineno?: number, colno?: number, error?:Error): void"] + }, + { + "kind": "constructor", + "interface": "Blob", + "signatures": ["new (blobParts?: any[], options?: BlobPropertyBag): Blob"] + }, + { + "kind": "constructor", + "interface": "FormData", + "signatures": ["new (form?: HTMLFormElement): FormData"] + }, + { + "kind": "constructor", + "interface": "MessageEvent", + "signatures": ["new(type: string, eventInitDict?: MessageEventInit): MessageEvent"] + }, + { + "kind": "constructor", + "interface": "ProgressEvent", + "signatures": ["new(type: string, eventInitDict?: ProgressEventInit): ProgressEvent"] + }, + { + "kind": "constructor", + "interface": "File", + "signatures": ["new (parts: (ArrayBuffer | ArrayBufferView | Blob | string)[], filename: string, properties?: FilePropertyBag): File"] + }, + { + "kind": "constructor", + "interface": "ImageData", + "signatures": [ + "new(width: number, height: number): ImageData", + "new(array: Uint8ClampedArray, width: number, height: number): ImageData" + ] } ] diff --git a/inputfiles/sample.json b/inputfiles/sample.json index bd81059fc..c461fa174 100644 --- a/inputfiles/sample.json +++ b/inputfiles/sample.json @@ -299,5 +299,21 @@ "interface": "Window", "name": "orientation", "type": "string | number" + }, + { + "kind": "method", + "interface": "Element", + "static": true, + "signatures": ["getElementsByClassName(classNames: string): NodeListOf"] + }, + { + "kind": "constructor", + "interface": "File", + "signatures": ["new (parts: (ArrayBuffer | ArrayBufferView | Blob | string)[], filename: string, properties?: FilePropertyBag): File"] + }, + { + "kind": "indexer", + "interface": "HTMLCollection", + "signatures": ["[index: number]: Element"] } ]