抽象语法树

AST 节点文档由两个关键部分构成。一是节点的 SyntaxKind 枚举,用于标识 AST 中的类型。二是其接口,即实例化 AST 时节点提供的 API。

这里是 interface Node 的一些关键成员:

  • TextRange 标识该节点在源文件中的起止位置。
  • SyntaxKind.SourceFile
  • interface SourceFile.
    每个 SourceFile 都是一棵 AST 的顶级节点,它们包含在 Program 中。

有个工具函数 ts.forEachChild,可以用来访问 AST 任一节点的所有子节点。

下面是简化的代码片段,用于演示如何工作:

该函数主要检查 node.kind 并据此判断 node 的接口,然后在其子节点上调用 cbNode。但是,要注意该函数不会为所有子节点调用 visitNode(例如:SyntaxKind.SemicolonToken)。想获得某 AST 节点的所有子节点,只要调用该节点的成员函数 .getChildren

如下函数会打印 AST 节点详细信息:

  1. console.log(new Array(depth + 1).join('----'), ts.syntaxKindToName(node.kind), node.pos, node.end);
  2. depth++;
  3. node.getChildren().forEach(c => printAllChildren(c, depth));
  4. }

SyntaxKind 被定义为一个常量枚举,如下所示:

这是个常量枚举,方便内联(例如:ts.SyntaxKind.EndOfFileToken 会变为 1),这样在使用 AST 时就不会有处理引用的额外开销。但编译时需要使用 —preserveConstEnums 编译标志,以便枚举在运行时仍可用。JavaScript 中你也可以根据需要使用 ts.SyntaxKind.EndOfFileToken。另外,可以用以下函数,将枚举成员转化为可读的字符串:

  1. export function syntaxKindToName(kind: ts.SyntaxKind) {
  2. return (<any>ts).SyntaxKind[kind];

杂项(Trivia)是指源文本中对正常理解代码不太重要的部分,例如:空白,注释,冲突标记。(为了保持轻量)杂项不会存储在 AST 中。但是可以视需要使用一些 ts.* API 来获取。

展示这些 API 前,你需要理解以下内容:

通常:

  • token 拥有它后面 同一行 到下一个 token 之前的所有杂项
  • 该行之后的注释都与下个的 token 相关
    对于文件中的前导(leading)和结束(ending)注释:

  • 而文件最后的一些列杂项则附加到文件结束符上,该 token 长度为 0

注释在多数基本使用中,都是让人关注的杂项。节点的注释可以通过以下函数获取:

假设下面是某个源文件的一部分:

function 而言,getLeadingCommentRanges 仅返回最后的两个注释 //bye/hi/。另外,而在 debugger 语句结束位置调用 getTrailingCommentRanges 会得到注释 /hello/

节点有所谓的 "token start" 和 "full start" 位置。

  • Token Start:比较自然的版本,即文件中一个 token 的文本开始的位置。
  • Full Start:是指扫描器从上一个重要 token 开始扫描的位置。
    AST 节点有 getStartgetFullStart API 用于获取以上两种位置,还是这个例子:
  1. debugger;/*hello*/
  2. //bye
  3. /*hi*/ function

function 而言,token start 即 function 的位置,而 full start 是 /hello/ 的位置。要注意,full start 甚至会包含前一节点拥有的杂项。