Skip to content

Commit 8230bc6

Browse files
blicklyjakebaileysandersn
authored
Drop unnecessary type arguments in the isolated declarations quick fix (#59665)
Co-authored-by: Jake Bailey <[email protected]> Co-authored-by: Nathan Shively-Sanders <[email protected]>
1 parent 52eaa7b commit 8230bc6

10 files changed

+167
-4
lines changed

src/compiler/checker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,6 +1641,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
16411641
getBaseTypeOfLiteralType,
16421642
getWidenedType,
16431643
getWidenedLiteralType,
1644+
fillMissingTypeArguments,
16441645
getTypeFromTypeNode: nodeIn => {
16451646
const node = getParseTreeNode(nodeIn, isTypeNode);
16461647
return node ? getTypeFromTypeNode(node) : errorType;

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5421,6 +5421,7 @@ export interface TypeChecker {
54215421
/** @internal */ isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node): boolean;
54225422
/** @internal */ typeHasCallOrConstructSignatures(type: Type): boolean;
54235423
/** @internal */ getSymbolFlags(symbol: Symbol): SymbolFlags;
5424+
/** @internal */ fillMissingTypeArguments(typeArguments: readonly Type[], typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[];
54245425
}
54255426

54265427
/** @internal */

src/services/codefixes/fixMissingTypeAnnotationOnExports.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import {
44
createImportAdder,
55
eachDiagnostic,
66
registerCodeFix,
7+
typeNodeToAutoImportableTypeNode,
78
typePredicateToAutoImportableTypeNode,
8-
typeToAutoImportableTypeNode,
9+
typeToMinimizedReferenceType,
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

Lines changed: 45 additions & 1 deletion
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,7 @@ import {
5960
isSetAccessorDeclaration,
6061
isStringLiteral,
6162
isTypeNode,
63+
isTypeReferenceNode,
6264
isTypeUsableAsPropertyName,
6365
isYieldExpression,
6466
LanguageServiceHost,
@@ -595,7 +597,15 @@ function createTypeParameterName(index: number) {
595597

596598
/** @internal */
597599
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);
600+
const typeNode = checker.typeToTypeNode(type, contextNode, flags, internalFlags, tracker);
601+
if (!typeNode) {
602+
return undefined;
603+
}
604+
return typeNodeToAutoImportableTypeNode(typeNode, importAdder, scriptTarget);
605+
}
606+
607+
/** @internal */
608+
export function typeNodeToAutoImportableTypeNode(typeNode: TypeNode, importAdder: ImportAdder, scriptTarget: ScriptTarget): TypeNode | undefined {
599609
if (typeNode && isImportTypeNode(typeNode)) {
600610
const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget);
601611
if (importableReference) {
@@ -608,6 +618,40 @@ export function typeToAutoImportableTypeNode(checker: TypeChecker, importAdder:
608618
return getSynthesizedDeepClone(typeNode);
609619
}
610620

621+
function endOfRequiredTypeParameters(checker: TypeChecker, type: GenericType): number {
622+
Debug.assert(type.typeArguments);
623+
const fullTypeArguments = type.typeArguments;
624+
const target = type.target;
625+
for (let cutoff = 0; cutoff < fullTypeArguments.length; cutoff++) {
626+
const typeArguments = fullTypeArguments.slice(0, cutoff);
627+
const filledIn = checker.fillMissingTypeArguments(typeArguments, target.typeParameters, cutoff, /*isJavaScriptImplicitAny*/ false);
628+
if (filledIn.every((fill, i) => fill === fullTypeArguments[i])) {
629+
return cutoff;
630+
}
631+
}
632+
// If we make it all the way here, all the type arguments are required.
633+
return fullTypeArguments.length;
634+
}
635+
636+
/** @internal */
637+
export function typeToMinimizedReferenceType(checker: TypeChecker, type: Type, contextNode: Node | undefined, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined {
638+
let typeNode = checker.typeToTypeNode(type, contextNode, flags, internalFlags, tracker);
639+
if (!typeNode) {
640+
return undefined;
641+
}
642+
if (isTypeReferenceNode(typeNode)) {
643+
const genericType = type as GenericType;
644+
if (genericType.typeArguments && typeNode.typeArguments) {
645+
const cutoff = endOfRequiredTypeParameters(checker, genericType);
646+
if (cutoff < typeNode.typeArguments.length) {
647+
const newTypeArguments = factory.createNodeArray(typeNode.typeArguments.slice(0, cutoff));
648+
typeNode = factory.updateTypeReferenceNode(typeNode, typeNode.typeName, newTypeArguments);
649+
}
650+
}
651+
}
652+
return typeNode;
653+
}
654+
611655
/** @internal */
612656
export function typePredicateToAutoImportableTypeNode(checker: TypeChecker, importAdder: ImportAdder, typePredicate: TypePredicate, contextNode: Node | undefined, scriptTarget: ScriptTarget, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined {
613657
let typePredicateNode = checker.typePredicateToTypePredicateNode(typePredicate, contextNode, flags, internalFlags, tracker);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
// @isolatedDeclarations: true
4+
// @declaration: true
5+
// @lib: es2015
6+
////let x: Iterator<number>;
7+
////export const y = x;
8+
9+
verify.codeFix({
10+
description: "Add annotation of type 'Iterator<number>'",
11+
index: 0,
12+
newFileContent:
13+
`let x: Iterator<number>;
14+
export const y: Iterator<number> = x;`,
15+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
// @isolatedDeclarations: true
4+
// @declaration: true
5+
6+
////export interface Foo<T, U = T[]> {}
7+
////export function foo(x: Foo<string>) {
8+
//// return x;
9+
////}
10+
11+
verify.codeFix({
12+
description: "Add return type 'Foo<string>'",
13+
index: 0,
14+
newFileContent:
15+
`export interface Foo<T, U = T[]> {}
16+
export function foo(x: Foo<string>): Foo<string> {
17+
return x;
18+
}`,
19+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
// In the abstract, we might prefer the inferred return type annotation to
4+
// be identical to the parameter type (with 2 type parameters).
5+
// Our current heuristic to avoid overly complex types in this case creates
6+
// "overly simple" types, but this tradeoff seems reasonable.
7+
8+
// @isolatedDeclarations: true
9+
// @declaration: true
10+
////export interface Foo<T, U = T[]> {}
11+
////export function foo(x: Foo<string, string[]>) {
12+
//// return x;
13+
////}
14+
15+
verify.codeFix({
16+
description: "Add return type 'Foo<string>'",
17+
index: 0,
18+
newFileContent:
19+
`export interface Foo<T, U = T[]> {}
20+
export function foo(x: Foo<string, string[]>): Foo<string> {
21+
return x;
22+
}`,
23+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
// Our current heursitic to avoid overly verbose generic types
4+
// doesn't handle generic types nested inside other types.
5+
6+
// @isolatedDeclarations: true
7+
// @declaration: true
8+
////export interface Foo<T, U = T[]> {}
9+
////export function foo(x: Map<number, Foo<string>>) {
10+
//// return x;
11+
////}
12+
13+
verify.codeFix({
14+
description: "Add return type 'Map<number, Foo<string, string[]>>'",
15+
index: 0,
16+
newFileContent:
17+
`export interface Foo<T, U = T[]> {}
18+
export function foo(x: Map<number, Foo<string>>): Map<number, Foo<string, string[]>> {
19+
return x;
20+
}`,
21+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
// @isolatedDeclarations: true
4+
// @declaration: true
5+
// @lib: es2015
6+
//// export function foo(x: Generator<number>) {
7+
//// return x;
8+
//// }
9+
10+
verify.codeFix({
11+
description: "Add return type 'Generator<number>'",
12+
index: 0,
13+
newFileContent:
14+
`export function foo(x: Generator<number>): Generator<number> {
15+
return x;
16+
}`
17+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
// @isolatedDeclarations: true
4+
// @declaration: true
5+
// @lib: es2015
6+
//// export function *foo() {
7+
//// yield 5;
8+
//// }
9+
10+
verify.codeFix({
11+
description: "Add return type 'Generator<number, void, unknown>'",
12+
index: 0,
13+
newFileContent:
14+
`export function *foo(): Generator<number, void, unknown> {
15+
yield 5;
16+
}`
17+
});

0 commit comments

Comments
 (0)