美文网首页Node.js
[Node] TypeScript 中跨文件的类型推导

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

作者: 何幻 | 来源:发表于2020-10-21 13:43 被阅读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 的类型信息的,

    • 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 就知道了 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

    相关文章

      网友评论

        本文标题:[Node] TypeScript 中跨文件的类型推导

        本文链接:https://www.haomeiwen.com/subject/jjismktx.html