背景
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 得到了补全数据 prop
和 otherProp
。
我们来看下 TypeScript 是如何推导下述代码 index.ts 中 res
的类型信息的,
- index.ts
import { libCls as Cls } from './lib';
const ins = new Cls;
const res = ins.func();
res
- lib.ts
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 就知道了 libCls
的 type
。
最后再回溯回去,在整条链路中,所有的符号相关的 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 进行跨文件类型推导的过程,
getTypeOfAlias
和 resolveAlias
起着关键作用,
它们建立起了 index.ts 和 lib.ts 文件内符号的关联。
关联关系保存到了 symbolLinks 的 target
和 type
属性中,
本文着重讨论了 type
属性的计算过程。
网友评论