typescript - 如何查看TypeScript如何计算类型?

标签 typescript typescript-generics

问题:我正在处理一个具有许多条件类型的文件,这些条件类型是从先前定义的条件类型派生其类型的,这已经变得非常复杂,并且难以调试如何派生类型。

我试图找到一种方法来“调试”或列出TypeScript编译器如何根据条件类型进行确定并选择一条路径来导出最终类型。

我已经浏览了compiler options,但在该区域还没有找到任何东西...

与我现在正在寻找的类比类似,如果您想查看 express 服务器在做什么,可以使用DEBUG=express:*类型的设置。

但是,我要解决的实际问题是能够解构和调试大型复杂的分层类型定义中如何派生类型。

重要说明:我不是要调试TypeScript项目的运行时执行。我正在尝试调试TypeScript编译器如何计算类型。

最佳答案

typescript 中没有任何内置机制可以注销所需的相关信息。但是,如果您想了解内部工作,这就是源代码中实际解决条件类型的地方。

看看 checker.ts 中的这些地方。

ln:13258 instantiateTypeWorker()ln:12303 getConditionalType()ln:12385 getTypeFromConditionalTypeNode()ln:12772 getTypeFromTypeNode()
附带的是我不小心将它们拼在一起的半成品 typescript 插件。它注销ConditionalType的原始数据结构。要了解此结构,请检查types.ts ln:4634。

这个插件的用户体验很糟糕,但是该结构确实告诉您typescript如何确定条件类型的最终值。

import stringify from "fast-safe-stringify";

function init(modules: {
  typescript: typeof import("typescript/lib/tsserverlibrary");
}) {
  const ts = modules.typescript;

  // #region utils
  function replacer(name, val) {
    if (name === "checker" || name === "parent") {
      return undefined;
    }
    return val;
  }

  function getContainingObjectLiteralElement(node) {
    var element = getContainingObjectLiteralElementWorker(node);
    return element &&
      (ts.isObjectLiteralExpression(element.parent) ||
        ts.isJsxAttributes(element.parent))
      ? element
      : undefined;
  }

  ts.getContainingObjectLiteralElement = getContainingObjectLiteralElement;
  function getContainingObjectLiteralElementWorker(node) {
    switch (node.kind) {
      case 10 /* StringLiteral */:
      case 14 /* NoSubstitutionTemplateLiteral */:
      case 8 /* NumericLiteral */:
        if (node.parent.kind === 153 /* ComputedPropertyName */) {
          return ts.isObjectLiteralElement(node.parent.parent)
            ? node.parent.parent
            : undefined;
        }
      // falls through
      case 75 /* Identifier */:
        return ts.isObjectLiteralElement(node.parent) &&
          (node.parent.parent.kind === 192 /* ObjectLiteralExpression */ ||
            node.parent.parent.kind === 272) /* JsxAttributes */ &&
          node.parent.name === node
          ? node.parent
          : undefined;
    }
    return undefined;
  }

  function getPropertySymbolsFromContextualType(
    node,
    checker,
    contextualType,
    unionSymbolOk
  ) {
    var name = ts.getNameFromPropertyName(node.name);
    if (!name) return ts.emptyArray;
    if (!contextualType.isUnion()) {
      var symbol = contextualType.getProperty(name);
      return symbol ? [symbol] : ts.emptyArray;
    }
    var discriminatedPropertySymbols = ts.mapDefined(
      contextualType.types,
      function(t) {
        return ts.isObjectLiteralExpression(node.parent) &&
          checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent)
          ? undefined
          : t.getProperty(name);
      }
    );
    if (
      unionSymbolOk &&
      (discriminatedPropertySymbols.length === 0 ||
        discriminatedPropertySymbols.length === contextualType.types.length)
    ) {
      var symbol = contextualType.getProperty(name);
      if (symbol) return [symbol];
    }
    if (discriminatedPropertySymbols.length === 0) {
      // Bad discriminant -- do again without discriminating
      return ts.mapDefined(contextualType.types, function(t) {
        return t.getProperty(name);
      });
    }
    return discriminatedPropertySymbols;
  }
  ts.getPropertySymbolsFromContextualType = getPropertySymbolsFromContextualType;

  function getNodeForQuickInfo(node) {
    if (ts.isNewExpression(node.parent) && node.pos === node.parent.pos) {
      return node.parent.expression;
    }
    return node;
  }
  // #endregion

  /**
   * plugin code starts here
   */
  function create(info: ts.server.PluginCreateInfo) {
    const log = (s: any) => {
      const prefix =
        ">>>>>>>> [TYPESCRIPT-FOOBAR-PLUGIN] <<<<<<<< \n";
      const suffix = "\n<<<<<<<<<<<";
      if (typeof s === "object") {
        s = stringify(s, null, 2);
      }
      info.project.projectService.logger.info(prefix + String(s) + suffix);
    };

    // Diagnostic logging
    log("PLUGIN UP AND RUNNING");

    // Set up decorator
    const proxy: ts.LanguageService = Object.create(null);
    for (let k of Object.keys(info.languageService) as Array<
      keyof ts.LanguageService
    >) {
      const x = info.languageService[k];
      proxy[k] = (...args: Array<{}>) => x.apply(info.languageService, args);
    }

    proxy.getQuickInfoAtPosition = (filename, position) => {
      var program = ts.createProgram(
        [filename],
        info.project.getCompilerOptions()
      );
      var sourceFiles = program.getSourceFiles();
      var sourceFile = sourceFiles[sourceFiles.length - 1];
      var checker = program.getDiagnosticsProducingTypeChecker();
      var node = ts.getTouchingPropertyName(sourceFile, position);
      var nodeForQuickInfo = getNodeForQuickInfo(node);
      var nodeType = checker.getTypeAtLocation(nodeForQuickInfo);

      let res;
      if (nodeType.flags & ts.TypeFlags.Conditional) {
        log(stringify(nodeType, replacer, 2));
      }

      if (!res)
        res = info.languageService.getQuickInfoAtPosition(filename, position);
      return res;
    };

    return proxy;
  }

  return { create };
}

export = init;


一些烦人的详细说明来使此插件运行:
  • mkdir my-ts-plugin && cd my-ts-plugin
  • touch package.json并编写{ "name": "my-ts-plugin", "main": "index.js" }
  • yarn add typescript fast-safe-stringify
  • 复制此代码段到index.ts,使用tsc将其编译为index.js
  • yarn link
  • 现在cd到您自己的ts项目的目录中,运行yarn link my-ts-plugin
  • { "compilerOptions": { "plugins": [{ "name": "my-ts-plugin" }] } }添加到您的tsconfig.json
  • 添加到工作区设置(.vscode/settings.json)此行:{ "typescript.tsdk": "<PATH_TO_YOUR_TS_PROJECT>/node_modules/typescript/lib" }
  • 打开vscode命令面板并运行:
  • TypeScript: Select TypeScript Version... -> Use Workspace Version
  • TypeScript: Restart TS Server
  • TypeScript: Open TS Server Log
  • 您应该能够看到插件注销"PLUGIN UP AND RUNNING",现在打开一个ts代码文件并将鼠标悬停在某些条件类型节点上,您应该看到将loooooong json数据结构添加到了日志文件中。
  • 关于typescript - 如何查看TypeScript如何计算类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58565584/

    相关文章:

    typescript - ./node_modules/graphql/index.mjs 中的错误 49 :0-53:205 Can't reexport the named export

    javascript - 如何使用 libphonenumber AsYouTypeFormatter 处理退格键

    typescript - 这个 `Type ' any[ ]' is not assignable to type ' [number]` TypeScript 错误的原因是什么?

    函数类型和参数类型映射之间的 typescript 匹配

    angular - TS2345 : Argument of type 'string' is not assignable to parameter of type 'never'

    angular - 如何以 Angular 访问 ElementRef.nativeElement 的嵌套元素

    typescript - 如何从 TypeScript 中的数字获取所有枚举标志?

    typescript - 在 typescript 中增强 vue.js

    reactjs - typescript 语法

    typescript - 确定泛型方法类型的默认参数