Problem: I'm working on a file that has a lot of conditional types that derive their types from previously defined conditional types and this has become very complex and difficult to debug how a type is being derived.
I'm trying to find a way to "debug" or list how the TypeScript compiler is making its determination on a conditional type and picking a path to derive the ultimate type.
I've looked through the compiler options and have not found anything in that area yet...
An analogy to what I'm looking for right now is the equivalent of the DEBUG=express:*
type of setting that one might use if you wanted to see what an express server was doing.
However, the actual problem I'm trying to solve is to be able to deconstruct and debug how a type is derived in a large complex hierarchical type definition.
Important Note: I'm not trying to debug the runtime execution of TypeScript project. I'm trying to debug how the types are computed by the TypeScript compiler.
Use the typeof operator to check the type of a variable in TypeScript, e.g. if (typeof myVar === 'string') {} . The typeof operator returns a string that indicates the type of the value and can be used as a type guard in TypeScript.
TypeScript infers types of variables when there is no explicit information available in the form of type annotations. Types are inferred by TypeScript compiler when: Variables are initialized. Default values are set for parameters.
Code written in TypeScript is checked for errors before it is executed, during compile time.
TypeScript automatically finds type definitions under node_modules/@types , so there's no other step needed to get these types available in your program.
There isn't any built-in mechanism in typescript to log out the desired info in question. However, if you are interested in understanding internal work, here's the place in source code where the actually resolving of conditional types happens.
Take a look at these places in checker.ts
.
ln:13258 instantiateTypeWorker()
ln:12303 getConditionalType()
ln:12385 getTypeFromConditionalTypeNode()
ln:12772 getTypeFromTypeNode()
Attached is a half-done typescript plugin I put together carelessly. It logs out the raw data structure of a ConditionalType
. To understand this struct, check types.ts ln:4634.
UX of this plugin is terrible, but that struct does tell you how typescript decides the final value of a conditional type.
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;
Some annoyingly detailed instructions to get this plugin running:
mkdir my-ts-plugin && cd my-ts-plugin
touch package.json
and write { "name": "my-ts-plugin", "main": "index.js" }
yarn add typescript fast-safe-stringify
index.ts
, use tsc to compile it to index.js
yarn link
cd
to your own ts project's dir, run yarn link my-ts-plugin
{ "compilerOptions": { "plugins": [{ "name": "my-ts-plugin" }] } }
to your tsconfig.json
(.vscode/settings.json)
this line: { "typescript.tsdk": "<PATH_TO_YOUR_TS_PROJECT>/node_modules/typescript/lib" }
TypeScript: Select TypeScript Version... -> Use Workspace Version
TypeScript: Restart TS Server
TypeScript: Open TS Server Log
"PLUGIN UP AND RUNNING"
, now open a ts code file and mouse hover to some conditional type node, you should see a loooooong json data struct added to the log file.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With