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; 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 : case 14 : case 8 : if (node.parent.kind === 153 ) { return ts.isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined; } case 75 : return ts.isObjectLiteralElement(node.parent) && (node.parent.parent.kind === 192 || node.parent.parent.kind === 272) && 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) { 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; } 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); }; log("PLUGIN UP AND RUNNING"); 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
- copy-paste this snippet to
index.ts
, use tsc to compile it to index.js
yarn link
- now
cd
to your own ts project's dir, run yarn link my-ts-plugin
- add
{ "compilerOptions": { "plugins": [{ "name": "my-ts-plugin" }] } }
to your tsconfig.json
- add to workspace setting
(.vscode/settings.json)
this line: { "typescript.tsdk": "<PATH_TO_YOUR_TS_PROJECT>/node_modules/typescript/lib" }
- open vscode command palette and run:
TypeScript: Select TypeScript Version... -> Use Workspace Version
TypeScript: Restart TS Server
TypeScript: Open TS Server Log
- you should be able to see the plugin log out
"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.