TypeScript Compiler API 使用简介
TypeScript 本身提供了一些 Compiler API,可以生成 TypeScript 代码对应的 AST 等操作。作为官方提供的编译工具,也可以说是目前唯一可用的工具了。TypeScript 官方有一个介绍 Compiler API 的 Wiki,但这个文档内容较少,还很晦涩,这里补充一些基本概念和使用例子。
基本概念
之前写过一篇文章,讲解了TypeScript的编译流程以及一些内部类,其中介绍了一些 TypeScript 中涉及到的类。这里再来简单说一下:
ts.SourceFile
SourceFile 代表一个源文件,可以把它理解为是 AST 的根节点。AST 的节点有一个基类:ts.Node
,SourceFile 继承了这个类。
ts.SyntaxKind
每个 AST 的节点都会有一个 kind 属性,对应 ts.SyntaxKind 类型,用来标记当前节点的种类。所有的种类可以通过ts.SyntaxKind 的定义来查看。
这里面有一个问题,就是 SyntaxKind 种类虽然有名称来进行释义,但是很多时候根据名称并不能确定对应的是哪种语法。
这个时候除了搜索 SyntaxKind 对应的关键词,就只能是根据猜测来尝试了。这里推荐TypeScript AST Viewer,这个工具可以在输入 TypeScript 代码后,实时看到对应的 AST 树。
创建一个 Program
TypeScript 在编译过程中会进行类型检查,而类型信息是需要结合多个文件得出的。例如对 A 文件进行类型检查时,需要知道 A 引入的 B 文件中的变量是什么类型。TypeScript 使用 Program 类来表示一个编译单元,一个 Program 是一些 SourceFile 的集合。
1 | const program = ts.createProgram(fileNames, options); |
fileNames 表示一些输入文件,TypeScript 会自动解析这些文件中的 import 等,把 fileNames 对应文件的引用文件也都引入进来。
从 Program 中获取 SourceFile
1 | program.getSourceFiles(); |
这里举个例子,左边我们有一个 index.ts 文件,这个文件引入了 ./test.ts 文件。右边是我们使用 ts.createProgram
的过程,可以看到我们只传入了 index.ts 。
把所有 SourceFile 打印出来,可以看到 Program 中引入了大量的文件,其中大部分都是 .d.ts 结尾的类型定义文件。
此外也包括了 index.ts 用到的 test.ts 文件。
使用 TypeChecker 来获取 AST 节点类型
当我们创建一个 Program 后可以使用 TypeChecker 检查代码中变量的类型信息。
1 | let checker = program.getTypeChecker(); |
TypeChecker 可以回答以下问题:
- 这个 Node 对应的 Symbol 是什么?
- 这个 Symbol 的类型是什么?
- 在这部分 AST 中,哪些 Symbol 是可见的?
- 这个文件有哪些 Error ?
TypeChecker 实例的使用很简单,它提供了大量的方法,例如:
1 | const symbol = typeChecker.getSymbolAtLocation(node.name); |
解析 TypeScript 代码,创建 AST
如果我们想要根据 TypeScript 代码创建 AST,修改 AST 后再得到 TypeScript 代码,不进行类型检查。那么就不需要创建 Program,直接创建一个 SourceFile。使用 ts.createSourceFile
方法可以创建一个 SourceFile。
1 | function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile; |
其中将 setParentNodes 设置为 true,那么每个节点会有父节点的信息,比较有用。
修改 TypeScript AST 代码
ts 中提供了一系列的 create 和 update 方法,来进行 AST 的创建和修改。
这些方法使用起来有些难度:
- 跟
ts.Syntax
中定义的类型不是一一对应的,在使用时需要根据名词来进行猜测。 - 跟 createProgram 等方法混在了一起,都在 ts 命名空间下。
- 没有找到什么文档。
好在都是有类型的,可以根据类型定义来传递需要的参数:
根据 AST 产出 TypeScript 代码
可以使用 printer 来根据 AST 产出 TypeScript 代码,注意不是产出 JavaScript 代码。
1 | const printer = ts.createPrinter(); |
第二个参数可以制定为 sourceFile 中的某个节点,可以直接产出这个节点的 TypeScript 代码。
1 | printer.printNode(ts.EmitHint.SourceFile, sourceFile.statements[0].expression, sourceFile) |
但是第二个参数不能传递任意节点,只能传递这些类型的节点:
1 | enum EmitHint { |
注意 ts.SourceFile 还有一个 sourceFile.getFullText() 方法, 这个方法只是把创建 sourceFile 时对应的 TypeScript 代码返回,并不会返回修改 AST 之后对应的代码。
简单的使用方法就是这样了,今天就先到这里,后续再补充更多细节。