web-dev-qa-db-fra.com

Comment puis-je voir comment TypeScript calcule les types?

Problème: je travaille sur un fichier contenant de nombreux types conditionnels qui dérivent leurs types de types conditionnels définis précédemment, ce qui est devenu très complexe et difficile à déboguer sur la façon dont un type est dérivé.

J'essaie de trouver un moyen de "déboguer" ou de lister comment le compilateur TypeScript effectue sa détermination sur un type conditionnel et choisit un chemin pour dériver le type ultime.

J'ai parcouru les options du compilateur et je n'ai encore rien trouvé dans ce domaine ...

Une analogie avec ce que je recherche en ce moment est l'équivalent du DEBUG=express:* type de paramètre que l'on pourrait utiliser si vous vouliez voir ce que faisait un serveur express.

Cependant, le problème réel que j'essaie de résoudre est de pouvoir déconstruire et déboguer comment un type est dérivé dans une grande définition de type hiérarchique complexe.

Remarque importante: je n'essaie pas de déboguer l'exécution au moment de l'exécution du projet TypeScript. J'essaie de déboguer la façon dont les types sont calculés par le compilateur TypeScript.

18
Guy

Il n'y a pas de mécanisme intégré dans TypeScript pour se déconnecter des informations souhaitées en question. Cependant, si vous êtes intéressé à comprendre le travail interne, voici l'endroit dans le code source où se déroule réellement la résolution des types conditionnels.

Jetez un oeil à ces endroits dans checker.ts .

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


Vous trouverez ci-joint un plug-in TypeScript à moitié fait que j'ai assemblé avec négligence. Il déconnecte la structure des données brutes d'un ConditionalType. Pour comprendre cette structure, vérifiez types.ts ln: 4634.

L'UX de ce plugin est terrible, mais cette structure vous indique comment TypeScript décide de la valeur finale d'un type conditionnel.

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;

Quelques instructions ennuyeusement détaillées pour faire fonctionner ce plugin:

  1. mkdir my-ts-plugin && cd my-ts-plugin
  2. touch package.json Et écrivez { "name": "my-ts-plugin", "main": "index.js" }
  3. yarn add TypeScript fast-safe-stringify
  4. copiez-collez cet extrait dans index.ts, utilisez tsc pour le compiler dans index.js
  5. yarn link
  6. maintenant cd dans le répertoire de votre propre projet ts, exécutez yarn link my-ts-plugin
  7. ajoutez { "compilerOptions": { "plugins": [{ "name": "my-ts-plugin" }] } } à votre tsconfig.json
  8. ajouter au paramètre d'espace de travail (.vscode/settings.json) cette ligne: { "TypeScript.tsdk": "<PATH_TO_YOUR_TS_PROJECT>/node_modules/TypeScript/lib" }
  9. ouvrez la palette de commandes vscode et exécutez:
    1. TypeScript: Select TypeScript Version... -> Use Workspace Version
    2. TypeScript: Restart TS Server
    3. TypeScript: Open TS Server Log
  10. vous devriez pouvoir voir le plug-in se déconnecter "PLUGIN UP AND RUNNING", maintenant ouvrir un fichier de code ts et survoler la souris vers un nœud de type conditionnel, vous devriez voir une structure de données loooooong json ajoutée au fichier journal.
1
hackape