0%

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 。

/images/2020/typescript-compiler-api/01.png

把所有 SourceFile 打印出来,可以看到 Program 中引入了大量的文件,其中大部分都是 .d.ts 结尾的类型定义文件。

/images/2020/typescript-compiler-api/02.png

此外也包括了 index.ts 用到的 test.ts 文件。

使用 TypeChecker 来获取 AST 节点类型

当我们创建一个 Program 后可以使用 TypeChecker 检查代码中变量的类型信息。

1
let checker = program.getTypeChecker();

TypeChecker 可以回答以下问题:

  • 这个 Node 对应的 Symbol 是什么?
  • 这个 Symbol 的类型是什么?
  • 在这部分 AST 中,哪些 Symbol 是可见的?
  • 这个文件有哪些 Error ?

TypeChecker 实例的使用很简单,它提供了大量的方法,例如:

1
2
3
const symbol = typeChecker.getSymbolAtLocation(node.name);

const nodeType = typeChecker.getTypeAtLocation(node);

解析 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 的创建和修改。

/images/2020/typescript-compiler-api/03.png

/images/2020/typescript-compiler-api/05.png

这些方法使用起来有些难度:

  1. ts.Syntax 中定义的类型不是一一对应的,在使用时需要根据名词来进行猜测。
  2. 跟 createProgram 等方法混在了一起,都在 ts 命名空间下。
  3. 没有找到什么文档。

好在都是有类型的,可以根据类型定义来传递需要的参数:

/images/2020/typescript-compiler-api/04.png

根据 AST 产出 TypeScript 代码

可以使用 printer 来根据 AST 产出 TypeScript 代码,注意不是产出 JavaScript 代码。

1
2
const printer = ts.createPrinter();
printer.printNode(ts.EmitHint.SourceFile, sourceFile, sourceFile);

第二个参数可以制定为 sourceFile 中的某个节点,可以直接产出这个节点的 TypeScript 代码。

1
printer.printNode(ts.EmitHint.SourceFile, sourceFile.statements[0].expression, sourceFile)

但是第二个参数不能传递任意节点,只能传递这些类型的节点:

1
2
3
4
5
6
7
8
9
enum EmitHint {
SourceFile = 0,
Expression = 1,
IdentifierName = 2,
MappedTypeParameter = 3,
Unspecified = 4,
EmbeddedStatement = 5,
JsxAttributeValue = 6
}

注意 ts.SourceFile 还有一个 sourceFile.getFullText() 方法, 这个方法只是把创建 sourceFile 时对应的 TypeScript 代码返回,并不会返回修改 AST 之后对应的代码。

简单的使用方法就是这样了,今天就先到这里,后续再补充更多细节。

梅旭光 wechat
欢迎扫码关注我的公众号