typescript get方法_.NET手撸绘制TypeScript类图——上篇

0f2447a47368912fe7aeaffa96d36e8f.png

.NET手撸绘制TypeScript类图——上篇

近年来随着交互界面的精细化,TypeScript越来越流行,前端的设计也越来复杂,而类图正是用简单的箭头和方块,反映对象与对象之间关系/依赖的好方式。许多工具都能生成C#类图,有些工具也能生成TypeScript类图,如tsuml,但存在一些局限性。

我们都是.NET开发,为啥不干脆就用.NET撸一个TypeScript类图呢?

说干就干!为了搞到类图,一共分两步走:

  1. 解析.ts文件,生成抽象语法树(AST),并转换为简单的属性方法等对象
  2. 将这个对象绘制出来

本文将分上下两部分,上篇将介绍我移植的一个.NET Standard 2.0TypeScript解析库,下篇将介绍如何将AST转换为真正的图,并实现一些基本的交互。

.ts文件生成抽象语法树

正常来说编译原理挺难的,但好在有人赶在了我的前头 。

TypeScript解析库

我在Github上找到了一个叫TypeScriptAST的项目,它刚好就能将.ts文件转换为AST。但它仅提供了.NET Framework版本。我看了一下实现方式,它是从微软官方的TypeScript仓库按源代码翻译的。其中Parse.cs高达近8000行代码,能把如此巨大的工作翻译完成,可见作者花了不少时间。

我拿了过来,稍微改造了一下,移植到了.NET CoreNuGet包地址为:

https://www.nuget.org/packages/Sdcb.TypeScriptAST/

我移植的这个版本源代码也开放到了Github,使用相同的Apache-2.0协议开源,开源项目链接如下:

https://github.com/sdcb/TypeScriptAST

虽然不知道是不是第一个移植的,但可以确定的是今后.NET Core也能解析TypeScript了:)

注意:官方没有提供TypeScript.NET解析工具,也没建议用.NET,使用ts解析是正常做法,官方的包用起来显然也更有自信——但这就是骚操作,不挑战一下怎么知道极限在哪呢?

简单使用

假如有如下TypeScript代码:

class Class1
{td: number = 3;ts: string = 'hello';doWork(): string {return `${3+this.td}-${this.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,可以将代码写得特别精练,最后可以达到“一行代码*”完成.tsAST的转换:

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:Usersdotnet-loversourcereposShootRShootRShootRClientShips", "*.ts")).Dump();

分析结果:

9d14da9eb907651fed9b14a4ad822cd7.png

成功找到了完整的7个类,并将类名字段名字段类型方法名方法参数返回值等信息都解析出来了。本还能做得更详细的,但这些可以留给读者完成。

总结

在本篇我们介绍了如何使用.NET解析TypeScript,并推荐了我移植的一个NuGet包:Sdcb.TypeScriptAST

下篇将在这篇的基础上,介绍如何使用代码将类图渲染出来。

本文所用到的完整代码,可以在我的Github仓库中下载: https://github.com/sdcb/blog-data/tree/master/2019/20191113-ts-uml-with-dotnet

喜欢的朋友 请关注我的微信公众号:【DotNet骚操作】

a4161dd6f383d39292a70dae48a4586e.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/564496.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring使用AspectJ开发AOP

AspectJ 是一个基于 Java 语言的 AOP 框架&#xff0c;它扩展了 Java 语言。Spring 2.0 以后&#xff0c;新增了对 AspectJ 方式的支持&#xff0c;新版本的 Spring 框架&#xff0c;建议使用 AspectJ 方式开发 AOP。 使用 AspectJ 开发 AOP 通常有两种方式&#xff1a; 基于 …

matlab特征方程的根,MATLAB 求解特征方程的根轨迹图稳定性分析

原文&#xff1a;http://tecdat.cn/?p3871根轨迹分析在下文中&#xff0c;我们提供了用于根轨迹分析的强大MATLAB命令的简要描述。读者可能想知道为什么当强大的MATLAB命令可用时&#xff0c;教师强调学习手工计算。对于给定的一组开环极点和零点&#xff0c;MATLAB立即绘制根…

python如何把一张图像的所有像素点的值都显示出来_情人节,教你用 Python 向女神表白...

点击上方 “AirPython”&#xff0c;选择 “加为星标”第一时间关注 Python 技术干货&#xff01;2020年&#xff0c;这个看起来如此浪漫的年份&#xff0c;你还是一个人吗&#xff1f;难不成我还能是一条狗&#xff1f;提醒你一下&#xff0c;后天就是 2月14日了。什么&#x…

Spring事务管理接口

Spring 的事务管理是基于 AOP 实现的&#xff0c;而 AOP 是以方法为单位的。Spring 的事务属性分别为传播行为、隔离级别、只读和超时属性&#xff0c;这些属性提供了事务应用的方法和描述策略。 在 Java EE 开发经常采用的分层模式中&#xff0c;Spring 的事务处理位于业务逻…

倒计时小工具_送你3个倒数计日的小程序,让你不再遗忘重要事

每天我们忙于工作&#xff0c;忙于生活&#xff0c;在很多重要事情&#xff0c;重要人的生日&#xff0c;以及重要有意义的日子总会在忙碌中被遗忘&#xff0c;那么这该怎么办呢&#xff1f;别紧张&#xff0c;小编为你带来3个倒数计日的小程序&#xff0c;让你不再遗忘重要事情…

Spring声明式事务管理

Spring 的事务管理有两种方式&#xff1a;一种是传统的编程式事务管理&#xff0c;即通过编写代码实现的事务管理&#xff1b;另一种是基于 AOP 技术实现的声明式事务管理。由于在实际开发中&#xff0c;编程式事务管理很少使用&#xff0c;所以我们只对 Spring 的声明式事务管…

无法检查指定的位置是否位于cfs上_打印机知识普及:七大原因导致的打印机无法打印及解决方法...

打印机无法打印的原因有很多&#xff0c;如果我们遇到打印机无法打印应该首先从简单到复杂入手。首先必须排除一些最简单的问题&#xff0c;比如打印机是否正常安装。另外打印机内部是不是已经放置有墨盒以及打印纸等&#xff0c;这些基本问题必须排除&#xff0c;另外还有一个…

Spring基于Annotation实现事务管理

在 Spring 中&#xff0c;除了使用基于 XML 的方式可以实现声明式事务管理以外&#xff0c;还可以通过 Annotation 注解的方式实现声明式事务管理。 使用 Annotation 的方式非常简单&#xff0c;只需要在项目中做两件事&#xff0c;具体如下。 1 在 Spring 容器中注册驱动&…

python扩展,用python扩展列

我试图在python中扩展数据帧的某一列。在r中,我将使用这个函数:在python中,我发现df.pivot_table(),但我刚发现一个错误:pandas.pivot_table(df, values Value, index[Day, Money, Product],columns[Account])^更新结果:数据帧没有更改。它只返回相同的数据文件而不进行扩展。考…

基于matlab的pcm系统仿真_深入理解基于RISC-V ISS Spike的仿真系统:探索Spike,pk和fesrv...

Spike, the RISC-V ISA Simulator, implements a functional model of one or more RISC-V processors.Spike is named after the golden spike used to celebrate the completion of the US transcontinental railway.一些同学初接触RISC-V&#xff0c;总逃脱不了被Rocketchip…

单机安装oracle,单机安装oracle系统

一、安装oracle6.9操作系统1、硬盘划分为30G(以下分区皆强制为主分区)/boot 200Mswap 4G/ 剩余全部空间安装下一步的步骤就不说了勾选图形选项基本系统--》备份客户端&#xff0c;大系统性能&#xff0c;存储工具&#xff0c;安全工具&#xff0c;性能工具&#xff0c;目录客户…

redis timeout设置多少合适_热水器怎么调温度?一般热水器温度设置多少度比较合适?...

对于热水器的温度设置您知道怎么操作吗&#xff1f;一般情况下电热水器设置多少度比较合适呢&#xff1f;今天蜜罐蚁装修网小编给大家介绍下热水器如何调节温度&#xff0c;以及热水器调节温度在什么范围比较合适。下面请看小编以美的电热水器某个型号产品举例图文讲解热水器调…

用swing设计一个打地鼠小游戏_这7个风靡欧美的英语小游戏,学会胜过刷100道题!...

精彩导读小编为大家搜罗了一些在国外家喻户晓的语言类小游戏。好的方法胜过刷上100道题&#xff0c;真正让孩子觉得好玩&#xff0c;教学才会事半功倍&#xff01;01Would You Rather...最近牛津大学的面试考题惊天地爆出了一题&#xff1a;你想要变成吸血鬼还是想要变成僵尸&a…

oracle数据库9i安装,Oracle 9i数据库服务器的安装和辅助软件安装教程

安装数据库服务器以Oracle 9i数据库服务器软件的安装过程为例&#xff0c;介绍数据库服务器的安装过程。14.3.1 安装数据库服务器系统环境数据库服务器安装之前&#xff0c;一般都需要检测系统安装环境&#xff0c;以避免系统不支持、内存不够、硬盘空间不足等情况发生。下面从…

oracle 触发器 行级,oracle的行级触发器使用

行级触发器&#xff1a;当触发器被触发时&#xff0c;要使用被插入、更新或删除的记录中的列值&#xff0c;有时要使用操作前、后列的值.:NEW 修饰符访问操作完成后列的值:OLD 修饰符访问操作完成前列的值例1: 建立一个触发器, 当职工表 emp 表被删除一条记录时&#xff0c;把被…

司铭宇老师:如何让企业销售培训效果落地

如何让企业销售培训效果落地 在企业销售培训中&#xff0c;我们经常听到一个词&#xff0c;那就是“落地”。所谓的“落地”&#xff0c;简单来说就是将培训中所学到的知识和技能转化为实际的工作行动&#xff0c;从而提高销售业绩。但是&#xff0c;如何才能让销售培训效果真…

linux 宽字符串,C语言中的多字节字符与宽字符

C语言原本是在英文环境中设计的&#xff0c;主要的字符集是7位的ASCII码&#xff0c;8位的byte(字节)是最常见的字符编码单位。但是国际化软件必须能够表示不同的字符&#xff0c;而这些字符数量庞大&#xff0c;无法使用一个字节编码。C95标准化了两种表示大型字符集的方法&am…

C++函数编译原理和成员函数的实现

C函数的编译 C中的函数在编译时会根据命名空间、类、参数签名等信息进行重新命名&#xff0c;形成新的函数名。这个重命名的过程是通过一个特殊的算法来实现的&#xff0c;称为 名字编码&#xff08;Name Mangling&#xff09;。 Name Mangling 是一种可逆的算法&#xff0c;…

linux 运行选择哪个cpu核,判断Linux进程在哪个CPU核运行的方法

问题&#xff1a;有一个Linux进程运行在多核处理器系统上&#xff0c;如何查看该进程运行在哪个CPU上&#xff1f;方法一&#xff1a;ps 命令可以告诉你每个进程/线程目前分配到的(在“PSR”列)CPU ID。ps -o pid,psr,comm -p 运行结果&#xff1a;PID PSR COMM5357 10 prog输…

C++对象数组

对象数组是什么 数组对象就是大批量实例化对象的一种方法&#xff0c;例如&#xff1a;Student stu 实例化对象&#xff0c;如果有好几百个对象应该怎么办&#xff1f;这时候就用到了对象数组&#xff0c;顾名思义&#xff0c;就是吧所有要实例化的对象都放到一个组里面&#…