如何使用TypeScript AST解析泛型条件类型以获取变量真实类型?
When dealing with conditional types that reference type aliases (like BarTypeParent in your example), the key is to recursively resolve those aliases down to their underlying base types before evaluating the extends condition. Here's how to implement this with ts-morph:
Step 1: Build a Recursive Type Resolver
First, create a function that takes a TypeScript type and resolves all nested type aliases until it reaches a non-alias base type (like string, number, a class type, etc.).
import { Type, SyntaxKind, TypeAliasDeclaration } from "ts-morph"; /** * Recursively resolves a type alias to its underlying base type */ function resolveToBaseType(type: Type): Type { // Get the symbol associated with the type, if it exists const typeSymbol = type.getSymbol(); if (!typeSymbol) return type; // Find the first type alias declaration for this symbol const typeAliasDecl = typeSymbol.getDeclarations().find( dec => dec.isKind(SyntaxKind.TypeAliasDeclaration) ) as TypeAliasDeclaration | undefined; // If we found an alias, recursively resolve its target type if (typeAliasDecl) { return resolveToBaseType(typeAliasDecl.getType()); } // No alias found—return the original type return type; }
Step 2: Update Your Conditional Type Evaluation Logic
Modify your existing code to use this resolver when checking the extends clause in conditional types. Remember that in TypeScript's conditional types, T extends U checks assignability, not strict inheritance—so we'll use ts-morph's isAssignableTo method after resolving both types.
Here's how to integrate this with your existing flow:
// Assume you've already retrieved the variable's type (e.g., BarType<string> for barVar) const variableType = project.getSourceFile("your-file.ts")?.getVariableDeclaration("barVar")?.getType(); const genericType = variableType?.getAliasTypeArguments()[0].getType(); const conditionalType = genericType?.getConditionalType(); if (conditionalType) { const extendsClause = conditionalType.getExtendsClause(); // Resolve both sides of the extends clause to their base types const resolvedLeft = resolveToBaseType(extendsClause.getLeft().getType()); const resolvedRight = resolveToBaseType(extendsClause.getRight().getType()); // Check if the left type is assignable to the right (matches T extends U logic) const satisfiesExtends = resolvedLeft.isAssignableTo(resolvedRight); if (satisfiesExtends) { // Get the "true" branch type (e.g., string in BarType) const realType = conditionalType.getTrueType(); console.log(`Real type of barVar: ${realType.getText()}`); } else { // Get the "false" branch type (e.g., number in BarType) const realType = conditionalType.getFalseType(); console.log(`Real type of barVar: ${realType.getText()}`); } }
Step 3: Handle Edge Cases
This approach works for most common scenarios, but you may want to extend it for edge cases like:
- Nested type aliases:
type A = B; type B = string;will resolve correctly tostring - Generic type aliases:
type Wrapper<T> = T; type MyString = Wrapper<string>;will resolve tostring - Union/intersection types: If your alias points to a union (e.g.,
type MyUnion = string | number),resolveToBaseTypewill return the union directly, andisAssignableTowill handle it as expected
Explanation for Your Specific Example
For your BarType code:
type BarTypeParent = string; type BarType<T> = T extends BarTypeParent ? string : number; let barVar: BarType<string>;
- We resolve
BarTypeParentto its base typestring - We resolve the substituted
T(which isstring) to itself - Check if
stringis assignable tostring→ true - Return the true branch type
string, which is the real type ofbarVar
内容的提问来源于stack exchange,提问作者Gilles Fabre

