问题:我正在处理一个包含许多条件类型的文件,这些条件类型是从先前定义的条件类型派生其类型的,这已经变得非常复杂,并且很难调试如何派生类型。
我正试图找到一种方法来“调试”或列出TypeScript编译器如何根据条件类型进行确定并选择一条路径来导出最终类型。
我已经浏览了compiler options,但在该区域还没有找到任何东西...
与我现在正在寻找的类比类似,相当于DEBUG=express:*
类型的设置,如果您想查看快速服务器在做什么,可以使用它。
但是,我要解决的实际问题是能够解构和调试大型复杂的分层类型定义中如何派生类型。
重要说明:我不是要调试TypeScript项目的运行时执行。我正在尝试调试TypeScript编译器如何计算类型。
答案 0 :(得分:1)
打字稿中没有任何内置机制可以注销所需的相关信息。但是,如果您想了解内部工作,这就是源代码中实际解决条件类型的地方。
看看checker.ts
中的这些地方。
ln:13258 instantiateTypeWorker()
ln:12303 getConditionalType()
ln:12385 getTypeFromConditionalTypeNode()
ln:12772 getTypeFromTypeNode()
附带的是我不小心将它们拼在一起的半成品打字稿插件。它注销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;
一些烦人的详细说明来使该插件运行:
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" }
TypeScript: Select TypeScript Version... -> Use Workspace Version
TypeScript: Restart TS Server
TypeScript: Open TS Server Log
"PLUGIN UP AND RUNNING"
,现在打开ts代码文件并将鼠标悬停在某些条件类型节点上,您应该会看到一个loooooong json数据结构已添加到日志文件中。