用 F# 手写 TypeScript 转 C# 类型绑定生成器
<h2>前言</h2><p>我们经常会遇到这样的事情:有时候我们找到了一个库,但是这个库是用 TypeScript 写的,但是我们想在 C# 调用,于是我们需要设法将原来的 TypeScript 类型声明翻译成 C# 的代码,然后如果是 UI 组件的话,我们需要将其封装到一个 WebView 里面,然后通过 JavaScript 和 C# 的互操作功能来调用该组件的各种方法,支持该组件的各种事件等等。</p>
<p>但是这是一个苦力活,尤其是类型翻译这一步。</p>
<p>这个是我最近在帮助维护一个开源 UWP 项目 monaco-editor-uwp 所需要的,该项目将微软的 monaco 编辑器封装成了 UWP 组件。</p>
<p>然而它的 monaco.d.ts 足足有 1.5 mb,并且 API 经常会变化,如果人工翻译,不仅工作量十分大,还可能会漏掉新的变化,但是如果有一个自动生成器的话,那么人工的工作就会少很多。</p>
<p>目前 GitHub 上面有一个叫做 QuickType 的项目,但是这个项目对 TypeScript 的支持极其有限,仍然停留在 TypeScript 3.2,而且遇到不认识的类型就会报错,比如 DOM 类型等等。</p>
<p>因此我决定手写一个代码生成器 TypedocConverter:https://github.com/hez2010/TypedocConverter</p>
<h2>构思</h2>
<p>本来是打算从 TypeScript 词法和语义分析开始做的,但是发现有一个叫做 Typedoc 的项目已经帮我们完成了这一步,而且支持输出 JSON schema,那么剩下的事情就简单了:我们只需要将 TypeScript 的 AST 转换成 C# 的 AST,然后再将 AST 还原成代码即可。</p>
<p>那么话不多说,这就开写。</p>
<h2>构建 Typescipt AST 类型绑定</h2>
<p>借助于 F# 更加强大的类型系统,类型的声明和使用非常简单,并且具有完善的recursive pattern。pattern matching、option types 等支持,这也是该项目选用 F# 而不是 C# 的原因,虽然 C# 也支持这些,也有一定的 FP 能力,但是它还是偏 OOP,写起来会有很多的样板代码,非常的繁琐。</p>
<p>我们将 Typescipt 的类型绑定定义到 Definition.fs 中,这一步直接将 Typedoc 的定义翻译到 F# 即可:</p>
<p>首先是 ReflectionKind 枚举,该枚举表示了 JSON Schema 中各节点的类型:</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">type</span><span style="color: rgba(0, 0, 0, 1)"> ReflectionKind =
</span>| Global = <span style="color: rgba(128, 0, 128, 1)">0</span>
| ExternalModule = <span style="color: rgba(128, 0, 128, 1)">1</span>
| Module = <span style="color: rgba(128, 0, 128, 1)">2</span>
| Enum = <span style="color: rgba(128, 0, 128, 1)">4</span>
| EnumMember = <span style="color: rgba(128, 0, 128, 1)">16</span>
| Variable = <span style="color: rgba(128, 0, 128, 1)">32</span>
| Function = <span style="color: rgba(128, 0, 128, 1)">64</span>
| Class = <span style="color: rgba(128, 0, 128, 1)">128</span>
| Interface = <span style="color: rgba(128, 0, 128, 1)">256</span>
| Constructor = <span style="color: rgba(128, 0, 128, 1)">512</span>
| Property = <span style="color: rgba(128, 0, 128, 1)">1024</span>
| Method = <span style="color: rgba(128, 0, 128, 1)">2048</span>
| CallSignature = <span style="color: rgba(128, 0, 128, 1)">4096</span>
| IndexSignature = <span style="color: rgba(128, 0, 128, 1)">8192</span>
| ConstructorSignature = <span style="color: rgba(128, 0, 128, 1)">16384</span>
| Parameter = <span style="color: rgba(128, 0, 128, 1)">32768</span>
| TypeLiteral = <span style="color: rgba(128, 0, 128, 1)">65536</span>
| TypeParameter = <span style="color: rgba(128, 0, 128, 1)">131072</span>
| Accessor = <span style="color: rgba(128, 0, 128, 1)">262144</span>
| GetSignature = <span style="color: rgba(128, 0, 128, 1)">524288</span>
| SetSignature = <span style="color: rgba(128, 0, 128, 1)">1048576</span>
| ObjectLiteral = <span style="color: rgba(128, 0, 128, 1)">2097152</span>
| TypeAlias = <span style="color: rgba(128, 0, 128, 1)">4194304</span>
| Event = <span style="color: rgba(128, 0, 128, 1)">8388608</span>
| Reference = <span style="color: rgba(128, 0, 128, 1)">16777216</span></pre>
</div>
<p> </p>
</div>
<p>然后是类型修饰标志 ReflectionFlags,注意该 record 所有的成员都是 option 的</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">type</span><span style="color: rgba(0, 0, 0, 1)"> ReflectionFlags = {
IsPrivate: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
IsProtected: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
IsPublic: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
IsStatic: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
IsExported: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
IsExternal: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
IsOptional: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
IsReset: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
HasExportAssignment: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
IsConstructorProperty: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
IsAbstract: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
IsConst: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
IsLet: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
}</span></pre>
</div>
<p> </p>
</div>
<p>然后到了我们的 Reflection,由于每一种类型的 Reflection 都可以由 ReflectionKind 来区分,因此我选择将所有类型的 Reflection 合并成为一个 record,而不是采用 Union Types,因为后者虽然看上去清晰,但是在实际 parse AST 的时候会需要大量 pattern matching 的代码。</p>
<p>由于部分 records 相互引用,因此我们使用 <code>and</code> 来定义 recursive records。</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">type</span><span style="color: rgba(0, 0, 0, 1)"> Reflection = {
Id: </span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">
Name: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
OriginalName: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Kind: ReflectionKind
KindString: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
Flags: ReflectionFlags
Parent: Reflection option
Comment: Comment option
Sources: SourceReference list option
Decorators: Decorator option
Decorates: Type list option
Url: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
Anchor: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
HasOwnDocument: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
CssClasses: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
DefaultValue: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> 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<</span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">, Reflection> option
Directory: SourceDirectory option
Files: SourceFile list option
Readme: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
PackageInfo: obj option
Parameters: Reflection list option
}
</span><span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> DeclarationHierarchy = {
Type: Type list
Next: DeclarationHierarchy option
IsTarget: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
}
</span><span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> Type = {
Type: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Id: </span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> option
Name: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
ElementType: Type option
Value: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
Types: Type list option
TypeArguments: Type list option
Constraint: Type option
Declaration: Reflection option
}
</span><span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> Decorator = {
Name: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Type: Type option
Arguments: obj option
}
</span><span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> ReflectionGroup = {
Title: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Kind: ReflectionKind
Children: </span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> list
CssClasses: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
AllChildrenHaveOwnDocument: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
AllChildrenAreInherited: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
AllChildrenArePrivate: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
AllChildrenAreProtectedOrPrivate: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
AllChildrenAreExternal: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
SomeChildrenAreExported: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
Categories: ReflectionCategory list option
}
</span><span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> ReflectionCategory = {
Title: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Children: </span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> list
AllChildrenHaveOwnDocument: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> option
}
</span><span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> SourceDirectory = {
Parent: SourceDirectory option
Directories: Map<</span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">, SourceDirectory>
Groups: ReflectionGroup list option
Files: SourceFile list
Name: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
DirName: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
Url: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
}
</span><span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> SourceFile = {
FullFileName: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
FileName: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Name: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Url: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
Parent: SourceDirectory option
Reflections: Reflection list option
Groups: ReflectionGroup list option
}
</span><span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> SourceReference = {
File: SourceFile option
FileName: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Line: </span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">
Character: </span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">
Url: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
}
</span><span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> Comment = {
ShortText: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Text: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
Returns: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
Tags: CommentTag list option
}
</span><span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> CommentTag = {
TagName: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
ParentName: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Text: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
}</span></pre>
</div>
<p> </p>
</div>
<p>这样,我们就简单的完成了类型绑定的翻译,接下来要做的就是将 Typedoc 生成的 JSON 反序列化成我们所需要的东西即可。</p>
<h2>反序列化</h2>
<p>虽然想着好像一切都很顺利,但是实际上 System.Text.Json、Newtonsoft.JSON 等均不支持 F# 的 option types,所需我们还需要一个 JsonConverter 处理 option types。</p>
<p>本项目采用 Newtonsoft.Json,因为 System.Text.Json 目前尚不成熟。得益于 F# 对 OOP 的兼容,我们可以很容易的实现一个 <code>OptionConverter</code>。</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">type</span><span style="color: rgba(0, 0, 0, 1)"> OptionConverter() =
</span><span style="color: rgba(0, 0, 255, 1)">inherit</span><span style="color: rgba(0, 0, 0, 1)"> JsonConverter()
</span><span style="color: rgba(0, 0, 255, 1)">override</span> __.CanConvert(objectType: Type) : <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> objectType.IsGenericType <span style="color: rgba(0, 0, 255, 1)">with</span>
| <span style="color: rgba(0, 0, 255, 1)">false</span> -> <span style="color: rgba(0, 0, 255, 1)">false</span>
| <span style="color: rgba(0, 0, 255, 1)">true</span> -><span style="color: rgba(0, 0, 0, 1)"> typedefof<_ option> = objectType.GetGenericTypeDefinition()
</span><span style="color: rgba(0, 0, 255, 1)">override</span><span style="color: rgba(0, 0, 0, 1)"> __.WriteJson(writer: JsonWriter, value: obj, serializer: JsonSerializer) : unit =
serializer.Serialize(writer,
</span><span style="color: rgba(0, 0, 255, 1)">if</span> isNull value <span style="color: rgba(0, 0, 255, 1)">then</span> <span style="color: rgba(0, 0, 255, 1)">null</span>
<span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> _, fields = FSharpValue.GetUnionFields(value, value.GetType())
fields.[</span><span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">]
)
</span><span style="color: rgba(0, 0, 255, 1)">override</span><span style="color: rgba(0, 0, 0, 1)"> __.ReadJson(reader: JsonReader, objectType: Type, _existingValue: obj, serializer: JsonSerializer) : obj =
</span><span style="color: rgba(0, 0, 255, 1)">let</span> innerType = objectType.GetGenericArguments().[<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">]
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> value =
serializer.Deserialize(
reader,
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> innerType.IsValueType
</span><span style="color: rgba(0, 0, 255, 1)">then</span> (typedefof<_ Nullable>).MakeGenericType([|innerType|<span style="color: rgba(0, 0, 0, 1)">])
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> innerType
)
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> cases = FSharpType.GetUnionCases objectType
</span><span style="color: rgba(0, 0, 255, 1)">if</span> isNull value <span style="color: rgba(0, 0, 255, 1)">then</span> FSharpValue.MakeUnion(cases.[<span style="color: rgba(128, 0, 128, 1)">0</span>], [||<span style="color: rgba(0, 0, 0, 1)">])
</span><span style="color: rgba(0, 0, 255, 1)">else</span> FSharpValue.MakeUnion(cases.[<span style="color: rgba(128, 0, 128, 1)">1</span>], [|value|])</pre>
</div>
<p> </p>
</div>
<p>这样所有的工作就完成了。</p>
<p>我们可以去 monaco-editor 仓库下载 monaco.d.ts 测试一下我们的 JSON Schema deserializer,可以发现 JSON Sechma 都被正确地反序列化了。</p>
<p><img class="origin_image zh-lightbox-thumb lazy" src="https://pic3.zhimg.com/80/v2-e8e1e4aa3a5366a9fc29c561831a8862_hd.jpg" alt="" width="760" height="595" data-size="normal" data-rawwidth="1916" data-rawheight="1501" data-original="https://pic3.zhimg.com/v2-e8e1e4aa3a5366a9fc29c561831a8862_r.jpg" data-actualsrc="https://pic3.zhimg.com/v2-e8e1e4aa3a5366a9fc29c561831a8862_b.jpg" data-lazy-status="ok"></p>
<p>反序列化结果</p>
<p class="ztext-empty-paragraph"> </p>
<h2>构建 C# AST 类型</h2>
<p>当然,此 "AST" 非彼 AST,我们没有必要其细化到语句层面,因为我们只是要写一个简单的代码生成器,我们只需要构建实体结构即可。</p>
<p>我们将实体结构定义到 Entity.fs 中,在此我们只需支持 interface、class、enum 即可,对于 class 和 interface,我们只需要支持 method、property 和 event 就足够了。</p>
<p>当然,代码中存在泛型的可能,这一点我们也需要考虑。</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">type</span><span style="color: rgba(0, 0, 0, 1)"> EntityBodyType = {
Type: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Name: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
InnerTypes: EntityBodyType list
}
</span><span style="color: rgba(0, 0, 255, 1)">type</span><span style="color: rgba(0, 0, 0, 1)"> EntityMethod = {
Comment: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Modifier: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> list
Type: EntityBodyType
Name: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
TypeParameter: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> list
Parameter: EntityBodyType list
}
</span><span style="color: rgba(0, 0, 255, 1)">type</span><span style="color: rgba(0, 0, 0, 1)"> EntityProperty = {
Comment: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Modifier: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> list
Name: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Type: EntityBodyType
WithGet: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)">
WithSet: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)">
IsOptional: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)">
InitialValue: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> option
}
</span><span style="color: rgba(0, 0, 255, 1)">type</span><span style="color: rgba(0, 0, 0, 1)"> EntityEvent = {
Comment: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Modifier: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> list
DelegateType: EntityBodyType
Name: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
IsOptional: </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 0, 255, 1)">type</span><span style="color: rgba(0, 0, 0, 1)"> EntityEnum = {
Comment: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Name: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Value: int64 option
}
</span><span style="color: rgba(0, 0, 255, 1)">type</span><span style="color: rgba(0, 0, 0, 1)"> EntityType =
</span>|<span style="color: rgba(0, 0, 0, 1)"> Interface
</span>|<span style="color: rgba(0, 0, 0, 1)"> Class
</span>|<span style="color: rgba(0, 0, 0, 1)"> Enum
</span>|<span style="color: rgba(0, 0, 0, 1)"> StringEnum
</span><span style="color: rgba(0, 0, 255, 1)">type</span><span style="color: rgba(0, 0, 0, 1)"> Entity = {
Namespace: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Name: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Comment: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
Methods: EntityMethod list
Properties: EntityProperty list
Events: EntityEvent list
Enums: EntityEnum list
InheritedFrom: EntityBodyType list
Type: EntityType
TypeParameter: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> list
Modifier: </span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> list
}</span></pre>
</div>
<p> </p>
</div>
<h2>文档化注释生成器</h2>
<p>文档化注释也是少不了的东西,能极大方便开发者后续使用生成的类型绑定,而无需参照原 typescript 类型声明上的注释。</p>
<p>代码很简单,只需要将文本处理成 xml 即可。</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">let</span> escapeSymbols (text: <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">) =
</span><span style="color: rgba(0, 0, 255, 1)">if</span> isNull text <span style="color: rgba(0, 0, 255, 1)">then</span> <span style="color: rgba(128, 0, 0, 1)">""</span>
<span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> text
.Replace(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&amp;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
.Replace(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
.Replace(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">></span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">let</span> toCommentText (text: <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">) =
</span><span style="color: rgba(0, 0, 255, 1)">if</span> isNull text <span style="color: rgba(0, 0, 255, 1)">then</span> <span style="color: rgba(128, 0, 0, 1)">""</span>
<span style="color: rgba(0, 0, 255, 1)">else</span> text.Split <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n</span><span style="color: rgba(128, 0, 0, 1)">"</span> |> Array.map (<span style="color: rgba(0, 0, 255, 1)">fun</span> t -> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/// </span><span style="color: rgba(128, 0, 0, 1)">"</span> + escapeSymbols t) |> Array.reduce(<span style="color: rgba(0, 0, 255, 1)">fun</span> accu next -> accu + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> + next)
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> getXmlDocComment (comment: Comment) =
</span><span style="color: rgba(0, 0, 255, 1)">let</span> prefix = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/// <summary>\n</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 0, 255, 1)">let</span> suffix = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n/// </summary></span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> summary =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> comment.Text <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some text -><span style="color: rgba(0, 0, 0, 1)"> prefix + toCommentText comment.ShortText + toCommentText text + suffix
</span>| _ ->
<span style="color: rgba(0, 0, 255, 1)">match</span> comment.ShortText <span style="color: rgba(0, 0, 255, 1)">with</span>
| <span style="color: rgba(128, 0, 0, 1)">""</span> -> <span style="color: rgba(128, 0, 0, 1)">""</span>
| _ -><span style="color: rgba(0, 0, 0, 1)"> prefix + toCommentText comment.ShortText + suffix
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> returns =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> comment.Returns <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some text -> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n/// <returns>\n</span><span style="color: rgba(128, 0, 0, 1)">"</span> + toCommentText text + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n/// </returns></span><span style="color: rgba(128, 0, 0, 1)">"</span>
| _ -> <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">
summary + returns</span></pre>
</div>
<p> </p>
</div>
<h2>类型生成器</h2>
<p>Typescript 的类型系统较为灵活,包括 union types、intersect types 等等,这些即使是目前的 C# 8 都不能直接表达,需要等到 C# 9 才行。当然我们可以生成一个 struct 并为其编写隐式转换操作符重载,支持 union types,但是目前尚未实现,我们就先用 union types 中的第一个类型代替,而对于 intersect types,我们姑且先使用 object。</p>
<p>然而 union types 有一个特殊情况:string literals types alias。就是这样的东西:</p>
<div class="highlight">
<pre><code class="language-text">type Size = "XS" | "S" | "M" | "L" | "XL";</code></pre>
</div>
<p>即纯 string 值组合的 type alias,这个我们还是有必要支持的,因为在 typescript 中用的非常广泛。</p>
<p>C# 在没有对应语法的时候要怎么支持呢?很简单,我们创建一个 enum,该 enum 包含该类型中的所有元素,然后我们为其编写 JsonConverter,这样就能确保序列化后,typescript 方能正确识别类型,而在 C# 又有 type sound 的编码体验。</p>
<p>另外,我们需要提供一些常用的类型转换:</p>
<ul>
<li><code>Array<T></code> -> <code>T[]</code> </li>
<li><code>Set<T></code> -> <code>System.Collections.Generic.ISet<T></code></li>
<li><code></code><code>Map<T></code> -> <code>System.Collections.Generic.IDictionary<T></code> </li>
<li><code>Promise<T></code> -> <code>System.Threading.Tasks.Task<T></code> </li>
<li>callbacks -> <code>System.Func<T...></code>, <code>System.Action<T...></code> </li>
<li>Tuple 类型</li>
<li>其他的数组类型如 <code>Uint32Array</code> </li>
<li>对于 <code><void></code>,我们需要解除泛型,即 <code>T<void></code> -> <code>T</code></li>
</ul>
<p>那么实现如下:</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">let</span> <span style="color: rgba(0, 0, 255, 1)">rec</span><span style="color: rgba(0, 0, 0, 1)"> getType (typeInfo: Type): EntityBodyType =
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> genericType =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> typeInfo.Type <span style="color: rgba(0, 0, 255, 1)">with</span>
| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">intrinsic</span><span style="color: rgba(128, 0, 0, 1)">"</span> ->
<span style="color: rgba(0, 0, 255, 1)">match</span> typeInfo.Name <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some name ->
<span style="color: rgba(0, 0, 255, 1)">match</span> name <span style="color: rgba(0, 0, 255, 1)">with</span>
| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">number</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">double</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">boolean</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">bool</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">string</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">string</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">void</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">void</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">reference</span><span style="color: rgba(128, 0, 0, 1)">"</span> | <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">typeParameter</span><span style="color: rgba(128, 0, 0, 1)">"</span> ->
<span style="color: rgba(0, 0, 255, 1)">match</span> typeInfo.Name <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some name ->
<span style="color: rgba(0, 0, 255, 1)">match</span> name <span style="color: rgba(0, 0, 255, 1)">with</span>
| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Promise</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Threading.Tasks.Task</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Set</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Collections.Generic.ISet</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Map</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Collections.Generic.IDictionary</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Array</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Array</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">BigUint64Array</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Array</span><span style="color: rgba(128, 0, 0, 1)">"</span>; InnerTypes = [{ Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ulong</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = [ ]; Name = None };]; Name = None };
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Uint32Array</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Array</span><span style="color: rgba(128, 0, 0, 1)">"</span>; InnerTypes = [{ Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">uint</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = [ ]; Name = None };]; Name = None };
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Uint16Array</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Array</span><span style="color: rgba(128, 0, 0, 1)">"</span>; InnerTypes = [{ Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ushort</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = [ ]; Name = None };]; Name = None };
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Uint8Array</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Array</span><span style="color: rgba(128, 0, 0, 1)">"</span>; InnerTypes = [{ Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">byte</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = [ ]; Name = None };]; Name = None };
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">BigInt64Array</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Array</span><span style="color: rgba(128, 0, 0, 1)">"</span>; InnerTypes = [{ Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">long</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = [ ]; Name = None };]; Name = None };
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Int32Array</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Array</span><span style="color: rgba(128, 0, 0, 1)">"</span>; InnerTypes = [{ Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">int</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = [ ]; Name = None };]; Name = None };
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Int16Array</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Array</span><span style="color: rgba(128, 0, 0, 1)">"</span>; InnerTypes = [{ Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">short</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = [ ]; Name = None };]; Name = None };
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Int8Array</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Array</span><span style="color: rgba(128, 0, 0, 1)">"</span>; InnerTypes = [{ Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">char</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = [ ]; Name = None };]; Name = None };
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RegExp</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">string</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None };
</span>| x -><span style="color: rgba(0, 0, 0, 1)"> { Type = x; InnerTypes = []; Name = None };
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">array</span><span style="color: rgba(128, 0, 0, 1)">"</span> ->
<span style="color: rgba(0, 0, 255, 1)">match</span> typeInfo.ElementType <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some elementType -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Array</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = ; Name = None }
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Array</span><span style="color: rgba(128, 0, 0, 1)">"</span>; InnerTypes = [{ Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }]; Name = None }
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">stringLiteral</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">string</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">tuple</span><span style="color: rgba(128, 0, 0, 1)">"</span> ->
<span style="color: rgba(0, 0, 255, 1)">match</span> typeInfo.Types <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some innerTypes ->
<span style="color: rgba(0, 0, 255, 1)">match</span> innerTypes <span style="color: rgba(0, 0, 255, 1)">with</span>
| [] -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.ValueTuple</span><span style="color: rgba(128, 0, 0, 1)">"</span>; InnerTypes = innerTypes |<span style="color: rgba(0, 0, 0, 1)">> List.map getType; Name = None }
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">union</span><span style="color: rgba(128, 0, 0, 1)">"</span> ->
<span style="color: rgba(0, 0, 255, 1)">match</span> typeInfo.Types <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some innerTypes ->
<span style="color: rgba(0, 0, 255, 1)">match</span> innerTypes <span style="color: rgba(0, 0, 255, 1)">with</span>
| [] -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| _ -><span style="color: rgba(0, 0, 0, 1)">
printWarning (</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Taking only the first type </span><span style="color: rgba(128, 0, 0, 1)">"</span> + innerTypes.[<span style="color: rgba(128, 0, 128, 1)">0</span>].Type + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"> for the entire union type.</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
getType innerTypes.[</span><span style="color: rgba(128, 0, 128, 1)">0</span>] <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> TODO: generate unions</span>
| _ ->{ Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">intersection</span><span style="color: rgba(128, 0, 0, 1)">"</span> -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span>; InnerTypes = []; Name = None } <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> TODO: generate intersections</span>
| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">reflection</span><span style="color: rgba(128, 0, 0, 1)">"</span> ->
<span style="color: rgba(0, 0, 255, 1)">match</span> typeInfo.Declaration <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some dec ->
<span style="color: rgba(0, 0, 255, 1)">match</span> dec.Signatures <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some ->
<span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> paras =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> signature.Parameters <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some p -><span style="color: rgba(0, 0, 0, 1)">
p
</span>|<span style="color: rgba(0, 0, 0, 1)">> List.map
(</span><span style="color: rgba(0, 0, 255, 1)">fun</span> pi ->
<span style="color: rgba(0, 0, 255, 1)">match</span> pi.Type <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some pt -><span style="color: rgba(0, 0, 0, 1)"> Some (getType pt)
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> None
)
</span>|<span style="color: rgba(0, 0, 0, 1)">> List.collect
(</span><span style="color: rgba(0, 0, 255, 1)">fun</span> x ->
<span style="color: rgba(0, 0, 255, 1)">match</span> x <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some s -><span style="color: rgba(0, 0, 0, 1)">
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
)
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
</span><span style="color: rgba(0, 0, 255, 1)">let</span> <span style="color: rgba(0, 0, 255, 1)">rec</span><span style="color: rgba(0, 0, 0, 1)"> getDelegateParas (paras: EntityBodyType list): EntityBodyType list =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> paras <span style="color: rgba(0, 0, 255, 1)">with</span>
| -><span style="color: rgba(0, 0, 0, 1)"> [{ Type = x.Type; InnerTypes = x.InnerTypes; Name = None }]
</span>| (front::tails) -><span style="color: rgba(0, 0, 0, 1)"> @ getDelegateParas tails
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> returnsType =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> signature.Type <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some t -><span style="color: rgba(0, 0, 0, 1)"> getType t
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">void</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> typeParas = getDelegateParas paras
</span><span style="color: rgba(0, 0, 255, 1)">match</span> typeParas <span style="color: rgba(0, 0, 255, 1)">with</span>
| [] -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Action</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| _ ->
<span style="color: rgba(0, 0, 255, 1)">if</span> returnsType.Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">void</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 0, 255, 1)">then</span> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Action</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = typeParas; Name = None }
</span><span style="color: rgba(0, 0, 255, 1)">else</span> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Func</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = typeParas @ ; Name = None }
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span><span style="color: rgba(0, 0, 255, 1)">let</span> <span style="color: rgba(0, 0, 255, 1)">mutable</span><span style="color: rgba(0, 0, 0, 1)"> innerTypes =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> typeInfo.TypeArguments <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some args -><span style="color: rgba(0, 0, 0, 1)"> getGenericTypeArguments args
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
</span><span style="color: rgba(0, 0, 255, 1)">if</span> genericType.Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Threading.Tasks.Task</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 0, 255, 1)">then</span>
<span style="color: rgba(0, 0, 255, 1)">match</span> innerTypes <span style="color: rgba(0, 0, 255, 1)">with</span>
| (front::_) -> <span style="color: rgba(0, 0, 255, 1)">if</span> front.Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">void</span><span style="color: rgba(128, 0, 0, 1)">"</span> <span style="color: rgba(0, 0, 255, 1)">then</span> innerTypes <- [] <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> ()
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> ()
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> ()
{
Type = genericType.Type;
Name = None;
InnerTypes = </span><span style="color: rgba(0, 0, 255, 1)">if</span> innerTypes = [] <span style="color: rgba(0, 0, 255, 1)">then</span> genericType.InnerTypes <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> innerTypes;
}
</span><span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> getGenericTypeArguments (typeInfos: Type list): EntityBodyType list =
typeInfos </span>|<span style="color: rgba(0, 0, 0, 1)">> List.map getType
</span><span style="color: rgba(0, 0, 255, 1)">and</span> getGenericTypeParameters (nodes: Reflection list) = <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> TODO: generate constaints</span>
<span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> types =
nodes
</span>|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Kind = ReflectionKind.TypeParameter)
</span>|> List.map (<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Name)
types </span>|> List.map (<span style="color: rgba(0, 0, 255, 1)">fun</span> x -> {| Type = x; Constraint = <span style="color: rgba(128, 0, 0, 1)">""</span> |})</pre>
</div>
<p> </p>
</div>
<p>当然,目前尚不支持生成泛型约束,如果以后有时间的话会考虑添加。</p>
<h2>修饰生成器</h2>
<p>例如 <code>public</code>、<code>private</code>、<code>protected</code>、<code>static</code> 等等。这一步很简单,直接将 ReflectionFlags 转换一下即可,个人觉得使用 mutable 代码会让代码变得非常不优雅,但是有的时候还是需要用一下的,不然会极大地提高代码的复杂度。</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> getModifier (flags: ReflectionFlags) =
</span><span style="color: rgba(0, 0, 255, 1)">let</span> <span style="color: rgba(0, 0, 255, 1)">mutable</span><span style="color: rgba(0, 0, 0, 1)"> modifier = []
</span><span style="color: rgba(0, 0, 255, 1)">match</span> flags.IsPublic <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some flag -> <span style="color: rgba(0, 0, 255, 1)">if</span> flag <span style="color: rgba(0, 0, 255, 1)">then</span> modifier <- modifier |> List.append [ <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">public</span><span style="color: rgba(128, 0, 0, 1)">"</span> ] <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> ()
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> ()
</span><span style="color: rgba(0, 0, 255, 1)">match</span> flags.IsAbstract <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some flag -> <span style="color: rgba(0, 0, 255, 1)">if</span> flag <span style="color: rgba(0, 0, 255, 1)">then</span> modifier <- modifier |> List.append [ <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">abstract</span><span style="color: rgba(128, 0, 0, 1)">"</span> ] <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> ()
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> ()
</span><span style="color: rgba(0, 0, 255, 1)">match</span> flags.IsPrivate <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some flag -> <span style="color: rgba(0, 0, 255, 1)">if</span> flag <span style="color: rgba(0, 0, 255, 1)">then</span> modifier <- modifier |> List.append [ <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">private</span><span style="color: rgba(128, 0, 0, 1)">"</span> ] <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> ()
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> ()
</span><span style="color: rgba(0, 0, 255, 1)">match</span> flags.IsProtected <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some flag -> <span style="color: rgba(0, 0, 255, 1)">if</span> flag <span style="color: rgba(0, 0, 255, 1)">then</span> modifier <- modifier |> List.append [ <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">protected</span><span style="color: rgba(128, 0, 0, 1)">"</span> ] <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> ()
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> ()
</span><span style="color: rgba(0, 0, 255, 1)">match</span> flags.IsStatic <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some flag -> <span style="color: rgba(0, 0, 255, 1)">if</span> flag <span style="color: rgba(0, 0, 255, 1)">then</span> modifier <- modifier |> List.append [ <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">static</span><span style="color: rgba(128, 0, 0, 1)">"</span> ] <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> ()
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> ()
modifier</span></pre>
</div>
<p> </p>
</div>
<h2>Enum 生成器</h2>
<p>终于到 parse 实体的部分了,我们先从最简单的做起:枚举。 代码很简单,直接将原 AST 中的枚举部分转换一下即可。</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">let</span> parseEnum (section: <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">) (node: Reflection): Entity =
</span><span style="color: rgba(0, 0, 255, 1)">let</span> values = <span style="color: rgba(0, 0, 255, 1)">match</span> node.Children <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some children -><span style="color: rgba(0, 0, 0, 1)">
children
</span>|> List.where (<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Kind = ReflectionKind.EnumMember)
</span>| None -><span style="color: rgba(0, 0, 0, 1)"> []
{
Type = EntityType.Enum;
Namespace = </span><span style="color: rgba(0, 0, 255, 1)">if</span> section = <span style="color: rgba(128, 0, 0, 1)">""</span> <span style="color: rgba(0, 0, 255, 1)">then</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">TypeDocGenerator</span><span style="color: rgba(128, 0, 0, 1)">"</span> <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> section;
Modifier = getModifier node.Flags;
Name = node.Name
Comment =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> node.Comment <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some comment -><span style="color: rgba(0, 0, 0, 1)"> getXmlDocComment comment
</span>| _ -> <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">
Methods = []; Properties = []; Events = []; InheritedFrom = [];
Enums = values </span>|> List.map (<span style="color: rgba(0, 0, 255, 1)">fun</span> x ->
<span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> comment =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> x.Comment <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some comment -><span style="color: rgba(0, 0, 0, 1)"> getXmlDocComment comment
</span>| _ -> <span style="color: rgba(128, 0, 0, 1)">""</span>
<span style="color: rgba(0, 0, 255, 1)">let</span> <span style="color: rgba(0, 0, 255, 1)">mutable</span> intValue = <span style="color: rgba(128, 0, 128, 1)">0L</span>
<span style="color: rgba(0, 0, 255, 1)">match</span> x.DefaultValue <span style="color: rgba(0, 0, 255, 1)">with</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ?????</span>
| Some value -> <span style="color: rgba(0, 0, 255, 1)">if</span> Int64.TryParse(value, &intValue) <span style="color: rgba(0, 0, 255, 1)">then</span><span style="color: rgba(0, 0, 0, 1)"> { Comment = comment; Name = toPascalCase x.Name; Value = Some intValue; }
</span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">match</span> getEnumReferencedValue values value x.Name <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some t -><span style="color: rgba(0, 0, 0, 1)"> { Comment = comment; Name = x.Name; Value = Some (int64 t); }
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> { Comment = comment; Name = x.Name; Value = None; }
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> { Comment = comment; Name = x.Name; Value = None; }
);
TypeParameter = []
}</span></pre>
</div>
<p> </p>
</div>
<p>你会注意到一个上面我有一处标了个 <code>?????</code>,这是在干什么呢?</p>
<p>其实,TypeScript 的 enum 是 recursive 的,也就意味着定义的时候,一个元素可以引用另一个元素,比如这样:</p>
<div class="highlight">
<pre><code class="language-text">enum MyEnum {
A = 1,
B = 2,
C = A
}</code></pre>
</div>
<p>这个时候,我们需要查找它引用的枚举值,比如在上面的例子里面,处理 C 的时候,需要将它的值 A 用真实值 1 代替。所以我们还需要一个查找函数:</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">let</span> <span style="color: rgba(0, 0, 255, 1)">rec</span><span style="color: rgba(0, 0, 0, 1)"> getEnumReferencedValue (nodes: Reflection list) value name =
</span><span style="color: rgba(0, 0, 255, 1)">match</span><span style="color: rgba(0, 0, 0, 1)"> nodes
</span>|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x ->
<span style="color: rgba(0, 0, 255, 1)">match</span> x.DefaultValue <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some v -><span style="color: rgba(0, 0, 0, 1)"> v <> value && not (name = x.Name)
</span>| _ -> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
)
</span>|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Name = value)
</span>|> List.tryFind(<span style="color: rgba(0, 0, 255, 1)">fun</span> x ->
<span style="color: rgba(0, 0, 255, 1)">let</span> <span style="color: rgba(0, 0, 255, 1)">mutable</span> intValue = <span style="color: rgba(128, 0, 128, 1)">0</span>
<span style="color: rgba(0, 0, 255, 1)">match</span> x.DefaultValue <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some y -><span style="color: rgba(0, 0, 0, 1)"> Int32.TryParse(y, &intValue)
</span>| _ -> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
) </span><span style="color: rgba(0, 0, 255, 1)">with</span>
| Some t -><span style="color: rgba(0, 0, 0, 1)"> t.DefaultValue
</span>| _ -> None</pre>
</div>
<p> </p>
</div>
<p>这样我们的 Enum parser 就完成了。</p>
<h2>Interface 和 Class 生成器</h2>
<p>下面到了重头戏,interface 和 class 才是类型绑定的关键。</p>
<p>我们的函数签名是这样的:</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">let</span> parseInterfaceAndClass (section: <span style="color: rgba(0, 0, 255, 1)">string</span>) (node: Reflection) (isInterface: <span style="color: rgba(0, 0, 255, 1)">bool</span>): Entity = ...</pre>
</div>
<p> </p>
</div>
<p>首先我们从 Reflection 节点中查找并生成注释、修饰、名称、泛型参数、继承关系、方法、属性和事件:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> comment =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> node.Comment <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some comment -><span style="color: rgba(0, 0, 0, 1)"> getXmlDocComment comment
</span>| _ -> <span style="color: rgba(128, 0, 0, 1)">""</span>
<span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> exts =
(</span><span style="color: rgba(0, 0, 255, 1)">match</span> node.ExtendedTypes <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some types -> types |> List.map(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> getType x)
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []) @
(</span><span style="color: rgba(0, 0, 255, 1)">match</span> node.ImplementedTypes <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some types -> types |> List.map(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> getType x)
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> [])
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> genericType =
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> types =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> node.TypeParameter <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some tp -><span style="color: rgba(0, 0, 0, 1)"> Some (getGenericTypeParameters tp)
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> None
</span><span style="color: rgba(0, 0, 255, 1)">match</span> types <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some result -><span style="color: rgba(0, 0, 0, 1)"> result
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> properties =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> node.Children <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some children ->
<span style="color: rgba(0, 0, 255, 1)">if</span> isInterface <span style="color: rgba(0, 0, 255, 1)">then</span><span style="color: rgba(0, 0, 0, 1)">
children
</span>|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Kind = ReflectionKind.Property)
</span>|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -> x.InheritedFrom = None) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> exclude inhreited properties</span>
|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -> x.Overwrites = None) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> exclude overrites properties</span>
<span style="color: rgba(0, 0, 255, 1)">else</span> children |> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Kind = ReflectionKind.Property)
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> events =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> node.Children <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some children ->
<span style="color: rgba(0, 0, 255, 1)">if</span> isInterface <span style="color: rgba(0, 0, 255, 1)">then</span><span style="color: rgba(0, 0, 0, 1)">
children
</span>|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Kind = ReflectionKind.Event)
</span>|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -> x.InheritedFrom = None) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> exclude inhreited events</span>
|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -> x.Overwrites = None) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> exclude overrites events</span>
<span style="color: rgba(0, 0, 255, 1)">else</span> children |> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Kind = ReflectionKind.Event)
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> methods =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> node.Children <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some children ->
<span style="color: rgba(0, 0, 255, 1)">if</span> isInterface <span style="color: rgba(0, 0, 255, 1)">then</span><span style="color: rgba(0, 0, 0, 1)">
children
</span>|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Kind = ReflectionKind.Method)
</span>|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -> x.InheritedFrom = None) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> exclude inhreited methods</span>
|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -> x.Overwrites = None) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> exclude overrites methods</span>
<span style="color: rgba(0, 0, 255, 1)">else</span> children |> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Kind = ReflectionKind.Method)
</span>| _ -> []</pre>
</div>
<p> </p>
<p>有一点要注意,就是对于 interface 来说,子 interface 无需重复父 interface 的成员,因此需要排除。</p>
<p>然后我们直接返回一个 record,代表该节点的实体即可。</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
Type = </span><span style="color: rgba(0, 0, 255, 1)">if</span> isInterface <span style="color: rgba(0, 0, 255, 1)">then</span> EntityType.Interface <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> EntityType.Class;
Namespace = </span><span style="color: rgba(0, 0, 255, 1)">if</span> section = <span style="color: rgba(128, 0, 0, 1)">""</span> <span style="color: rgba(0, 0, 255, 1)">then</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">TypedocConverter</span><span style="color: rgba(128, 0, 0, 1)">"</span> <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> section;
Name = node.Name;
Comment = comment;
Modifier = getModifier node.Flags;
InheritedFrom = exts;
Methods =
methods
</span>|<span style="color: rgba(0, 0, 0, 1)">> List.map (
</span><span style="color: rgba(0, 0, 255, 1)">fun</span> x ->
<span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> retType =
</span><span style="color: rgba(0, 0, 255, 1)">match</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(0, 0, 255, 1)">match</span> x.Signatures <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some signatures -><span style="color: rgba(0, 0, 0, 1)">
signatures </span>|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Kind = ReflectionKind.CallSignature)
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> [])
</span><span style="color: rgba(0, 0, 255, 1)">with</span>
| [] -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span>| (front::_) ->
<span style="color: rgba(0, 0, 255, 1)">match</span> front.Type <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some typeInfo -><span style="color: rgba(0, 0, 0, 1)"> getType typeInfo
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; InnerTypes = []; Name = None }
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> typeParameter =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> x.Signatures <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some (sigs::_) ->
<span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> types =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> sigs.TypeParameter <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some tp -><span style="color: rgba(0, 0, 0, 1)"> Some (getGenericTypeParameters tp)
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> None
</span><span style="color: rgba(0, 0, 255, 1)">match</span> types <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some result -><span style="color: rgba(0, 0, 0, 1)"> result
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
</span>|> List.map (<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Type)
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> parameters =
getMethodParameters
(</span><span style="color: rgba(0, 0, 255, 1)">match</span> x.Signatures <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some signatures -><span style="color: rgba(0, 0, 0, 1)">
signatures
</span>|> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Kind = ReflectionKind.CallSignature)
</span>|<span style="color: rgba(0, 0, 0, 1)">> List.map(
</span><span style="color: rgba(0, 0, 255, 1)">fun</span> x ->
<span style="color: rgba(0, 0, 255, 1)">match</span> x.Parameters <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some parameters -> parameters |> List.where(<span style="color: rgba(0, 0, 255, 1)">fun</span> p -><span style="color: rgba(0, 0, 0, 1)"> p.Kind = ReflectionKind.Parameter)
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
)
</span>|> List.reduce(<span style="color: rgba(0, 0, 255, 1)">fun</span> accu next -><span style="color: rgba(0, 0, 0, 1)"> accu @ next)
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> [])
{
Comment =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> x.Comment <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some comment -><span style="color: rgba(0, 0, 0, 1)"> getXmlDocComment comment
</span>| _ -> <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">
Modifier = </span><span style="color: rgba(0, 0, 255, 1)">if</span> isInterface <span style="color: rgba(0, 0, 255, 1)">then</span> [] <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> getModifier x.Flags;
Type = retType
Name = x.Name
TypeParameter = typeParameter
Parameter = parameters
}
);
Events =
events
</span>|<span style="color: rgba(0, 0, 0, 1)">> List.map (
</span><span style="color: rgba(0, 0, 255, 1)">fun</span> x ->
<span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> paras =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> x.Signatures <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some sigs -><span style="color: rgba(0, 0, 0, 1)">
sigs
</span>|> List.where (<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Kind = ReflectionKind.Event)
</span>|> List.map(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Parameters)
</span>|> List.collect (<span style="color: rgba(0, 0, 255, 1)">fun</span> x ->
<span style="color: rgba(0, 0, 255, 1)">match</span> x <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some paras -><span style="color: rgba(0, 0, 0, 1)"> paras
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> [])
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
{
Name = x.Name;
IsOptional =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> x.Flags.IsOptional <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some optional -><span style="color: rgba(0, 0, 0, 1)"> optional
</span>| _ -> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
;
DelegateType =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> paras <span style="color: rgba(0, 0, 255, 1)">with</span>
| (front::_) ->
<span style="color: rgba(0, 0, 255, 1)">match</span> front.Type <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some typeInfo -><span style="color: rgba(0, 0, 0, 1)"> getType typeInfo
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Delegate</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; Name = None; InnerTypes = [] }
</span>| _ ->
<span style="color: rgba(0, 0, 255, 1)">match</span> x.Type <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some typeInfo -><span style="color: rgba(0, 0, 0, 1)"> getType typeInfo
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">System.Delegate</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; Name = None; InnerTypes = [] }
;
Comment =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> x.Comment <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some comment -><span style="color: rgba(0, 0, 0, 1)"> getXmlDocComment comment
</span>| _ -> <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">
;
Modifier = </span><span style="color: rgba(0, 0, 255, 1)">if</span> isInterface <span style="color: rgba(0, 0, 255, 1)">then</span> [] <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> getModifier x.Flags;
}
);
Properties =
properties
</span>|<span style="color: rgba(0, 0, 0, 1)">> List.map (
</span><span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)">
{
Comment =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> x.Comment <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some comment -><span style="color: rgba(0, 0, 0, 1)"> getXmlDocComment comment
</span>| _ -> <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">
Modifier = </span><span style="color: rgba(0, 0, 255, 1)">if</span> isInterface <span style="color: rgba(0, 0, 255, 1)">then</span> [] <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> getModifier x.Flags;
Name = x.Name
Type =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> x.Type <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some typeInfo -><span style="color: rgba(0, 0, 0, 1)"> getType typeInfo
</span>| _ -> { Type = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">object</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">; Name = None; InnerTypes = [] }
WithGet = </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
WithSet = </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
IsOptional =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> x.Flags.IsOptional <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some optional -><span style="color: rgba(0, 0, 0, 1)"> optional
</span>| _ -> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">
;
InitialValue =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> x.DefaultValue <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some value -><span style="color: rgba(0, 0, 0, 1)"> Some value
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> None
}
);
Enums = [];
TypeParameter = genericType </span>|> List.map(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -><span style="color: rgba(0, 0, 0, 1)"> x.Type);
}</span></pre>
</div>
<p> </p>
</div>
<p>注意处理 event 的时候,委托的类型需要特殊处理一下。</p>
<h2>Type alias 生诚器</h2>
<p>还记得我们最上面说的一种特殊的 union types 吗?这里就是处理纯 string 的 type alias 的。</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">let</span> parseUnionTypeAlias (section: <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">) (node: Reflection) (nodes: Type list): Entity list =
</span><span style="color: rgba(0, 0, 255, 1)">let</span> notStringLiteral = nodes |> List.tryFind(<span style="color: rgba(0, 0, 255, 1)">fun</span> x -> x.Type <> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">stringLiteral</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> enums =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> notStringLiteral <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some _ -><span style="color: rgba(0, 0, 0, 1)">
printWarning (</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Type alias </span><span style="color: rgba(128, 0, 0, 1)">"</span> + node.Name + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"> is not supported.</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
[]
</span>| None -><span style="color: rgba(0, 0, 0, 1)">
nodes
</span>|<span style="color: rgba(0, 0, 0, 1)">> List.collect
(</span><span style="color: rgba(0, 0, 255, 1)">fun</span> x ->
<span style="color: rgba(0, 0, 255, 1)">match</span> x.Value <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some value -><span style="color: rgba(0, 0, 0, 1)">
[{
Name = toPascalCase value
Comment = </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">///<summary>\n</span><span style="color: rgba(128, 0, 0, 1)">"</span> + toCommentText value + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n///</summary></span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
Value = None
}]
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> enums = [] <span style="color: rgba(0, 0, 255, 1)">then</span><span style="color: rgba(0, 0, 0, 1)"> []
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
[
{
Namespace = section
Name = node.Name
Comment =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> node.Comment <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some comment -><span style="color: rgba(0, 0, 0, 1)"> getXmlDocComment comment
</span>| _ -> <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">
Methods = []
Events = []
Properties = []
Enums = enums
InheritedFrom = []
Type = EntityType.StringEnum
TypeParameter = []
Modifier = getModifier node.Flags
}
]
</span><span style="color: rgba(0, 0, 255, 1)">let</span> parseTypeAlias (section: <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">) (node: Reflection): Entity list =
</span><span style="color: rgba(0, 0, 255, 1)">let</span><span style="color: rgba(0, 0, 0, 1)"> typeInfo = node.Type
</span><span style="color: rgba(0, 0, 255, 1)">match</span> typeInfo <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some aliasType ->
<span style="color: rgba(0, 0, 255, 1)">match</span> aliasType.Type <span style="color: rgba(0, 0, 255, 1)">with</span>
| <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">union</span><span style="color: rgba(128, 0, 0, 1)">"</span> ->
<span style="color: rgba(0, 0, 255, 1)">match</span> aliasType.Types <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some types -><span style="color: rgba(0, 0, 0, 1)"> parseUnionTypeAlias section node types
</span>| _ -><span style="color: rgba(0, 0, 0, 1)">
printWarning (</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Type alias </span><span style="color: rgba(128, 0, 0, 1)">"</span> + node.Name + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"> is not supported.</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
[]
</span>| _ -><span style="color: rgba(0, 0, 0, 1)">
printWarning (</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Type alias </span><span style="color: rgba(128, 0, 0, 1)">"</span> + node.Name + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"> is not supported.</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
[]
</span>| _ -> []</pre>
</div>
<p> </p>
</div>
<h2>组合 Prasers</h2>
<p>我们最后将以上 parsers 组合起来就 ojbk 了:</p>
<div class="highlight">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">let</span> <span style="color: rgba(0, 0, 255, 1)">rec</span> parseNode (section: <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">) (node: Reflection): Entity list =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> node.Kind <span style="color: rgba(0, 0, 255, 1)">with</span>
| ReflectionKind.Global ->
<span style="color: rgba(0, 0, 255, 1)">match</span> node.Children <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some children -><span style="color: rgba(0, 0, 0, 1)"> parseNodes section children
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
</span>| ReflectionKind.Module ->
<span style="color: rgba(0, 0, 255, 1)">match</span> node.Children <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some children -><span style="color: rgba(0, 0, 0, 1)">
parseNodes (</span><span style="color: rgba(0, 0, 255, 1)">if</span> section = <span style="color: rgba(128, 0, 0, 1)">""</span> <span style="color: rgba(0, 0, 255, 1)">then</span> node.Name <span style="color: rgba(0, 0, 255, 1)">else</span> section + <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">.</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> + node.Name) children
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
</span>| ReflectionKind.ExternalModule ->
<span style="color: rgba(0, 0, 255, 1)">match</span> node.Children <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some children -><span style="color: rgba(0, 0, 0, 1)"> parseNodes section children
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
</span>| ReflectionKind.Enum -><span style="color: rgba(0, 0, 0, 1)">
</span>| ReflectionKind.Interface ->
</span>| ReflectionKind.Class ->
</span>| ReflectionKind.TypeAlias ->
<span style="color: rgba(0, 0, 255, 1)">match</span> node.Type <span style="color: rgba(0, 0, 255, 1)">with</span>
| Some _ -><span style="color: rgba(0, 0, 0, 1)"> parseTypeAlias section node
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
</span>| _ -><span style="color: rgba(0, 0, 0, 1)"> []
</span><span style="color: rgba(0, 0, 255, 1)">and</span><span style="color: rgba(0, 0, 0, 1)"> parseNodes section (nodes: Reflection list): Entity list =
</span><span style="color: rgba(0, 0, 255, 1)">match</span> nodes <span style="color: rgba(0, 0, 255, 1)">with</span>
| ([ front ]) -><span style="color: rgba(0, 0, 0, 1)"> parseNode section front
</span>| (front :: tails) -><span style="color: rgba(0, 0, 0, 1)">
parseNode section front @ parseNodes section tails
</span>| _ -> []</pre>
</div>
<p> </p>
</div>
<p>至此,我们的 parse 工作全部搞定,完结撒花~~~</p>
<h2>代码生成</h2>
<p>有了 C# 的实体类型,代码生成还困难吗?</p>
<p>不过有一点要注意的是,我们需要将名称转换为 Pascal Case,还需要生成 string literals union types 的 JsonConverter。不过这些都是样板代码,非常简单。</p>
<p>这里就不放代码了,感兴趣的同学可以自行去我的 GitHub 仓库查看。</p>
<h2>测试效果</h2>
<p>原 typescipt 代码:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">declare namespace test {
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* The declaration of an enum
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export enum MyEnum {
A </span>= 0<span style="color: rgba(0, 0, 0, 1)">,
B </span>= 1<span style="color: rgba(0, 0, 0, 1)">,
C </span>= 2<span style="color: rgba(0, 0, 0, 1)">,
D </span>=<span style="color: rgba(0, 0, 0, 1)"> C
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* The declaration of an interface
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export interface MyInterface1 {
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* A method
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
testMethod(arg: string, callback: () </span>=> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)">): string;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* An event
* @event
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
onTest(listener: (e: MyInterface1) </span>=> <span style="color: rgba(0, 0, 255, 1)">void</span>): <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* An property
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
readonly testProp: string;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* Another declaration of an interface
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export interface MyInterface2</span><T><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* A method
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
testMethod(arg: T, callback: () </span>=> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)">): T;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* An event
* @event
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
onTest(listener: (e: MyInterface2</span><T>) => <span style="color: rgba(0, 0, 255, 1)">void</span>): <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* An property
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
readonly testProp: T;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* The declaration of a class
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export class MyClass1</span><T><span style="color: rgba(0, 0, 0, 1)"> implements MyInterface1 {
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* A method
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
testMethod(arg: string, callback: () </span>=> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)">): string;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* An event
* @event
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
onTest(listener: (e: MyInterface1) </span>=> <span style="color: rgba(0, 0, 255, 1)">void</span>): <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* An property
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
readonly testProp: string;
static staticMethod(value: string, isOption</span>?: <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">): UnionStr;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* Another declaration of a class
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export class MyClass2</span><T> implements MyInterface2<T><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* A method
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
testMethod(arg: T, callback: () </span>=> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)">): T;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* An event
* @event
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
onTest(listener: (e: MyInterface2</span><T>) => <span style="color: rgba(0, 0, 255, 1)">void</span>): <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* An property
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
readonly testProp: T;
static staticMethod(value: string, isOption</span>?: <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">): UnionStr;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* The declaration of a type alias
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
export type UnionStr </span>= "A" | "B" | "C" | "other"<span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<p> </p>
<p>Typedoc 生成的 JSON 后,将其作为输入,生成 C# 代码:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> TypedocConverter.Test
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> The declaration of an enum
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">enum</span><span style="color: rgba(0, 0, 0, 1)"> MyEnum
{
A </span>= <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">,
B </span>= <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">,
C </span>= <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">,
D </span>= <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">
}
}
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> TypedocConverter.Test
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> The declaration of a class
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">class</span> MyClass1<T><span style="color: rgba(0, 0, 0, 1)"> : MyInterface1
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> An property
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
</span><span style="color: rgba(0, 0, 255, 1)">string</span> TestProp { <span style="color: rgba(0, 0, 255, 1)">get</span> => <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> System.NotImplementedException(); <span style="color: rgba(0, 0, 255, 1)">set</span> => <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> System.NotImplementedException(); }
</span><span style="color: rgba(0, 0, 255, 1)">event</span> System.Action<MyInterface1><span style="color: rgba(0, 0, 0, 1)"> OnTest;
</span><span style="color: rgba(0, 0, 255, 1)">string</span> TestMethod(<span style="color: rgba(0, 0, 255, 1)">string</span> arg, System.Action callback) => <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> System.NotImplementedException();
</span><span style="color: rgba(0, 0, 255, 1)">static</span> UnionStr StaticMethod(<span style="color: rgba(0, 0, 255, 1)">string</span> value, <span style="color: rgba(0, 0, 255, 1)">bool</span> isOption) => <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> System.NotImplementedException();
}
}
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> TypedocConverter.Test
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> Another declaration of a class
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">class</span> MyClass2<T> : MyInterface2<T><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> An property
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
T TestProp { </span><span style="color: rgba(0, 0, 255, 1)">get</span> => <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> System.NotImplementedException(); <span style="color: rgba(0, 0, 255, 1)">set</span> => <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> System.NotImplementedException(); }
</span><span style="color: rgba(0, 0, 255, 1)">event</span> System.Action<MyInterface2<T>><span style="color: rgba(0, 0, 0, 1)"> OnTest;
T TestMethod(T arg, System.Action callback) </span>=> <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> System.NotImplementedException();
</span><span style="color: rgba(0, 0, 255, 1)">static</span> UnionStr StaticMethod(<span style="color: rgba(0, 0, 255, 1)">string</span> value, <span style="color: rgba(0, 0, 255, 1)">bool</span> isOption) => <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> System.NotImplementedException();
}
}
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> TypedocConverter.Test
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> The declaration of an interface
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> MyInterface1
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> An property
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
</span><span style="color: rgba(0, 0, 255, 1)">string</span> TestProp { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">event</span> System.Action<MyInterface1><span style="color: rgba(0, 0, 0, 1)"> OnTest;
</span><span style="color: rgba(0, 0, 255, 1)">string</span> TestMethod(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> arg, System.Action callback);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> TypedocConverter.Test
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> Another declaration of an interface
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">interface</span> MyInterface2<T><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> An property
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
T TestProp { </span><span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">event</span> System.Action<MyInterface2<T>><span style="color: rgba(0, 0, 0, 1)"> OnTest;
T TestMethod(T arg, System.Action callback);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> TypedocConverter.Test
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> The declaration of a type alias
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
</span><span style="color: rgba(0, 0, 255, 1)">enum</span><span style="color: rgba(0, 0, 0, 1)"> UnionStr
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> A
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)"></summary></span>
A,
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> B
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)"></summary></span>
B,
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> C
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)"></summary></span>
C,
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> other
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(128, 128, 128, 1)"></summary></span>
Other
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> UnionStrConverter : Newtonsoft.Json.JsonConverter
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> CanConvert(System.Type t) => t == <span style="color: rgba(0, 0, 255, 1)">typeof</span>(UnionStr) || t == <span style="color: rgba(0, 0, 255, 1)">typeof</span>(UnionStr?<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">object</span> ReadJson(Newtonsoft.Json.JsonReader reader, System.Type t, <span style="color: rgba(0, 0, 255, 1)">object</span>?<span style="color: rgba(0, 0, 0, 1)"> existingValue, Newtonsoft.Json.JsonSerializer serializer)
</span>=> reader.TokenType <span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)">
{
Newtonsoft.Json.JsonToken.String </span>=><span style="color: rgba(0, 0, 0, 1)">
serializer.Deserialize</span><<span style="color: rgba(0, 0, 255, 1)">string</span>>(reader) <span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">A</span><span style="color: rgba(128, 0, 0, 1)">"</span> =><span style="color: rgba(0, 0, 0, 1)"> UnionStr.A,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">B</span><span style="color: rgba(128, 0, 0, 1)">"</span> =><span style="color: rgba(0, 0, 0, 1)"> UnionStr.B,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">C</span><span style="color: rgba(128, 0, 0, 1)">"</span> =><span style="color: rgba(0, 0, 0, 1)"> UnionStr.C,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Other</span><span style="color: rgba(128, 0, 0, 1)">"</span> =><span style="color: rgba(0, 0, 0, 1)"> UnionStr.Other,
_ </span>=> <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> System.Exception(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cannot unmarshal type UnionStr</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
},
_ </span>=> <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> System.Exception(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cannot unmarshal type UnionStr</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
};
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span> WriteJson(Newtonsoft.Json.JsonWriter writer, <span style="color: rgba(0, 0, 255, 1)">object</span>?<span style="color: rgba(0, 0, 0, 1)"> untypedValue, Newtonsoft.Json.JsonSerializer serializer)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (untypedValue <span style="color: rgba(0, 0, 255, 1)">is</span> <span style="color: rgba(0, 0, 255, 1)">null</span>) { serializer.Serialize(writer, <span style="color: rgba(0, 0, 255, 1)">null</span>); <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">var</span> value =<span style="color: rgba(0, 0, 0, 1)"> (UnionStr)untypedValue;
</span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (value)
{
</span><span style="color: rgba(0, 0, 255, 1)">case</span> UnionStr.A: serializer.Serialize(writer, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">A</span><span style="color: rgba(128, 0, 0, 1)">"</span>); <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> UnionStr.B: serializer.Serialize(writer, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">B</span><span style="color: rgba(128, 0, 0, 1)">"</span>); <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> UnionStr.C: serializer.Serialize(writer, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">C</span><span style="color: rgba(128, 0, 0, 1)">"</span>); <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> UnionStr.Other: serializer.Serialize(writer, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Other</span><span style="color: rgba(128, 0, 0, 1)">"</span>); <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">default</span>: <span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> System.Exception(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cannot marshal type UnionStr</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
}</span></pre>
</div>
<p> </p>
<h2>后记</h2>
<p>有了这个工具后,妈妈再也不用担心我封装 TypeScript 的库了。有了 TypedocConverter,任何 TypeScript 的库都能轻而易举地转换成 C# 的类型绑定,然后进行封装,非常方便。</p>
<p>感谢大家看到这里,最后,欢迎大家使用 TypedocConverter。当然,如果能 star 一波甚至贡献代码,我会非常感谢的!</p><br><br>
来源:https://www.cnblogs.com/hez2010/p/12246841.html
頁:
[1]