Node.js

[Node] TypeScript 中跨文件的类型推导

2020-10-21  本文已影响0人  何幻

背景

TypeScript 在使用 tsc 转译的时候,会调用 checkSourceFile 对源码进行类型检查。
在检查的过程中,会建立一堆 symbolLinks。

symbolLinks 的 target 字段保存了跨文件之符号的关联。
type 字段保存了当前符号相关的类型。

在 VSCode 中使用 tsserver 的时候,也会由 typescript-language-features 插件,
发送 geterr 消息,触发 CommandNames.Geterr 获取诊断信息,
从而间接调用 checkSourceFile 建立 symbolLinks。

在获取补全信息的时候,TypeScript 会先在表达式上进行类型推导,
利用保存在 symbolLinks 中的 type 信息,获取当前节点的类型,最后再获取该类型有关的补全信息。
如果事先没有 checkSourceFile,TypeScript 会重新计算 symbolLinks 中的 type 信息。

类型推导

示例代码在这里:github: debug-symbol-links/type.ts

我们调用 TypeScript Compiler API,还原了上文提到的类型推导全过程。
最后通过 type.getApparentProperties() 模拟 tsserver 得到了补全数据 propotherProp

我们来看下 TypeScript 是如何推导下述代码 index.ts 中 res 的类型信息的,

import { libCls as Cls } from './lib';
const ins = new Cls;
const res = ins.func();
res
export const libCls = class {
  func() {
    return {
      prop: 1,
      otherProp: 2,
    };
  }
};

TypeScript 从 index.ts 中的 res 不断挖掘,最终找到了 lib.ts 中 libCls 这个源头,

res -> ins.func() -> ins -> new Cls -> Cls -> libCls

先根据 res = ins.func(),知道 res 的类型等于 ins.func() 的类型,
而要得知 ins.func() 的类型,必先知道 ins 的类型,
ins 的类型等于 new Cls 的类型,所以还要先知道 Cls 的类型。

Cls 是由 lib.ts 导出的,TypeScript 会根据 ./lib 找到 lib.ts 的 sourceFile
继而找到 lib.ts 导出的所有变量,
所以要想知道 Cls 的类型,必先获得 libCls 的类型。

在 lib.ts 中,libCls 是由 class 表达式定义的,
于是 TypeScript 就知道了 libClstype

最后再回溯回去,在整条链路中,所有的符号相关的 type 都是可以确定的了。

symbolLinks.type

有了类型信息之后,TypeScript 会把它们保存在 symbolLinks 中。


function getTypeOfAlias(symbol) {
  var links = getSymbolLinks(symbol);
  if (!links.type) {
      var targetSymbol = resolveAlias(symbol);
      // It only makes sense to get the type of a value symbol. If the result of resolving
      // the alias is not a value, then it has no type. To get the type associated with a
      // type symbol, call getDeclaredTypeOfSymbol.
      // This check is important because without it, a call to getTypeOfSymbol could end
      // up recursively calling getTypeOfAlias, causing a stack overflow.
      links.type = targetSymbol.flags & 111551 /* Value */
          ? getTypeOfSymbol(targetSymbol)
          : errorType;
  }
  return links.type;
}

getTypeOfAlias 是 index.ts 和 lib.ts 中符号的分界点,
它本身是用来获取 alias symbol Cls 的类型。

首先,它会调用 resolveAlias 获取 Cls 关联 lib.ts 中的 libCls 符号。
并把 libCls 符号设置为 Cls 符号 symbolLinks 的 target 属性。
(这个过程可以参考 resolveAlias 的实现,具体做法是通过 ./lib 找到模块,然后找出模块导出的符号 libCls

然后调用 getTypeOfSymbol 获取 lib.ts 文件中 libCls 符号的类型 type
设置为 Cls 符号 symbolLinks 的 type 属性。

后续的操作中,TypeScript 会把整条链路中遇到的符号,都关联到相应的 type 上。
下图我们看到调用栈中,ins 符号 symbolLinks 的 type 属性也会被赋值。
![](https://img.haomeiwen.com/i1023733/40c929dece3e5bb4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

总结

上文分析了 TypeScript 进行跨文件类型推导的过程,
getTypeOfAliasresolveAlias 起着关键作用,
它们建立起了 index.ts 和 lib.ts 文件内符号的关联。

关联关系保存到了 symbolLinks 的 targettype 属性中,
本文着重讨论了 type 属性的计算过程。


参考

TypeScript v3.7.3

上一篇 下一篇

猜你喜欢

热点阅读