Skip to content

Refactor to make sending PR easier #2

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 5 commits into from
Oct 22, 2015
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
150 changes: 75 additions & 75 deletions JS.fs

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions Program.fs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@

[<EntryPoint>]
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
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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.
187 changes: 117 additions & 70 deletions Shared.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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<Flavor>) 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) =
Expand All @@ -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()

Expand Down Expand Up @@ -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
/// ===========================================
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 |]
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -374,15 +410,15 @@ 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
| _ -> () ]
|> List.map (fun e -> e.Type)
|> 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))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Loading