web-dev-qa-db-ja.com

TypeScriptが型を計算する方法を確認するにはどうすればよいですか?

問題:以前に定義された条件型から型を派生させる多くの条件型を含むファイルで作業していますが、これは非常に複雑になり、型がどのように派生しているかをデバッグするのが困難になりました。

TypeScriptコンパイラーがどのようにして条件付きタイプを決定し、最終的なタイプを導出するためのパスを選択するかを「デバッグ」またはリストする方法を見つけようとしています。

コンパイラオプション を調べましたが、その領域にはまだ何も見つかりません...

私が今探しているものの類推は、DEBUG=express:*高速サーバーの動作を確認したい場合に使用できる設定のタイプ。

ただし、私が解決しようとしている実際の問題は、大規模で複雑な階層型定義で型が派生する方法を分解およびデバッグできることです。

重要な注意:TypeScriptプロジェクトのランタイム実行をデバッグしようとしているのではありません。 TypeScriptコンパイラによって型が計算される方法をデバッグしようとしています。

18
Guy

TypeScriptには、必要な情報をログアウトするための組み込みのメカニズムはありません。ただし、内部の作業を理解することに興味がある場合は、実際に条件型の解決が行われるソースコード内の場所を以下に示します。

_checker.ts_ でこれらの場所を見てください。

ln:13258 instantiateTypeWorker()
ln:12303 getConditionalType()
ln:12385 getTypeFromConditionalTypeNode()
ln:12772 getTypeFromTypeNode()


添付されているのは、私が不用意に組み立てた半完成のTypeScriptプラグインです。 ConditionalTypeの生データ構造をログアウトします。この構造体を理解するには、 types.ts ln:4634を確認してください。

このプラグインのUXはひどいですが、この構造体は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;_

このプラグインを実行するための厄介な詳細な手順:

  1. _mkdir my-ts-plugin && cd my-ts-plugin_
  2. _touch package.json_と記述_{ "name": "my-ts-plugin", "main": "index.js" }_
  3. _yarn add TypeScript fast-safe-stringify_
  4. このスニペットを_index.ts_にコピーして貼り付け、tscを使用して_index.js_にコンパイルします
  5. _yarn link_
  6. cdを自分のtsプロジェクトのディレクトリに移動し、_yarn link my-ts-plugin_を実行します
  7. _{ "compilerOptions": { "plugins": [{ "name": "my-ts-plugin" }] } }_を_tsconfig.json_に追加します
  8. ワークスペース設定に追加_(.vscode/settings.json)_この行:_{ "TypeScript.tsdk": "<PATH_TO_YOUR_TS_PROJECT>/node_modules/TypeScript/lib" }_
  9. vscodeコマンドパレットを開いて実行:
    1. _TypeScript: Select TypeScript Version... -> Use Workspace Version_
    2. _TypeScript: Restart TS Server_
    3. _TypeScript: Open TS Server Log_
  10. プラグインのログアウト_"PLUGIN UP AND RUNNING"_を確認できるはずです。tsコードファイルを開き、条件付きタイプのノードにカーソルを合わせると、ログファイルに追加されたjooデータ構造体が表示されます。
1
hackape