Skip to content

Commit 52486ae

Browse files
author
Andy
authored
Improve isPossiblyTypeArgumentPosition (#25043)
* Improve isPossiblyTypeArgumentPosition * Update API (#24966) * Handle new Type<Type< * Add test for `new C < C<`
1 parent 97f10bc commit 52486ae

File tree

6 files changed

+59
-9
lines changed

6 files changed

+59
-9
lines changed

src/harness/fourslash.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4767,7 +4767,7 @@ namespace FourSlashInterface {
47674767
}
47684768

47694769
export interface VerifyCompletionsOptions {
4770-
readonly marker?: ArrayOrSingle<string>;
4770+
readonly marker?: ArrayOrSingle<string | FourSlash.Marker>;
47714771
readonly isNewIdentifierLocation?: boolean;
47724772
readonly exact?: ArrayOrSingle<ExpectedCompletionEntry>;
47734773
readonly includes?: ArrayOrSingle<ExpectedCompletionEntry>;

src/services/completions.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1060,7 +1060,7 @@ namespace ts.Completions {
10601060
const isImportType = isLiteralImportTypeNode(node);
10611061
const isTypeLocation = insideJsDocTagTypeExpression || (isImportType && !(node as ImportTypeNode).isTypeOf) || isPartOfTypeNode(node.parent);
10621062
const isRhsOfImportDeclaration = isInRightSideOfInternalImportEqualsDeclaration(node);
1063-
const allowTypeOrValue = isRhsOfImportDeclaration || (!isTypeLocation && isPossiblyTypeArgumentPosition(contextToken, sourceFile));
1063+
const allowTypeOrValue = isRhsOfImportDeclaration || (!isTypeLocation && isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker));
10641064
if (isEntityName(node) || isImportType) {
10651065
let symbol = typeChecker.getSymbolAtLocation(node);
10661066
if (symbol) {
@@ -1275,7 +1275,7 @@ namespace ts.Completions {
12751275

12761276
function filterGlobalCompletion(symbols: Symbol[]): void {
12771277
const isTypeOnlyCompletion = insideJsDocTagTypeExpression || !isContextTokenValueLocation(contextToken) && (isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken));
1278-
const allowTypes = isTypeOnlyCompletion || !isContextTokenValueLocation(contextToken) && isPossiblyTypeArgumentPosition(contextToken, sourceFile);
1278+
const allowTypes = isTypeOnlyCompletion || !isContextTokenValueLocation(contextToken) && isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker);
12791279
if (isTypeOnlyCompletion) keywordFilters = KeywordCompletionFilters.TypeKeywords;
12801280

12811281
filterMutate(symbols, symbol => {

src/services/signatureHelp.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ namespace ts.SignatureHelp {
103103
if (onlyUseSyntacticOwners && !lessThanFollowsCalledExpression(startingToken, sourceFile, invocation.called)) {
104104
return undefined;
105105
}
106-
const type = checker.getTypeAtLocation(invocation.called)!; // TODO: GH#18217
107-
const signatures = isNewExpression(invocation.called.parent) ? type.getConstructSignatures() : type.getCallSignatures();
108-
const candidates = signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= argumentInfo.argumentCount);
106+
const candidates = getPossibleGenericSignatures(invocation.called, argumentInfo.argumentCount, checker);
109107
return candidates.length === 0 ? undefined : { candidates, resolvedSignature: first(candidates) };
110108
}
111109
else {
@@ -261,7 +259,7 @@ namespace ts.SignatureHelp {
261259
};
262260
}
263261
else {
264-
const typeArgInfo = isPossiblyTypeArgumentPosition(node, sourceFile);
262+
const typeArgInfo = getPossibleTypeArgumentsInfo(node, sourceFile);
265263
if (typeArgInfo) {
266264
const { called, nTypeArguments } = typeArgInfo;
267265
const invocation: Invocation = { kind: InvocationKind.TypeArgs, called };

src/services/utilities.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -917,11 +917,25 @@ namespace ts {
917917
}
918918
}
919919

920+
export function isPossiblyTypeArgumentPosition(token: Node, sourceFile: SourceFile, checker: TypeChecker): boolean {
921+
const info = getPossibleTypeArgumentsInfo(token, sourceFile);
922+
return info !== undefined && (isPartOfTypeNode(info.called) ||
923+
getPossibleGenericSignatures(info.called, info.nTypeArguments, checker).length !== 0 ||
924+
isPossiblyTypeArgumentPosition(info.called, sourceFile, checker));
925+
}
926+
927+
export function getPossibleGenericSignatures(called: Expression, typeArgumentCount: number, checker: TypeChecker): ReadonlyArray<Signature> {
928+
const type = checker.getTypeAtLocation(called)!; // TODO: GH#18217
929+
const signatures = isNewExpression(called.parent) ? type.getConstructSignatures() : type.getCallSignatures();
930+
return signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= typeArgumentCount);
931+
}
932+
920933
export interface PossibleTypeArgumentInfo {
921934
readonly called: Identifier;
922935
readonly nTypeArguments: number;
923936
}
924-
export function isPossiblyTypeArgumentPosition(tokenIn: Node, sourceFile: SourceFile): PossibleTypeArgumentInfo | undefined {
937+
// Get info for an expression like `f <` that may be the start of type arguments.
938+
export function getPossibleTypeArgumentsInfo(tokenIn: Node, sourceFile: SourceFile): PossibleTypeArgumentInfo | undefined {
925939
let token: Node | undefined = tokenIn;
926940
// This function determines if the node could be type argument position
927941
// Since during editing, when type argument list is not complete,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////const x = 0;
4+
////type T = number;
5+
////function f(x: number) {}
6+
////function g<T>(x: T) {}
7+
////class C<T> {}
8+
9+
////x + {| "valueOnly": true |}
10+
////x < {| "valueOnly": true |}
11+
////f < {| "valueOnly": true |}
12+
////g < {| "valueOnly": false |}
13+
////const something: C<{| "valueOnly": false |};
14+
////const something2: C<C<{| "valueOnly": false |};
15+
////new C<{| "valueOnly": false |};
16+
////new C<C<{| "valueOnly": false |};
17+
////
18+
////declare const callAndConstruct: { new<T>(): callAndConstruct<T>; <T>(): string; };
19+
////interface callAndConstruct<T> {}
20+
////new callAndConstruct<callAndConstruct</*callAndConstruct*/
21+
22+
for (const marker of test.markers()) {
23+
if (marker.data && marker.data.valueOnly) {
24+
verify.completions({ marker, includes: "x", excludes: "T" });
25+
}
26+
else {
27+
verify.completions({ marker, includes: ["x", "T"] });
28+
}
29+
}
30+
31+
verify.signatureHelp({
32+
marker: "callAndConstruct",
33+
text: "callAndConstruct<T>(): string",
34+
parameterName: "T",
35+
parameterSpan: "T",
36+
parameterCount: 1,
37+
argumentCount: 1,
38+
});

tests/cases/fourslash/fourslash.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ declare namespace FourSlashInterface {
200200
assertHasRanges(ranges: Range[]): void;
201201
caretAtMarker(markerName?: string): void;
202202
completions(...options: {
203-
readonly marker?: ArrayOrSingle<string>,
203+
readonly marker?: ArrayOrSingle<string | Marker>,
204204
readonly isNewIdentifierLocation?: boolean;
205205
readonly exact?: ArrayOrSingle<ExpectedCompletionEntry>;
206206
readonly includes?: ArrayOrSingle<ExpectedCompletionEntry>;

0 commit comments

Comments
 (0)