問題:以前に定義された条件型から型を派生させる多くの条件型を含むファイルで作業していますが、これは非常に複雑になり、型がどのように派生しているかをデバッグするのが困難になりました。
TypeScriptコンパイラーがどのようにして条件付きタイプを決定し、最終的なタイプを導出するためのパスを選択するかを「デバッグ」またはリストする方法を見つけようとしています。
コンパイラオプション を調べましたが、その領域にはまだ何も見つかりません...
私が今探しているものの類推は、DEBUG=express:*
高速サーバーの動作を確認したい場合に使用できる設定のタイプ。
ただし、私が解決しようとしている実際の問題は、大規模で複雑な階層型定義で型が派生する方法を分解およびデバッグできることです。
重要な注意:TypeScriptプロジェクトのランタイム実行をデバッグしようとしているのではありません。 TypeScriptコンパイラによって型が計算される方法をデバッグしようとしています。
TypeScriptには、必要な情報をログアウトするための組み込みのメカニズムはありません。ただし、内部の作業を理解することに興味がある場合は、実際に条件型の解決が行われるソースコード内の場所を以下に示します。
_checker.ts
_ でこれらの場所を見てください。
ln:13258 instantiateTypeWorker()
ln:12303 getConditionalType()
ln:12385 getTypeFromConditionalTypeNode()
ln:12772 getTypeFromTypeNode()
添付されているのは、私が不用意に組み立てた半完成のTypeScriptプラグインです。 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コードファイルを開き、条件付きタイプのノードにカーソルを合わせると、ログファイルに追加されたjooデータ構造体が表示されます。