用 F# 手写 TypeScript 转 C# 类型绑定生成器

用 F# 手写 TypeScript 转 C# 类型绑定生成器,第1张

概述使用 F# 手写一个 Typedoc 转 C# 代码生成器,方便一切 C# 项目对 TypeScript 项目的封装。 前言

我们经常会遇到这样的事情:有时候我们找到了一个库,但是这个库是用 TypeScript 写的,但是我们想在 C# 调用,于是我们需要设法将原来的 TypeScript 类型声明翻译成 C# 的代码,然后如果是 UI 组件的话,我们需要将其封装到一个 WebVIEw 里面,然后通过 JavaScript 和 C# 的互 *** 作功能来调用该组件的各种方法,支持该组件的各种事件等等。

但是这是一个苦力活,尤其是类型翻译这一步。

这个是我最近在帮助维护一个开源 UWP 项目 monaco-editor-uwp 所需要的,该项目将微软的 monaco 编辑器封装成了 UWP 组件。

然而它的 monaco.d.ts 足足有 1.5 mb,并且 API 经常会变化,如果人工翻译,不仅工作量十分大,还可能会漏掉新的变化,但是如果有一个自动生成器的话,那么人工的工作就会少很多。

目前 GitHub 上面有一个叫做 QuickType 的项目,但是这个项目对 TypeScript 的支持极其有限,仍然停留在 TypeScript 3.2,而且遇到不认识的类型就会报错,比如 DOM 类型等等。

因此我决定手写一个代码生成器 TypedocConverter:https://github.com/hez2010/TypedocConverter

构思

本来是打算从 TypeScript 词法和语义分析开始做的,但是发现有一个叫做 Typedoc 的项目已经帮我们完成了这一步,而且支持输出 JsON schema,那么剩下的事情就简单了:我们只需要将 TypeScript 的 AST 转换成 C# 的 AST,然后再将 AST 还原成代码即可。

那么话不多说,这就开写。

构建 Typescipt AST 类型绑定

借助于 F# 更加强大的类型系统,类型的声明和使用非常简单,并且具有完善的recursive pattern。pattern matching、option types 等支持,这也是该项目选用 F# 而不是 C# 的原因,虽然 C# 也支持这些,也有一定的 FP 能力,但是它还是偏 OOP,写起来会有很多的样板代码,非常的繁琐。

我们将 Typescipt 的类型绑定定义到 DeFinition.fs 中,这一步直接将 Typedoc 的定义翻译到 F# 即可:

首先是 ReflectionKind 枚举,该枚举表示了 JsON Schema 中各节点的类型:

type ReflectionKind = | Global = 0| ExternalModule = 1| Module = 2| Enum = 4| EnumMember = 16| Variable = 32| Function = 64| Class = 128| Interface = 256| Constructor = 512| Property = 1024| Method = 2048| CallSignature = 4096| IndexSignature = 8192| ConstructorSignature = 16384| Parameter = 32768| Typeliteral = 65536| TypeParameter = 131072| Accessor = 262144| GetSignature = 524288| SetSignature = 1048576| Objectliteral = 2097152| TypeAlias = 4194304| Event = 8388608| Reference = 16777216

 

然后是类型修饰标志 ReflectionFlags,注意该 record 所有的成员都是 option 的

 ReflectionFlags = {    IsPrivate: bool option    IsProtected:  option    IsPublic:  option    Isstatic:  option    IsExported:  option    IsExternal:  option    IsOptional:  option    Isreset:  option    HasExportAssignment:  option    IsConstructorProperty:  option    IsAbstract:  option    IsConst:  option    IsLet:  option}

 

然后到了我们的 Reflection,由于每一种类型的 Reflection 都可以由 ReflectionKind 来区分,因此我选择将所有类型的 Reflection 合并成为一个 record,而不是采用 Union Types,因为后者虽然看上去清晰,但是在实际 parse AST 的时候会需要大量 pattern matching 的代码。

由于部分 records 相互引用,因此我们使用 and 来定义 recursive records。

 Reflection = {    ID: int    name: string    Originalname:     Kind: ReflectionKind    KindString:  option    Flags: ReflectionFlags    Parent: Reflection option    Comment: Comment option    Sources: SourceReference List option    Decorators: Decorator option    Decorates: Type List option    Url:  option    Anchor:  option    HasOwndocument:  option    CSSClasses:  option    DefaultValue:  option    Type: Type option    TypeParameter: Reflection List option    Signatures: Reflection List option    IndexSignature: Reflection List option    GetSignature: Reflection List option    SetSignature: Reflection List option    Overwrites: Type option    inheritedFrom: Type option    ImplementationOf: Type option    ExtendedTypes: Type List option    ExtendedBy: Type List option    ImplementedTypes: Type List option    ImplementedBy: Type List option    TypeHIErarchy: DeclarationHIErarchy option    Children: Reflection List option    Groups: ReflectionGroup List option    CategorIEs: Reflectioncategory List option    Reflections: Map<,Reflection> option    Directory: SourceDirectory option    files: Sourcefile List option    Readme:  option    PackageInfo: obj option    Parameters: Reflection List option}and DeclarationHIErarchy = {    Type: Type List    Next: DeclarationHIErarchy option    IsTarget:  option} Type = {    Type:     ID:  option    name:  option    ElementType: Type option    Value:  option    Types: Type List option    TypeArguments: Type List option    Constraint: Type option    Declaration: Reflection option} Decorator = {    name:     Type: Type option    Arguments: obj option} ReflectionGroup = {    Title:     Kind: ReflectionKind    Children:  List    CSSClasses:  option    AllChildrenHaveOwndocument:  option    AllChildrenAreinherited:  option    AllChildrenArePrivate:  option    AllChildrenAreProtectedOrPrivate:  option    AllChildrenAreExternal:  option    SomeChildrenAreExported:  option    CategorIEs: Reflectioncategory List option} Reflectioncategory = {    Title:     Children:  List    AllChildrenHaveOwndocument:  SourceDirectory = {    Parent: SourceDirectory option    DirectorIEs: Map< option    Dirname:  option    Url:  Sourcefile = {    Fullfilename:     filename:     Url:  option    Parent: SourceDirectory option    Reflections: Reflection List option    Groups: ReflectionGroup List option} SourceReference = {    file: Sourcefile option    filename:     line:     Character:  Comment = {    ShortText:     Text:  option    Returns:  option    Tags: CommentTag List option} CommentTag = {    Tagname:     Parentname: }

 

这样,我们就简单的完成了类型绑定的翻译,接下来要做的就是将 Typedoc 生成的 JsON 反序列化成我们所需要的东西即可。

反序列化

虽然想着好像一切都很顺利,但是实际上 System.Text.Json、Newtonsoft.JsON 等均不支持 F# 的 option types,所需我们还需要一个 JsonConverter 处理 option types。

本项目采用 Newtonsoft.Json,因为 System.Text.Json 目前尚不成熟。得益于 F# 对 OOP 的兼容,我们可以很容易的实现一个 OptionConverter

 OptionConverter() =    inherit JsonConverter()    overrIDe __.CanConvert(objectType: Type) :  =         match objectType.IsGenericType with        | false -> false        | true -> typedefof<_ option> = objectType.GetGenericTypeDeFinition()    overrIDe __.WriteJson(writer: JsonWriter,value: obj,serializer: JsonSerializer) : unit =         serializer.Serialize(writer,if isNull value then null            else let _,fIElds = FSharpValue.GetUnionFIElds(value,value.GetType())                 fIElds.[0]        )     __.ReadJson(reader: JsonReader,objectType: Type,_existingValue: obj,serializer: JsonSerializer) : obj =         let innerType = objectType.GetGenericArguments().[]         value =             serializer.Deserialize(                reader,1)">if innerType.IsValueType                 then (typedefof<_ Nullable>).MakeGenericType([|innerType|])                else innerType        )         cases = FSharpType.GetUnionCases objectType        then FSharpValue.MakeUnion(cases.[0],[||])        else FSharpValue.MakeUnion(cases.[1],[|value|])

 

这样所有的工作就完成了。

我们可以去 monaco-editor 仓库下载 monaco.d.ts 测试一下我们的 JsON Schema deserializer,可以发现 JsON Sechma 都被正确地反序列化了。

反序列化结果

 

构建 C# AST 类型

当然,此 "AST" 非彼 AST,我们没有必要其细化到语句层面,因为我们只是要写一个简单的代码生成器,我们只需要构建实体结构即可。

我们将实体结构定义到 Entity.fs 中,在此我们只需支持 interface、class、enum 即可,对于 class 和 interface,我们只需要支持 method、property 和 event 就足够了。

当然,代码中存在泛型的可能,这一点我们也需要考虑。

 EntityBodyType = {    Type:  option    InnerTypes: EntityBodyType List} EntityMethod = {    Comment:     ModifIEr:  List    Type: EntityBodyType    name:     TypeParameter:  List    Parameter: EntityBodyType List} EntityProperty = {    Comment:  List    name:     Type: EntityBodyType    WithGet:     WithSet:     IsOptional:     InitialValue:  option} EntityEvent = {    Comment:  List    DelegateType: EntityBodyType    name: } EntityEnum = {    Comment:     Value: int64 option} EntityType = | Interface| Class| Enum| StringEnum Entity = {    namespace:     Comment:     Methods: EntityMethod List    PropertIEs: EntityProperty List    Events: EntityEvent List    Enums: EntityEnum List    inheritedFrom: EntityBodyType List    Type: EntityType    TypeParameter:  List    ModifIEr:  List}

 

文档化注释生成器

文档化注释也是少不了的东西,能极大方便开发者后续使用生成的类型绑定,而无需参照原 typescript 类型声明上的注释。

代码很简单,只需要将文本处理成 xml 即可。

let escapeSymbols (text: ) =     if isNull text then ""     text            .Replace("&",&amp;")            .Replace(<&lt;>&gt;)let toCommentText (text: else text.Split \n" |> Array.map (fun t -> /// " + escapeSymbols t) |> Array.reduce(fun accu next -> accu +  + next) getXmlDocComment (comment: Comment) =    let prefix = /// <summary>\n"    let suffix = \n/// </summary> summary =         match comment.Text with        | Some text -> prefix + toCommentText comment.ShortText + toCommentText text + suffix        | _ ->             match comment.ShortText with            | "" -> ""            | _ -> prefix + toCommentText comment.ShortText + suffix     returns =         match comment.Returns with        | Some text -> \n/// <returns>\n" + toCommentText text + \n/// </returns>"        | _ -> ""    summary + returns

 

类型生成器

Typescript 的类型系统较为灵活,包括 union types、intersect types 等等,这些即使是目前的 C# 8 都不能直接表达,需要等到 C# 9 才行。当然我们可以生成一个 struct 并为其编写隐式转换 *** 作符重载,支持 union types,但是目前尚未实现,我们就先用 union types 中的第一个类型代替,而对于 intersect types,我们姑且先使用 object。

然而 union types 有一个特殊情况:string literals types alias。就是这样的东西:

type Size = "XS" | "S" | "M" | "L" | "XL";

即纯 string 值组合的 type alias,这个我们还是有必要支持的,因为在 typescript 中用的非常广泛。

C# 在没有对应语法的时候要怎么支持呢?很简单,我们创建一个 enum,该 enum 包含该类型中的所有元素,然后我们为其编写 JsonConverter,这样就能确保序列化后,typescript 方能正确识别类型,而在 C# 又有 type sound 的编码体验。

另外,我们需要提供一些常用的类型转换:

Array<T> -> T[] Set<T> -> System.Collections.Generic.ISet<T>Map<T> -> System.Collections.Generic.IDictionary<T> Promise<T> -> System.Threading.Tasks.Task<T> callbacks -> System.Func<T...>System.Action<T...> Tuple 类型其他的数组类型如 Uint32Array 对于 <voID>,我们需要解除泛型,即 T<voID> -> T

那么实现如下:

let rec getType (typeInfo: Type): EntityBodyType =      genericType =        match typeInfo.Type with        | intrinsic" ->             match typeInfo.name with            | Some name ->                 match name with                | number" -> { Type = double; InnerTypes = []; name = None }                | booleanboolstringvoID; InnerTypes = []; name = None }                | _ -> { Type = object; InnerTypes = []; name = None }            | _ -> { Type = ; InnerTypes = []; name = None }        | reference" | typeParameterPromiseSystem.Threading.Tasks.TaskSetSystem.Collections.Generic.ISetMapSystem.Collections.Generic.IDictionaryArraySystem.ArrayBigUint64Array"; InnerTypes = [{ Type = ulong; InnerTypes = [ ]; name = None };]; name = None };                | Uint32ArrayuintUint16ArrayushortUint8ArraybyteBigInt64ArraylongInt32ArrayintInt16ArrayshortInt8ArraycharRegExp; InnerTypes = []; name = None };                | x -> { Type = x; InnerTypes = []; name = None };            | _ -> { Type = arraymatch typeInfo.ElementType with            | Some elementType -> { Type = ; InnerTypes = [getType elementType]; name = None }            | _ -> { Type = ; InnerTypes = []; name = None }]; name = None }        | stringliteraltuple" ->            match typeInfo.Types with            | Some innerTypes ->                 match innerTypes with                | [] -> { Type = System.ValueTuple"; InnerTypes = innerTypes |> List.map getType; name = None }            | _ -> { Type = union; InnerTypes = []; name = None }                | _ ->                     printWarning (Taking only the first type " + innerTypes.[0].Type +  for the entire union type.)                    getType innerTypes.[0] // Todo: generate unions| _ ->{ Type = intersection"; InnerTypes = []; name = None }  Todo: generate intersections| reflectionmatch typeInfo.Declaration with            | Some dec ->                 match dec.Signatures with                | Some [signature] ->                      paras =                         match signature.Parameters with                        | Some p ->                             p                             |> List.map                                (fun pi ->                                     match pi.Type with                                     | Some pt -> Some (getType pt)                                    | _ -> None                                )                            |> List.collect                                (fun x ->                                     match x with                                    | Some s -> [s]                                    | _ -> []                                )                        | _ -> []                     getDelegateParas (paras: EntityBodyType List): EntityBodyType List =                        match paras with                        | [x] -> [{ Type = x.Type; InnerTypes = x.InnerTypes; name = None }]                        | (front::tails) -> [front] @ getDelegateParas tails                        | _ -> returnsType =                         match signature.Type with                        | Some t -> getType t                        | _ -> { Type = ; InnerTypes = []; name = None }                     typeParas = getDelegateParas paras                    match typeParas with                    | [] -> { Type = System.Action; InnerTypes = []; name = None }                    | _ ->                         if returnsType.Type = "                         then { Type = ; InnerTypes = typeParas; name = None }                         else { Type = System.Func; InnerTypes = typeParas @ [returnsType]; name = None }                | _ -> { Type = ; InnerTypes = []; name = None }        | _ -> { Type = ; InnerTypes = []; name = None }    mutable innerTypes =         match typeInfo.TypeArguments with        | Some args -> getGenericTypeArguments args        | _ -> []    if genericType.Type = then         with        | (front::_) -> if front.Type = " then innerTypes <- []  ()        | _ -> ()     ()    {         Type = genericType.Type;         name = None;         InnerTypes = if innerTypes = [] then genericType.InnerTypes  innerTypes;     } getGenericTypeArguments (typeInfos: Type List): EntityBodyType List =     typeInfos |> List.map getTypeand getGenericTypeParameters (nodes: Reflection List) =  Todo: generate constaints types =         nodes         |> List.where(fun x -> x.Kind = ReflectionKind.TypeParameter)        |> List.map ( x.name)    types |> List.map (fun x -> {| Type = x; Constraint = "" |})

 

当然,目前尚不支持生成泛型约束,如果以后有时间的话会考虑添加。

修饰生成器

例如 publicprivateprotectedstatic 等等。这一步很简单,直接将 ReflectionFlags 转换一下即可,个人觉得使用 mutable 代码会让代码变得非常不优雅,但是有的时候还是需要用一下的,不然会极大地提高代码的复杂度。

 getModifIEr (flags: ReflectionFlags) =      modifIEr = []    match flags.IsPublic with    | Some flag -> if flag then modifIEr <- modifIEr |> List.append [ public" ]  ()    | _ ->match flags.IsAbstract abstractmatch flags.IsPrivate privatematch flags.IsProtected protectedmatch flags.Isstatic static ()    modifIEr

 

Enum 生成器

终于到 parse 实体的部分了,我们先从最简单的做起:枚举。 代码很简单,直接将原 AST 中的枚举部分转换一下即可。

let parseEnum (section: ) (node: Reflection): Entity =    let values = match node.Children with                 | Some children ->                     children                     |> List.where ( x.Kind = ReflectionKind.EnumMember)                 | None -> []    {         Type = EntityType.Enum;        namespace = if section = "" TypeDocgenerator section;        ModifIEr = getModifIEr node.Flags;        name = node.name        Comment =             match node.Comment with            | Some comment -> getXmlDocComment comment            | _ ->         Methods = []; PropertIEs = []; Events = []; inheritedFrom = [];        Enums = values |> List.map (fun x ->             comment =                 match x.Comment with                | Some comment -> getXmlDocComment comment                | _ -> ""            mutable intValue = 0L            match x.DefaultValue with             ?????            | Some value -> if Int64.TryParse(value,&intValue) then { Comment = comment; name = topascalCase x.name; Value = Some intValue; }                            match getEnumReferencedValue values value x.name with                                 | Some t -> { Comment = comment; name = x.name; Value = Some (int64 t); }                                 | _ -> { Comment = comment; name = x.name; Value = None; }            | _ -> { Comment = comment; name = x.name; Value = None; }        );        TypeParameter = []    }

 

你会注意到一个上面我有一处标了个 ?????,这是在干什么呢?

其实,TypeScript 的 enum 是 recursive 的,也就意味着定义的时候,一个元素可以引用另一个元素,比如这样:

enum MyEnum {    A = 1,B = 2,C = A}

这个时候,我们需要查找它引用的枚举值,比如在上面的例子里面,处理 C 的时候,需要将它的值 A 用真实值 1 代替。所以我们还需要一个查找函数:

 getEnumReferencedValue (nodes: Reflection List) value name =     match nodes           |> List.where(fun x ->               with              | Some v -> v <> value && not (name = x.name)              | _ -> true          )           |> List.where( x.name = value)          |> List.tryFind(fun x ->                             0                            with                            | Some y -> Int32.TryParse(y,&intValue)                            | _ ->            ) with    | Some t -> t.DefaultValue    | _ -> None

 

这样我们的 Enum parser 就完成了。

Interface 和 Class 生成器

下面到了重头戏,interface 和 class 才是类型绑定的关键。

我们的函数签名是这样的:

let parseInterfaceAndClass (section: string) (node: Reflection) (isInterface: bool): Entity = ...

 

首先我们从 Reflection 节点中查找并生成注释、修饰、名称、泛型参数、继承关系、方法、属性和事件:

 comment =     with    | Some comment -> getXmlDocComment comment    | _ -> "" exts =     (match node.ExtendedTypes with    | Some types -> types |> List.map( getType x)    | _ -> []) @    (match node.ImplementedTypes  []) genericType =     types =           match node.TypeParameter with          | Some tp -> Some (getGenericTypeParameters tp)          | _ -> None    match types with    | Some result -> result    | _ -> [] propertIEs =     with    | Some children ->         if isInterface             children             |> List.where( x.Kind = ReflectionKind.Property)            |> List.where(fun x -> x.inheritedFrom = None)  exclude inhreited propertIEs            |> List.where(fun x -> x.Overwrites = None)  exclude overrites propertIEs        else children |> List.where( x.Kind = ReflectionKind.Property)    | _ -> events =      x.Kind = ReflectionKind.Event)            |> List.where( exclude inhreited events            |> List.where( exclude overrites events         x.Kind = ReflectionKind.Event)    | _ -> methods =      x.Kind = ReflectionKind.Method)            |> List.where( exclude inhreited methods            |> List.where( exclude overrites methods         x.Kind = ReflectionKind.Method)    | _ -> []

 

有一点要注意,就是对于 interface 来说,子 interface 无需重复父 interface 的成员,因此需要排除。

然后我们直接返回一个 record,代表该节点的实体即可。

{    Type = then EntityType.Interface  EntityType.Class;    namespace = TypedocConverter section;    name = node.name;    Comment = comment;    ModifIEr = getModifIEr node.Flags;    inheritedFrom = exts;    Methods =         methods         |> List.map (            fun x ->                  retType =                      (                            match x.Signatures with                            | Some signatures ->                                 signatures |> List.where( x.Kind = ReflectionKind.CallSignature)                            | _ -> [])                         with                        | [] -> { Type = ; InnerTypes = []; name = None }                        | (front::_) ->                            match front.Type with                            | Some typeInfo -> getType typeInfo                            | _ -> { Type = ; InnerTypes = []; name = None }                 typeParameter =                     with                    | Some (sigs::_) ->                          types =                               match sigs.TypeParameter with                              | Some tp -> Some (getGenericTypeParameters tp)                              | _ -> None                        with                        | Some result -> result                        | _ -> []                    | _ -> []                    |> List.map ( x.Type)                 parameters =                     getmethodParameters                         (with                        | Some signatures ->                             signatures                             |> List.where( x.Kind = ReflectionKind.CallSignature)                             |> List.map(                                match x.Parameters with                                    | Some parameters -> parameters |> List.where(fun p -> p.Kind = ReflectionKind.Parameter)                                    | _ -> []                                )                            |> List.reduce(fun accu next -> accu @ next)                        | _ -> [])                {                    Comment =                         with                        | Some comment -> getXmlDocComment comment                        | _ ->                     ModifIEr = then []  getModifIEr x.Flags;                    Type = retType                    name = x.name                    TypeParameter = typeParameter                    Parameter = parameters                }        );    Events =         events        | paras =                     with                    | Some sigs ->                         sigs                         |> List.where ( x.Kind = ReflectionKind.Event)                        |> List.map( x.Parameters)                        |> List.collect (fun x ->                            with                            | Some paras -> paras                            | _ -> [])                    | _ -> []                {                     name = x.name;                     IsOptional =                         match x.Flags.IsOptional with                        | Some optional -> optional                        | _ -> false                        ;                    DelegateType =                         with                        | (front::_) ->                             System.Delegate; name = None; InnerTypes = [] }                        | _ ->                             match x.Type ; name = None; InnerTypes = [] }                        ;                    Comment =                                                 ;                    ModifIEr =  getModifIEr x.Flags;                }        );    PropertIEs =         propertIEs         |                 {                    Comment =                          getModifIEr x.Flags;                    name = x.name                    Type =                         with                        | Some typeInfo -> getType typeInfo                        | _ -> { Type = ; name = None; InnerTypes = [] }                    WithGet = ;                    WithSet = ;                    IsOptional =                                                ;                    InitialValue =                         with                        | Some value -> Some value                        | _ -> None                }        );    Enums = [];    TypeParameter = genericType |> List.map( x.Type);}

 

注意处理 event 的时候,委托的类型需要特殊处理一下。

Type alias 生诚器

还记得我们最上面说的一种特殊的 union types 吗?这里就是处理纯 string 的 type alias 的。

let parseUnionTypeAlias (section: ) (node: Reflection) (nodes: Type List): Entity List =    let notStringliteral = nodes |> List.tryFind(fun x -> x.Type <> )     enums =         match notStringliteral with        | Some _ ->             printWarning (Type alias " + node.name +  is not supported.)            []        | None ->            nodes             |> List.collect                (fun x ->                    match x.Value with                    | Some value ->                         [{                            name = topascalCase value                            Comment = ///<summary>\n" + toCommentText value + \n///</summary>                            Value = None                        }]                    | _ -> []                )    if enums = []          [            {                namespace = section                name = node.name                Comment =                     with                    | Some comment -> getXmlDocComment comment                    | _ ->                 Methods = []                Events = []                PropertIEs = []                Enums = enums                inheritedFrom = []                Type = EntityType.StringEnum                TypeParameter = []                ModifIEr = getModifIEr node.Flags            }        ]let parseTypeAlias (section: ) (node: Reflection): Entity List =     typeInfo = node.Type    match typeInfo with    | Some aliasType ->        match aliasType.Type match aliasType.Types with            | Some types -> parseUnionTypeAlias section node types            | _ ->                 printWarning ()                []        | _ ->            printWarning ()            []    | _ -> []

 

组合 Prasers

我们最后将以上 parsers 组合起来就 ojbk 了:

rec parseNode (section: match node.Kind with    | ReflectionKind.Global ->        with        | Some children -> parseNodes section children        | _ -> []    | ReflectionKind.Module ->                    parseNodes (then node.name else section + . + node.name) children        | _ -> []    | ReflectionKind.ExternalModule ->         []    | ReflectionKind.Enum -> [parseEnum section node]    | ReflectionKind.Interface -> [parseInterfaceAndClass section node ]    | ReflectionKind.Class -> [parseInterfaceAndClass section node ]    | ReflectionKind.TypeAlias ->         match node.Type  parseTypeAlias section node        | _ -> []    | _ -> [] parseNodes section (nodes: Reflection List): Entity List =    match nodes with    | ([ front ]) -> parseNode section front    | (front :: tails) ->        parseNode section front @ parseNodes section tails    | _ -> []

 

至此,我们的 parse 工作全部搞定,完结撒花~~~

代码生成

有了 C# 的实体类型,代码生成还困难吗?

不过有一点要注意的是,我们需要将名称转换为 Pascal Case,还需要生成 string literals union types 的 JsonConverter。不过这些都是样板代码,非常简单。

这里就不放代码了,感兴趣的同学可以自行去我的 GitHub 仓库查看。

测试效果

原 typescipt 代码:

declare namespace test {  /**   * The declaration of an enum   */  export enum MyEnum {    A = 0= 2 C  }  *   * The declaration of an interface     export interface MyInterface1 {    *     * A method         testMethod(arg: string,callback: () => voID): string;    *     * An event     * @event         onTest(Listener: (e: MyInterface1) => voID): ;    *     * An property         Readonly testProp: string;  }  *   * Another declaration of an interface     export interface MyInterface2<T> {        testMethod(arg: T,1)">): T;        onTest(Listener: (e: MyInterface2<T>) =>     Readonly testProp: T;  }  *   * The declaration of a class     export class MyClass1<T> implements MyInterface1 {        Readonly testProp: string;    static staticmethod(value: string,isOption?: boolean): UnionStr;  }  *   * Another declaration of a class     export class MyClass2<T> implements MyInterface2<T>    Readonly testProp: T;    static staticmethod(value: string,1)">*   * The declaration of a type alias     export type UnionStr = "A" | "B" | "C" | "other";}

 

Typedoc 生成的 JsON 后,将其作为输入,生成 C# 代码:

namespace TypedocConverter.Test{    /// <summary>    /// The declaration of an enum    </summary>    enum MyEnum    {        [Newtonsoft.Json.JsonProperty(A Newtonsoft.Json.NullValueHandling.Ignore)]        A = B Newtonsoft.Json.NullValueHandling.Ignore)]        B = 1C Newtonsoft.Json.NullValueHandling.Ignore)]        C = 2D Newtonsoft.Json.NullValueHandling.Ignore)]        D =     }} The declaration of a class    class MyClass1<T> : MyInterface1    {        <summary>         An property        </summary>        [Newtonsoft.Json.JsonProperty(testProp Newtonsoft.Json.NullValueHandling.Ignore)]        string TestProp { get => throw new System.NotImplementedException(); set => new System.NotImplementedException(); }        event System.Action<MyInterface1> OnTest;        string TestMethod(string arg,System.Action callback) =>  System.NotImplementedException();        static UnionStr Staticmethod(string value,bool isOption) =>  System.NotImplementedException();    }} Another declaration of a class    class MyClass2<T> : MyInterface2<T>    {         Newtonsoft.Json.NullValueHandling.Ignore)]        T TestProp { event System.Action<MyInterface2<T>> OnTest;        T TestMethod(T arg,System.Action callback) =>  The declaration of an interface    interface MyInterface1    {        get; set; }         arg,System.Action callback);    }} Another declaration of an interface    interface MyInterface2<T> The declaration of a type alias    </summary>    [Newtonsoft.Json.JsonConverter(typeof(UnionStrConverter))]     UnionStr    {        /// A         Newtonsoft.Json.NullValueHandling.Ignore)]        A, B         Newtonsoft.Json.NullValueHandling.Ignore)]        B,1)"> C         Newtonsoft.Json.NullValueHandling.Ignore)]        C,1)"> other        Other Newtonsoft.Json.NullValueHandling.Ignore)]        Other    }    class UnionStrConverter : Newtonsoft.Json.JsonConverter    {        public overrIDe bool CanConvert(System.Type t) => t == typeof(UnionStr) || t == typeof(UnionStr?);        object ReadJson(Newtonsoft.Json.JsonReader reader,System.Type t,1)">object? existingValue,Newtonsoft.Json.JsonSerializer serializer)            => reader.TokenType switch            {                Newtonsoft.Json.JsonToken.String =>                    serializer.Deserialize<string>(reader)                     {                        " => UnionStr.A,1)"> UnionStr.B,1)"> UnionStr.C,1)"> UnionStr.Other,_ => new System.Exception(Cannot unmarshal type UnionStr)                    },1)">)            };        voID WriteJson(Newtonsoft.Json.JsonWriter writer,1)"> untypedValue,Newtonsoft.Json.JsonSerializer serializer)        {            if (untypedValue is null) { serializer.Serialize(writer,1)">null); return; }            var value = (UnionStr)untypedValue;             (value)            {                case UnionStr.A: serializer.Serialize(writer,1)">"); ;                case UnionStr.B: serializer.Serialize(writer,1)">case UnionStr.C: serializer.Serialize(writer,1)">case UnionStr.Other: serializer.Serialize(writer,1)">default: break;            }            Cannot marshal type UnionStr);        }    }}

 

后记

有了这个工具后,妈妈再也不用担心我封装 TypeScript 的库了。有了 TypedocConverter,任何 TypeScript 的库都能轻而易举地转换成 C# 的类型绑定,然后进行封装,非常方便。

感谢大家看到这里,最后,欢迎大家使用 TypedocConverter。当然,如果能 star 一波甚至贡献代码,我会非常感谢的!

总结

以上是内存溢出为你收集整理的用 F# 手写 TypeScript 转 C# 类型绑定生成器全部内容,希望文章能够帮你解决用 F# 手写 TypeScript 转 C# 类型绑定生成器所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/1209924.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-06-04
下一篇2022-06-04

发表评论

登录后才能评论

评论列表(0条)

    保存