Skip to content

Commit 6f8e37d

Browse files
committed
Add functionality to convert a ReferenceType into a "minimal" TypeNode
This is the TypeNode representation of a ReferenceType that includes the minimal number of typeArguments that are still semantically equivalent to the full type. Also use this functionality in the isolatedDeclaration autofixer to fix #58449
1 parent dd29423 commit 6f8e37d

File tree

4 files changed

+61
-4
lines changed

4 files changed

+61
-4
lines changed

src/compiler/checker.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1640,6 +1640,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
16401640
getBaseTypeOfLiteralType,
16411641
getWidenedType,
16421642
getWidenedLiteralType,
1643+
fillMissingTypeArguments,
16431644
getTypeFromTypeNode: nodeIn => {
16441645
const node = getParseTreeNode(nodeIn, isTypeNode);
16451646
return node ? getTypeFromTypeNode(node) : errorType;

src/compiler/types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -5031,6 +5031,9 @@ export interface TypeCheckerHost extends ModuleSpecifierResolutionHost {
50315031
}
50325032

50335033
export interface TypeChecker {
5034+
5035+
/** @internal */
5036+
fillMissingTypeArguments(typeArguments: readonly Type[], typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[];
50345037
getTypeOfSymbolAtLocation(symbol: Symbol, node: Node): Type;
50355038
getTypeOfSymbol(symbol: Symbol): Type;
50365039
getDeclaredTypeOfSymbol(symbol: Symbol): Type;

src/services/codefixes/fixMissingTypeAnnotationOnExports.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
eachDiagnostic,
66
registerCodeFix,
77
typePredicateToAutoImportableTypeNode,
8-
typeToAutoImportableTypeNode,
8+
typeToMinimizedReferenceType,
9+
typeNodeToAutoImportableTypeNode,
910
} from "../_namespaces/ts.codefix.js";
1011
import {
1112
ArrayBindingPattern,
@@ -1096,9 +1097,9 @@ function withContext<T>(
10961097
return emptyInferenceResult;
10971098
}
10981099

1099-
function typeToTypeNode(type: Type, enclosingDeclaration: Node, flags = NodeBuilderFlags.None) {
1100+
function typeToTypeNode(type: Type, enclosingDeclaration: Node, flags = NodeBuilderFlags.None): TypeNode|undefined {
11001101
let isTruncated = false;
1101-
const result = typeToAutoImportableTypeNode(typeChecker, importAdder, type, enclosingDeclaration, scriptTarget, declarationEmitNodeBuilderFlags | flags, declarationEmitInternalNodeBuilderFlags, {
1102+
const minimizedTypeNode = typeToMinimizedReferenceType(typeChecker, type, enclosingDeclaration, declarationEmitNodeBuilderFlags | flags, declarationEmitInternalNodeBuilderFlags, {
11021103
moduleResolverHost: program,
11031104
trackSymbol() {
11041105
return true;
@@ -1107,6 +1108,10 @@ function withContext<T>(
11071108
isTruncated = true;
11081109
},
11091110
});
1111+
if (!minimizedTypeNode) {
1112+
return undefined;
1113+
}
1114+
const result = typeNodeToAutoImportableTypeNode(minimizedTypeNode, importAdder, scriptTarget);
11101115
return isTruncated ? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) : result;
11111116
}
11121117

src/services/codefixes/helpers.ts

+49-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
flatMap,
2424
FunctionDeclaration,
2525
FunctionExpression,
26+
GenericType,
2627
GetAccessorDeclaration,
2728
getAllAccessorDeclarations,
2829
getCheckFlags,
@@ -59,6 +60,8 @@ import {
5960
isSetAccessorDeclaration,
6061
isStringLiteral,
6162
isTypeNode,
63+
isTypeReferenceNode,
64+
TypeReferenceNode,
6265
isTypeUsableAsPropertyName,
6366
isYieldExpression,
6467
LanguageServiceHost,
@@ -595,7 +598,15 @@ function createTypeParameterName(index: number) {
595598

596599
/** @internal */
597600
export function typeToAutoImportableTypeNode(checker: TypeChecker, importAdder: ImportAdder, type: Type, contextNode: Node | undefined, scriptTarget: ScriptTarget, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined {
598-
let typeNode = checker.typeToTypeNode(type, contextNode, flags, internalFlags, tracker);
601+
const typeNode = checker.typeToTypeNode(type, contextNode, flags, internalFlags, tracker);
602+
if (!typeNode) {
603+
return undefined;
604+
}
605+
return typeNodeToAutoImportableTypeNode(typeNode, importAdder, scriptTarget);
606+
}
607+
608+
/** @internal */
609+
export function typeNodeToAutoImportableTypeNode(typeNode: TypeNode, importAdder: ImportAdder, scriptTarget: ScriptTarget): TypeNode | undefined {
599610
if (typeNode && isImportTypeNode(typeNode)) {
600611
const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget);
601612
if (importableReference) {
@@ -608,6 +619,43 @@ export function typeToAutoImportableTypeNode(checker: TypeChecker, importAdder:
608619
return getSynthesizedDeepClone(typeNode);
609620
}
610621

622+
function endOfRequiredTypeParameters(checker: TypeChecker, type: GenericType, fullTypeArguments: readonly Type[]) : number {
623+
if (fullTypeArguments !== type.typeArguments!) {
624+
throw new Error('fullTypeArguments should be set')
625+
}
626+
const target = type.target;
627+
next_cutoff: for (let cutoff = 0; cutoff < fullTypeArguments.length; cutoff++) {
628+
const typeArguments = fullTypeArguments.slice(0, cutoff);
629+
const filledIn = checker.fillMissingTypeArguments(typeArguments, target.typeParameters, cutoff, /*isJavaScriptImplicitAny*/ false);
630+
for (let i = 0; i < filledIn.length; i++) {
631+
// If they don't match, then we haven't yet reached the right cutoff
632+
if (filledIn[i] !== fullTypeArguments[i]) continue next_cutoff;
633+
}
634+
return cutoff;
635+
}
636+
// If we make it all the way here, all the type arguments are required.
637+
return fullTypeArguments.length;
638+
}
639+
640+
export function typeToMinimizedReferenceType(checker: TypeChecker, type: Type, contextNode: Node | undefined, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined {
641+
const typeNode = checker.typeToTypeNode(type, contextNode, flags, internalFlags, tracker);
642+
if (!typeNode) {
643+
return undefined;
644+
}
645+
if (isTypeReferenceNode(typeNode)) {
646+
const genericType = type as GenericType;
647+
if (genericType.typeArguments) {
648+
const cutoff = endOfRequiredTypeParameters(checker, genericType, genericType.typeArguments);
649+
if (cutoff !== undefined && typeNode.typeArguments) {
650+
// Looks like the wrong way to do this. What APIs should I use here?
651+
(typeNode as any).typeArguments = typeNode.typeArguments.slice(0, cutoff);
652+
}
653+
}
654+
655+
}
656+
return typeNode;
657+
}
658+
611659
/** @internal */
612660
export function typePredicateToAutoImportableTypeNode(checker: TypeChecker, importAdder: ImportAdder, typePredicate: TypePredicate, contextNode: Node | undefined, scriptTarget: ScriptTarget, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined {
613661
let typePredicateNode = checker.typePredicateToTypePredicateNode(typePredicate, contextNode, flags, internalFlags, tracker);

0 commit comments

Comments
 (0)