如何查看TypeScript如何计算类型



问题:我正在处理一个文件,该文件有很多条件类型,这些类型是从以前定义的条件类型派生的,这变得非常复杂,很难调试如何派生类型。

我正试图找到一种方法来"调试"或列出TypeScript编译器是如何确定条件类型并选择派生最终类型的路径的。

我已经查看了编译器选项,但在这方面还没有发现任何内容。。。

与我现在正在寻找的类似之处是,如果您想了解express服务器在做什么,可以使用DEBUG=express:*类型的设置。

然而,我试图解决的实际问题是能够解构和调试在大型复杂层次类型定义中如何派生类型。

重要提示:我并没有试图调试TypeScript项目的运行时执行。我正在尝试调试TypeScript编译器是如何计算类型的。

typescript中没有任何内置机制来注销所需的相关信息。但是,如果您有兴趣了解内部工作,这里是源代码中实际解析条件类型的地方。

看看checker.ts中的这些地方。

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


附件是我不小心组装的一个半成品的打字脚本插件。它注销了ConditionalType的原始数据结构。要理解此结构,请检查types.ts ln:4634。

这个插件的用户体验很糟糕,但这个结构确实告诉你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代码文件并将鼠标悬停在某个条件类型节点上,您应该会看到一个loooooong json数据结构添加到日志文件中

我在使用条件类型时使用的一个技巧是用字符串文字替换值。

所以说你有:

type SomeComplexType<T> = T extends string ? /* Condition A */ : /* Condition B */

其中Condition ACondition B是两个深度嵌套的路径,充满了其他条件,我将其替换为:

type SomeComplexType<T> = T extends string ? "string" : "noString"

然后当使用它时,比如:

type result = SomeComplexType<"myComplexType">

您可以将鼠标悬停在result上,它将显示SomeComplexType的解析值,在本例中为"string""noString"。一旦您知道了解析的值,就可以将其替换为原始条件,并在更深一层执行相同的步骤。

这并不理想,但总比什么都没有好。

最新更新