Skip to content

Commit 0d1a49c

Browse files
author
Andy
authored
Ignore trailing comma when resolving signature for quick info (microsoft#25841)
* Ignore trailing comma when resolving signature for quick info * Add test for signature help
1 parent 114cd80 commit 0d1a49c

File tree

5 files changed

+64
-40
lines changed

5 files changed

+64
-40
lines changed

src/compiler/checker.ts

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,10 @@ namespace ts {
198198
},
199199
isContextSensitive,
200200
getFullyQualifiedName,
201-
getResolvedSignature: (nodeIn, candidatesOutArray, theArgumentCount) => {
202-
const node = getParseTreeNode(nodeIn, isCallLikeExpression);
203-
apparentArgumentCount = theArgumentCount;
204-
const res = node ? getResolvedSignature(node, candidatesOutArray) : undefined;
205-
apparentArgumentCount = undefined;
206-
return res;
207-
},
201+
getResolvedSignature: (node, candidatesOutArray, agumentCount) =>
202+
getResolvedSignatureWorker(node, candidatesOutArray, agumentCount, /*isForSignatureHelp*/ false),
203+
getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, agumentCount) =>
204+
getResolvedSignatureWorker(node, candidatesOutArray, agumentCount, /*isForSignatureHelp*/ true),
208205
getConstantValue: nodeIn => {
209206
const node = getParseTreeNode(nodeIn, canHaveConstantValue);
210207
return node ? getConstantValue(node) : undefined;
@@ -354,6 +351,14 @@ namespace ts {
354351
}
355352
};
356353

354+
function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, isForSignatureHelp: boolean): Signature | undefined {
355+
const node = getParseTreeNode(nodeIn, isCallLikeExpression);
356+
apparentArgumentCount = argumentCount;
357+
const res = node ? getResolvedSignature(node, candidatesOutArray, isForSignatureHelp) : undefined;
358+
apparentArgumentCount = undefined;
359+
return res;
360+
}
361+
357362
const tupleTypes = createMap<GenericType>();
358363
const unionTypes = createMap<UnionType>();
359364
const intersectionTypes = createMap<IntersectionType>();
@@ -17194,7 +17199,7 @@ namespace ts {
1719417199
const jsxStatelessElementType = getJsxStatelessElementTypeAt(openingLikeElement);
1719517200
if (jsxStatelessElementType) {
1719617201
// We don't call getResolvedSignature here because we have already resolve the type of JSX Element.
17197-
const callSignature = getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, /*candidatesOutArray*/ undefined);
17202+
const callSignature = getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, /*candidatesOutArray*/ undefined, /*isForSignatureHelp*/ false);
1719817203
if (callSignature !== unknownSignature) {
1719917204
const callReturnType = callSignature && getReturnTypeOfSignature(callSignature);
1720017205
let paramType = callReturnType && (callSignature!.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature!.parameters[0]));
@@ -17231,7 +17236,7 @@ namespace ts {
1723117236
if (jsxStatelessElementType) {
1723217237
// We don't call getResolvedSignature because here we have already resolve the type of JSX Element.
1723317238
const candidatesOutArray: Signature[] = [];
17234-
getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, candidatesOutArray);
17239+
getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, candidatesOutArray, /*isForSignatureHelp*/ false);
1723517240
let result: Type | undefined;
1723617241
let allMatchingAttributesType: Type | undefined;
1723717242
for (const candidate of candidatesOutArray) {
@@ -19094,7 +19099,7 @@ namespace ts {
1909419099
return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, paramCount, typeArguments.length);
1909519100
}
1909619101

19097-
function resolveCall(node: CallLikeExpression, signatures: ReadonlyArray<Signature>, candidatesOutArray: Signature[] | undefined, fallbackError?: DiagnosticMessage): Signature {
19102+
function resolveCall(node: CallLikeExpression, signatures: ReadonlyArray<Signature>, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean, fallbackError?: DiagnosticMessage): Signature {
1909819103
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
1909919104
const isDecorator = node.kind === SyntaxKind.Decorator;
1910019105
const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
@@ -19180,7 +19185,7 @@ namespace ts {
1918019185
// If we are in signature help, a trailing comma indicates that we intend to provide another argument,
1918119186
// so we will only accept overloads with arity at least 1 higher than the current number of provided arguments.
1918219187
const signatureHelpTrailingComma =
19183-
candidatesOutArray && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma;
19188+
isForSignatureHelp && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma;
1918419189

1918519190
// Section 4.12.1:
1918619191
// if the candidate list contains one or more signatures for which the type of each argument
@@ -19422,7 +19427,7 @@ namespace ts {
1942219427
return maxParamsIndex;
1942319428
}
1942419429

19425-
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined): Signature {
19430+
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
1942619431
if (node.expression.kind === SyntaxKind.SuperKeyword) {
1942719432
const superType = checkSuperExpression(node.expression);
1942819433
if (isTypeAny(superType)) {
@@ -19437,7 +19442,7 @@ namespace ts {
1943719442
const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!);
1943819443
if (baseTypeNode) {
1943919444
const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode);
19440-
return resolveCall(node, baseConstructors, candidatesOutArray);
19445+
return resolveCall(node, baseConstructors, candidatesOutArray, isForSignatureHelp);
1944119446
}
1944219447
}
1944319448
return resolveUntypedCall(node);
@@ -19490,7 +19495,7 @@ namespace ts {
1949019495
}
1949119496
return resolveErrorCall(node);
1949219497
}
19493-
return resolveCall(node, callSignatures, candidatesOutArray);
19498+
return resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
1949419499
}
1949519500

1949619501
/**
@@ -19504,7 +19509,7 @@ namespace ts {
1950419509
!numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & (TypeFlags.Union | TypeFlags.Never)) && isTypeAssignableTo(funcType, globalFunctionType);
1950519510
}
1950619511

19507-
function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined): Signature {
19512+
function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
1950819513
if (node.arguments && languageVersion < ScriptTarget.ES5) {
1950919514
const spreadIndex = getSpreadArgumentIndex(node.arguments);
1951019515
if (spreadIndex >= 0) {
@@ -19557,7 +19562,7 @@ namespace ts {
1955719562
return resolveErrorCall(node);
1955819563
}
1955919564

19560-
return resolveCall(node, constructSignatures, candidatesOutArray);
19565+
return resolveCall(node, constructSignatures, candidatesOutArray, isForSignatureHelp);
1956119566
}
1956219567

1956319568
// If expressionType's apparent type is an object type with no construct signatures but
@@ -19566,7 +19571,7 @@ namespace ts {
1956619571
// operation is Any. It is an error to have a Void this type.
1956719572
const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
1956819573
if (callSignatures.length) {
19569-
const signature = resolveCall(node, callSignatures, candidatesOutArray);
19574+
const signature = resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
1957019575
if (!isJavaScriptConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) {
1957119576
error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword);
1957219577
}
@@ -19673,7 +19678,7 @@ namespace ts {
1967319678
}
1967419679
}
1967519680

19676-
function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined): Signature {
19681+
function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
1967719682
const tagType = checkExpression(node.tag);
1967819683
const apparentType = getApparentType(tagType);
1967919684

@@ -19694,7 +19699,7 @@ namespace ts {
1969419699
return resolveErrorCall(node);
1969519700
}
1969619701

19697-
return resolveCall(node, callSignatures, candidatesOutArray);
19702+
return resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
1969819703
}
1969919704

1970019705
/**
@@ -19725,7 +19730,7 @@ namespace ts {
1972519730
/**
1972619731
* Resolves a decorator as if it were a call expression.
1972719732
*/
19728-
function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined): Signature {
19733+
function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
1972919734
const funcType = checkExpression(node.expression);
1973019735
const apparentType = getApparentType(funcType);
1973119736
if (apparentType === errorType) {
@@ -19754,7 +19759,7 @@ namespace ts {
1975419759
return resolveErrorCall(node);
1975519760
}
1975619761

19757-
return resolveCall(node, callSignatures, candidatesOutArray, headMessage);
19762+
return resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp, headMessage);
1975819763
}
1975919764

1976019765
/**
@@ -19779,32 +19784,32 @@ namespace ts {
1977919784
* @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service;
1978019785
* the function will fill it up with appropriate candidate signatures
1978119786
*/
19782-
function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[] | undefined): Signature | undefined {
19787+
function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature | undefined {
1978319788
Debug.assert(!(elementType.flags & TypeFlags.Union));
1978419789
const callSignatures = elementType && getSignaturesOfType(elementType, SignatureKind.Call);
1978519790
if (callSignatures && callSignatures.length > 0) {
19786-
return resolveCall(openingLikeElement, callSignatures, candidatesOutArray);
19791+
return resolveCall(openingLikeElement, callSignatures, candidatesOutArray, isForSignatureHelp);
1978719792
}
1978819793

1978919794
return undefined;
1979019795
}
1979119796

19792-
function resolveSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature {
19797+
function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
1979319798
switch (node.kind) {
1979419799
case SyntaxKind.CallExpression:
19795-
return resolveCallExpression(node, candidatesOutArray);
19800+
return resolveCallExpression(node, candidatesOutArray, isForSignatureHelp);
1979619801
case SyntaxKind.NewExpression:
19797-
return resolveNewExpression(node, candidatesOutArray);
19802+
return resolveNewExpression(node, candidatesOutArray, isForSignatureHelp);
1979819803
case SyntaxKind.TaggedTemplateExpression:
19799-
return resolveTaggedTemplateExpression(node, candidatesOutArray);
19804+
return resolveTaggedTemplateExpression(node, candidatesOutArray, isForSignatureHelp);
1980019805
case SyntaxKind.Decorator:
19801-
return resolveDecorator(node, candidatesOutArray);
19806+
return resolveDecorator(node, candidatesOutArray, isForSignatureHelp);
1980219807
case SyntaxKind.JsxOpeningElement:
1980319808
case SyntaxKind.JsxSelfClosingElement:
1980419809
// This code-path is called by language service
1980519810
const exprTypes = checkExpression(node.tagName);
1980619811
return forEachType(exprTypes, exprType => {
19807-
const sfcResult = getResolvedJsxStatelessFunctionSignature(node, exprType, candidatesOutArray);
19812+
const sfcResult = getResolvedJsxStatelessFunctionSignature(node, exprType, candidatesOutArray, isForSignatureHelp);
1980819813
if (sfcResult && sfcResult !== unknownSignature) {
1980919814
return sfcResult;
1981019815
}
@@ -19825,7 +19830,7 @@ namespace ts {
1982519830
* the function will fill it up with appropriate candidate signatures
1982619831
* @return a signature of the call-like expression or undefined if one can't be found
1982719832
*/
19828-
function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature {
19833+
function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, isForSignatureHelp = false): Signature {
1982919834
const links = getNodeLinks(node);
1983019835
// If getResolvedSignature has already been called, we will have cached the resolvedSignature.
1983119836
// However, it is possible that either candidatesOutArray was not passed in the first time,
@@ -19836,7 +19841,7 @@ namespace ts {
1983619841
return cached;
1983719842
}
1983819843
links.resolvedSignature = resolvingSignature;
19839-
const result = resolveSignature(node, candidatesOutArray);
19844+
const result = resolveSignature(node, candidatesOutArray, isForSignatureHelp);
1984019845
// If signature resolution originated in control flow type analysis (for example to compute the
1984119846
// assigned type in a flow assignment) we don't cache the result as it may be based on temporary
1984219847
// types from the control flow analysis.

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2972,6 +2972,7 @@ namespace ts {
29722972
* @param argumentCount Apparent number of arguments, passed in case of a possibly incomplete call. This should come from an ArgumentListInfo. See `signatureHelp.ts`.
29732973
*/
29742974
getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
2975+
/* @internal */ getResolvedSignatureForSignatureHelp(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
29752976
getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature | undefined;
29762977
isImplementationOfOverload(node: SignatureDeclaration): boolean | undefined;
29772978
isUndefinedSymbol(symbol: Symbol): boolean;

src/services/signatureHelp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ namespace ts.SignatureHelp {
6666
return undefined;
6767
}
6868
const candidates: Signature[] = [];
69-
const resolvedSignature = checker.getResolvedSignature(invocation.node, candidates, argumentInfo.argumentCount)!; // TODO: GH#18217
69+
const resolvedSignature = checker.getResolvedSignatureForSignatureHelp(invocation.node, candidates, argumentInfo.argumentCount)!; // TODO: GH#18217
7070
return candidates.length === 0 ? undefined : { candidates, resolvedSignature };
7171
}
7272
else if (invocation.kind === InvocationKind.TypeArgs) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////declare function f<T>(a: T): T;
4+
/////**/f(2,);
5+
6+
verify.quickInfoAt("", "function f<2>(a: 2): 2");

tests/cases/fourslash/trailingCommaSignatureHelp.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,24 @@
77
//// */
88
////function str(n: number, radix: number): string;
99
////function str(n: number, radix?: number): string { return ""; }
10+
////
11+
////str(1, /*a*/)
12+
////
13+
////declare function f<T>(a: T): T;
14+
////f(2, /*b*/);
1015

11-
edit.insert("str(1,");
12-
verify.signatureHelp({
13-
text: "str(n: number, radix: number): string",
14-
parameterName: "radix",
15-
parameterDocComment: "The radix",
16-
docComment: "Stringifies a number with radix",
17-
tags: [{ name: "param", text: "radix The radix" }],
18-
});
16+
verify.signatureHelp(
17+
{
18+
marker: "a",
19+
text: "str(n: number, radix: number): string",
20+
parameterName: "radix",
21+
parameterDocComment: "The radix",
22+
docComment: "Stringifies a number with radix",
23+
tags: [{ name: "param", text: "radix The radix" }],
24+
overloadsCount: 2,
25+
},
26+
{
27+
marker: "b",
28+
text: "f(a: {}): {}",
29+
},
30+
);

0 commit comments

Comments
 (0)