typescript - 如何查看 TypeScript 是如何计算类型的?
问题描述
问题:我正在处理一个有很多条件类型的文件,这些条件类型从先前定义的条件类型派生它们的类型,这变得非常复杂并且难以调试类型是如何派生的。
我试图找到一种“调试”或列出 TypeScript 编译器如何确定条件类型并选择派生最终类型的路径的方法。
我查看了编译器选项,但在该区域还没有找到任何东西......
与我现在正在寻找的内容类似,DEBUG=express:*
如果您想查看快速服务器在做什么,可能会使用的设置类型。
然而,我试图解决的实际问题是能够解构和调试在大型复杂分层类型定义中类型是如何派生的。
重要提示:我不是在尝试调试 TypeScript 项目的运行时执行。我正在尝试调试 TypeScript 编译器如何计算类型。
解决方案
打字稿中没有任何内置机制可以注销所需的相关信息。但是,如果您有兴趣了解内部工作,这里是源代码中实际解析条件类型的地方。
看看这些地方checker.ts
。
输入:13258instantiateTypeWorker()
输入:12303 输入getConditionalType()
:12385 输入getTypeFromConditionalTypeNode()
:12772getTypeFromTypeNode()
附上一个我不小心拼凑到一半的打字稿插件。它会注销 a 的原始数据结构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" }
- 打开 vscode 命令面板并运行:
TypeScript: Select TypeScript Version... -> Use Workspace Version
TypeScript: Restart TS Server
TypeScript: Open TS Server Log
- 您应该能够看到插件注销
"PLUGIN UP AND RUNNING"
,现在打开一个 ts 代码文件并将鼠标悬停在某个条件类型节点上,您应该会看到一个 loooooong json 数据结构添加到日志文件中。
推荐阅读
- python - 如何顺序处理多个标准输入到python?
- ios - App Review Information 部分未保存在 App Store Connect 中
- c++ - 如何比较两个字符串?这是我的代码,它没有给出想要的结果。它没有正确比较
- flutter - 如何从 Flutter 中的 URL 下载到记录器
- android - Android api 21/22 android version 5 视频播放问题, video play throws error 无法播放视频
- reactjs - 为什么在反应的“useState”钩子中一遍又一遍地设置初始状态
- django - 如何根据基于 django 的角色添加删除视图
- graphics - 如何将此 HLSL 像素着色器更正为四边形的圆角?
- javascript - 我的离线网站与离线网站的显示方式不同(图片不会显示)
- html - VS代码自动标记完成仅选择结束标记