.NET手撸绘制TypeScript类图——上篇
.NET手擼繪制TypeScript類圖——上篇
近年來隨著交互界面的精細化,?TypeScript越來越流行,前端的設計也越來復雜,而?類圖正是用簡單的箭頭和方塊,反映對象與對象之間關系/依賴的好方式。許多工具都能生成?C#類圖,有些工具也能生成?TypeScript類圖,如?tsuml,但存在一些局限性。
我們都是?.NET開發,為啥不干脆就用?.NET擼一個?TypeScript類圖呢?
說干就干!為了搞到類圖,一共分兩步走:
解析?.ts文件,生成抽象語法樹(?AST),并轉換為簡單的?類、?屬性、?方法等對象
將這個對象繪制出來
本文將分上下兩篇,上篇將介紹我移植的一個.NET Standard 2.0的TypeScript解析庫,下篇將介紹如何將AST轉換為真正的圖,并實現一些基本的交互。
.ts文件生成抽象語法樹
正常來說編譯原理挺難的,但好在有人趕在了我的前頭😁。
TypeScript解析庫
我在?Github上找到了一個叫?TypeScriptAST的項目,它剛好就能將?.ts文件轉換為?AST。但它僅提供了?.NETFramework版本。我看了一下實現方式,它是從微軟官方的TypeScript倉庫按源代碼翻譯的。其中?Parse.cs高達近?8000行代碼,能把如此巨大的工作翻譯完成,可見作者花了不少時間。
我拿了過來,稍微改造了一下,移植到了?.NETCore。?NuGet包地址為:
https://www.nuget.org/packages/Sdcb.TypeScriptAST/
我移植的這個版本源代碼也開放到了?Github,使用相同的?Apache-2.0協議開源,開源項目鏈接如下:
https://github.com/sdcb/TypeScriptAST
雖然不知道是不是第一個移植的,但可以確定的是今后?.NETCore也能解析?TypeScript了:)
注意:官方沒有提供?TypeScript的?.NET解析工具,也沒建議用?.NET,使用?ts解析是正常做法,官方的包用起來顯然也更有自信——但這就是?騷操作,不挑戰一下怎么知道極限在哪呢?
簡單使用
假如有如下?TypeScript代碼:
class Class1 { td: number = 3; ts: string = 'hello'; doWork(): string { return `${3+this.td}-${ts}`; } } var tc = new Class1();我們可以使用?TypeScriptAST的類進行分析,只需使用?TypeScriptAST類:
var ast = new TypeScriptAST(source: tsSourceStringContent);該類有許多對象,提供了豐富的解析方式,使用如下代碼,即可將代碼中的類抽出來:
var classAsts = ast.OfKind(SyntaxKind.ClassDeclaration);由于?AST中的屬性太多,我們調試時抽重要的顯示出來,并轉換為?JSON:
JsonSerializer.Serialize(classAsts.Select(c => new { c.IdentifierStr, Children = c.Children.Skip(1).Select(x => x.IdentifierStr), }), new JsonSerializerOptions { WriteIndented = true}).Dump();結果如下:
[ { "IdentifierStr": "Class1", "Children": [ "td", "ts", "doWork" ] } ]有了這個,我們即可定義一些類型,用于后續繪制?AST:
class ClassDef { public string Name { get; set; } public List<PropertyDef> Properties { get; set; } public List<MethodDef> Methods { get; set; } } class PropertyDef { public string Name { get; set; } public bool IsPublic { get; set; } public bool IsStatic { get; set; } public string Type { get; set; } public override string ToString() => (IsPublic ? "+" : "-") + $" {Name}: " + (String.IsNullOrWhiteSpace(Type) ? "any" : Type); } class MethodDef { public string Name { get; set; } public bool IsPublic { get; set; } public bool IsStatic { get; set; } public List<ParameterDef> Parameters { get; set; } public string ReturnType { get; set; } public override string ToString() => (IsPublic ? "+" : "-") + $" {Name}({String.Join(", ", Parameters)})" + (Name == ".ctor" ? "" : $": {ReturnType}"); } class ParameterDef { public string Name { get; set; } public string Type { get; set; } public override string ToString() => $"{Name}: {Type}"; }借助于?.NET強大的?LINQ,可以將代碼寫得特別精練,最后可以達到“一行代碼”完成?.ts到?AST的轉換:
static Dictionary<string, ClassDef> ParseFiles(IEnumerable<string> files) => files .Select(x => new TypeScriptAST(File.ReadAllText(x), x)) .SelectMany(x => x.OfKind(SyntaxKind.ClassDeclaration)) .Select(x => new ClassDef { Name = x.OfKind(SyntaxKind.Identifier).FirstOrDefault().GetText(), Properties = x.OfKind(SyntaxKind.PropertyDeclaration) .Select(x => new PropertyDef { Name = x.IdentifierStr, IsPublic = x.First.Kind != SyntaxKind.PrivateKeyword, IsStatic = x.OfKind(SyntaxKind.StaticKeyword).Any(), Type = GetType(x), }).ToList(), Methods = x.OfKind(SyntaxKind.Constructor).Concat(x.OfKind(SyntaxKind.MethodDeclaration)) .Select(x => new MethodDef { Name = x is ConstructorDeclaration ctor ? ".ctor" : x.IdentifierStr, IsPublic = x.First.Kind != SyntaxKind.PrivateKeyword, IsStatic = x.OfKind(SyntaxKind.StaticKeyword).Any(), Parameters = ((ISignatureDeclaration)x).Parameters.Select(x => new ParameterDef { Name = x.OfKind(SyntaxKind.Identifier).FirstOrDefault().GetText(), Type = GetType(x), }).ToList(), ReturnType = GetReturnType(x), }).ToList(), }).ToDictionary(x => x.Name, v => v);兩個函數稍微提取一下,代碼能更精練:
static string GetReturnType(Node node) => node.Children.OfType<TypeNode>().FirstOrDefault()?.GetText(); static string GetType(Node node) => node switch { var x when x.OfKind(SyntaxKind.TypeReference).Any() => x.OfKind(SyntaxKind.TypeReference).First().GetText(), _ => node.Last switch { LiteralExpression literal => literal.Kind.ToString()[..^7].ToLower() switch { "numeric" => "number", var x => x, }, var x => x.GetText(), }, };使用
我對這個ShootR項目進行了分析,分析代碼如下:
ParseFiles(Directory.EnumerateFiles( path: @"C:\Users\dotnet-lover\source\repos\ShootR\ShootR\ShootR\Client\Ships", "*.ts") ).Dump();分析結果:?
成功找到了完整的?7個類,并將?類名、?字段名、?字段類型、?方法名、?方法參數和?返回值等信息都解析出來了。
總結
在本篇我們介紹了如何使用?.NET解析?TypeScript,并推薦了我移植的一個?NuGet包:?Sdcb.TypeScriptAST。
下篇將在這篇的基礎上,介紹如何使用代碼將類圖渲染出來。
本文所用到的完整代碼,可以在我的?Github倉庫中下載:https://github.com/sdcb/blog-data/tree/master/2019/20191113-ts-uml-with-dotnet
微信可能無法評論,有想法的朋友可以前往博客園點贊/評論:https://www.cnblogs.com/sdflysha/p/20191113-ts-uml-with-dotnet-1.html
?
總結
以上是生活随笔為你收集整理的.NET手撸绘制TypeScript类图——上篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TypeScript 3.7稳定版发布
- 下一篇: 阅读源码学设计模式-单例模式