Skip to content

Commit 8aeffba

Browse files
author
Andy Hanson
committed
Use isPossiblyTypeArgumentPosition and support new expressions
1 parent c6cc8e7 commit 8aeffba

File tree

3 files changed

+64
-35
lines changed

3 files changed

+64
-35
lines changed

src/services/signatureHelp.ts

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ namespace ts.SignatureHelp {
5959
}
6060
else {
6161
const type = checker.getTypeAtLocation(invocation.called);
62-
const candidates = type.getCallSignatures().filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= argumentInfo.argumentCount);
62+
const signatures = isNewExpression(invocation.called.parent) ? type.getConstructSignatures() : type.getCallSignatures();
63+
const candidates = signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= argumentInfo.argumentCount);
6364
return candidates.length === 0 ? undefined : { candidates, resolvedSignature: first(candidates) };
6465
}
6566
}
@@ -204,27 +205,17 @@ namespace ts.SignatureHelp {
204205
argumentCount: 1
205206
};
206207
}
207-
else if (isBinaryExpression(node.parent)) {
208-
const info = getInfoForBinaryExpressionResemblingTypeArguments(node.parent);
209-
if (!info) return undefined;
210-
const { called, nTypeArguments } = info;
211-
const argumentsSpan = createTextSpanFromBounds(called.getStart(sourceFile), node.end);
212-
return { kind: ArgumentListKind.TypeArguments, invocation: { kind: InvocationKind.TypeArgs, called }, argumentsSpan, argumentIndex: nTypeArguments - 1, argumentCount: nTypeArguments };
208+
else {
209+
const typeArgInfo = isPossiblyTypeArgumentPosition(node, sourceFile);
210+
if (typeArgInfo) {
211+
const { called, nTypeArguments } = typeArgInfo;
212+
const invocation: Invocation = { kind: InvocationKind.TypeArgs, called };
213+
const argumentsSpan = createTextSpanFromBounds(called.getStart(sourceFile), node.end);
214+
return { kind: ArgumentListKind.TypeArguments, invocation, argumentsSpan, argumentIndex: nTypeArguments, argumentCount: nTypeArguments + 1 };
215+
}
213216
}
214-
return undefined;
215-
}
216217

217-
/** Code like `f<T, U,` will be parsed as a series of `BinaryExpression`s. */
218-
function getInfoForBinaryExpressionResemblingTypeArguments(bin: BinaryExpression): { readonly called: Expression, readonly nTypeArguments: number } | undefined {
219-
switch (bin.operatorToken.kind) {
220-
case SyntaxKind.LessThanToken:
221-
return { called: bin.left, nTypeArguments: 1 };
222-
case SyntaxKind.CommaToken:
223-
const left = isBinaryExpression(bin.left) ? getInfoForBinaryExpressionResemblingTypeArguments(bin.left) : undefined;
224-
return left && { called: left.called, nTypeArguments: left.nTypeArguments + 1 };
225-
default:
226-
return undefined;
227-
}
218+
return undefined;
228219
}
229220

230221
function getArgumentIndex(argumentsList: Node, node: Node) {

src/services/utilities.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -912,21 +912,26 @@ namespace ts {
912912
}
913913
}
914914

915-
export function isPossiblyTypeArgumentPosition(token: Node, sourceFile: SourceFile) {
915+
export interface PossibleTypeArgumentInfo {
916+
readonly called: Identifier;
917+
readonly nTypeArguments: number;
918+
}
919+
export function isPossiblyTypeArgumentPosition(token: Node, sourceFile: SourceFile): PossibleTypeArgumentInfo | undefined {
916920
// This function determines if the node could be type argument position
917921
// Since during editing, when type argument list is not complete,
918922
// the tree could be of any shape depending on the tokens parsed before current node,
919923
// scanning of the previous identifier followed by "<" before current node would give us better result
920924
// Note that we also balance out the already provided type arguments, arrays, object literals while doing so
921925
let remainingLessThanTokens = 0;
926+
let nTypeArguments = 0;
922927
while (token) {
923928
switch (token.kind) {
924929
case SyntaxKind.LessThanToken:
925930
// Found the beginning of the generic argument expression
926931
token = findPrecedingToken(token.getFullStart(), sourceFile);
927-
const tokenIsIdentifier = token && isIdentifier(token);
928-
if (!remainingLessThanTokens || !tokenIsIdentifier) {
929-
return tokenIsIdentifier;
932+
if (!token || !isIdentifier(token)) return undefined;
933+
if (!remainingLessThanTokens) {
934+
return { called: token, nTypeArguments };
930935
}
931936
remainingLessThanTokens--;
932937
break;
@@ -947,25 +952,28 @@ namespace ts {
947952
// This can be object type, skip until we find the matching open brace token
948953
// Skip until the matching open brace token
949954
token = findPrecedingMatchingToken(token, SyntaxKind.OpenBraceToken, sourceFile);
950-
if (!token) return false;
955+
if (!token) return undefined;
951956
break;
952957

953958
case SyntaxKind.CloseParenToken:
954959
// This can be object type, skip until we find the matching open brace token
955960
// Skip until the matching open brace token
956961
token = findPrecedingMatchingToken(token, SyntaxKind.OpenParenToken, sourceFile);
957-
if (!token) return false;
962+
if (!token) return undefined;
958963
break;
959964

960965
case SyntaxKind.CloseBracketToken:
961966
// This can be object type, skip until we find the matching open brace token
962967
// Skip until the matching open brace token
963968
token = findPrecedingMatchingToken(token, SyntaxKind.OpenBracketToken, sourceFile);
964-
if (!token) return false;
969+
if (!token) return undefined;
965970
break;
966971

967972
// Valid tokens in a type name. Skip.
968973
case SyntaxKind.CommaToken:
974+
nTypeArguments++;
975+
break;
976+
969977
case SyntaxKind.EqualsGreaterThanToken:
970978

971979
case SyntaxKind.Identifier:
@@ -989,13 +997,13 @@ namespace ts {
989997
}
990998

991999
// Invalid token in type
992-
return false;
1000+
return undefined;
9931001
}
9941002

9951003
token = findPrecedingToken(token.getFullStart(), sourceFile);
9961004
}
9971005

998-
return false;
1006+
return undefined;
9991007
}
10001008

10011009
/**

tests/cases/fourslash/signatureHelpTypeArguments.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,59 @@
44
////declare function f<T extends number>(): void;
55
////declare function f<T, U>(): void;
66
////declare function f<T, U, V extends string>(): void;
7-
////f</*0*/;
8-
////f<number, /*1*/;
9-
////f<number, string, /*2*/;
7+
////f</*f0*/;
8+
////f<number, /*f1*/;
9+
////f<number, string, /*f2*/;
10+
////
11+
////declare const C: {
12+
//// new<T extends number>(): void;
13+
//// new<T, U>(): void;
14+
//// new<T, U, V extends string>(): void;
15+
////};
16+
////new C</*C0*/;
17+
////new C<number, /*C1*/;
18+
////new C<number, string, /*C2*/;
1019

1120
verify.signatureHelp(
1221
{
13-
marker: "0",
22+
marker: "f0",
1423
overloadsCount: 3,
1524
text: "f<T extends number>(): void",
1625
parameterName: "T",
1726
parameterSpan: "T extends number",
1827
},
1928
{
20-
marker: "1",
29+
marker: "f1",
2130
overloadsCount: 2,
2231
text: "f<T, U>(): void",
2332
parameterName: "U",
2433
parameterSpan: "U",
2534
},
2635
{
27-
marker: "2",
36+
marker: "f2",
2837
text: "f<T, U, V extends string>(): void",
2938
parameterName: "V",
3039
parameterSpan: "V extends string",
3140
},
41+
42+
{
43+
marker: "C0",
44+
overloadsCount: 3,
45+
text: "C<T extends number>(): void",
46+
parameterName: "T",
47+
parameterSpan: "T extends number",
48+
},
49+
{
50+
marker: "C1",
51+
overloadsCount: 2,
52+
text: "C<T, U>(): void",
53+
parameterName: "U",
54+
parameterSpan: "U",
55+
},
56+
{
57+
marker: "C2",
58+
text: "C<T, U, V extends string>(): void",
59+
parameterName: "V",
60+
parameterSpan: "V extends string",
61+
},
3262
);

0 commit comments

Comments
 (0)