Ignore trailing comma when resolving signature for quick info (#25841)

* Ignore trailing comma when resolving signature for quick info

* Add test for signature help
This commit is contained in:
Andy 2018-07-31 11:39:39 -07:00 committed by GitHub
parent 114cd80ed9
commit 0d1a49c865
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 64 additions and 40 deletions

View file

@ -198,13 +198,10 @@ namespace ts {
}, },
isContextSensitive, isContextSensitive,
getFullyQualifiedName, getFullyQualifiedName,
getResolvedSignature: (nodeIn, candidatesOutArray, theArgumentCount) => { getResolvedSignature: (node, candidatesOutArray, agumentCount) =>
const node = getParseTreeNode(nodeIn, isCallLikeExpression); getResolvedSignatureWorker(node, candidatesOutArray, agumentCount, /*isForSignatureHelp*/ false),
apparentArgumentCount = theArgumentCount; getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, agumentCount) =>
const res = node ? getResolvedSignature(node, candidatesOutArray) : undefined; getResolvedSignatureWorker(node, candidatesOutArray, agumentCount, /*isForSignatureHelp*/ true),
apparentArgumentCount = undefined;
return res;
},
getConstantValue: nodeIn => { getConstantValue: nodeIn => {
const node = getParseTreeNode(nodeIn, canHaveConstantValue); const node = getParseTreeNode(nodeIn, canHaveConstantValue);
return node ? getConstantValue(node) : undefined; return node ? getConstantValue(node) : undefined;
@ -354,6 +351,14 @@ namespace ts {
} }
}; };
function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, isForSignatureHelp: boolean): Signature | undefined {
const node = getParseTreeNode(nodeIn, isCallLikeExpression);
apparentArgumentCount = argumentCount;
const res = node ? getResolvedSignature(node, candidatesOutArray, isForSignatureHelp) : undefined;
apparentArgumentCount = undefined;
return res;
}
const tupleTypes = createMap<GenericType>(); const tupleTypes = createMap<GenericType>();
const unionTypes = createMap<UnionType>(); const unionTypes = createMap<UnionType>();
const intersectionTypes = createMap<IntersectionType>(); const intersectionTypes = createMap<IntersectionType>();
@ -17194,7 +17199,7 @@ namespace ts {
const jsxStatelessElementType = getJsxStatelessElementTypeAt(openingLikeElement); const jsxStatelessElementType = getJsxStatelessElementTypeAt(openingLikeElement);
if (jsxStatelessElementType) { if (jsxStatelessElementType) {
// We don't call getResolvedSignature here because we have already resolve the type of JSX Element. // We don't call getResolvedSignature here because we have already resolve the type of JSX Element.
const callSignature = getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, /*candidatesOutArray*/ undefined); const callSignature = getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, /*candidatesOutArray*/ undefined, /*isForSignatureHelp*/ false);
if (callSignature !== unknownSignature) { if (callSignature !== unknownSignature) {
const callReturnType = callSignature && getReturnTypeOfSignature(callSignature); const callReturnType = callSignature && getReturnTypeOfSignature(callSignature);
let paramType = callReturnType && (callSignature!.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature!.parameters[0])); let paramType = callReturnType && (callSignature!.parameters.length === 0 ? emptyObjectType : getTypeOfSymbol(callSignature!.parameters[0]));
@ -17231,7 +17236,7 @@ namespace ts {
if (jsxStatelessElementType) { if (jsxStatelessElementType) {
// We don't call getResolvedSignature because here we have already resolve the type of JSX Element. // We don't call getResolvedSignature because here we have already resolve the type of JSX Element.
const candidatesOutArray: Signature[] = []; const candidatesOutArray: Signature[] = [];
getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, candidatesOutArray); getResolvedJsxStatelessFunctionSignature(openingLikeElement, elementType, candidatesOutArray, /*isForSignatureHelp*/ false);
let result: Type | undefined; let result: Type | undefined;
let allMatchingAttributesType: Type | undefined; let allMatchingAttributesType: Type | undefined;
for (const candidate of candidatesOutArray) { for (const candidate of candidatesOutArray) {
@ -19094,7 +19099,7 @@ namespace ts {
return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, paramCount, typeArguments.length); return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, paramCount, typeArguments.length);
} }
function resolveCall(node: CallLikeExpression, signatures: ReadonlyArray<Signature>, candidatesOutArray: Signature[] | undefined, fallbackError?: DiagnosticMessage): Signature { function resolveCall(node: CallLikeExpression, signatures: ReadonlyArray<Signature>, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean, fallbackError?: DiagnosticMessage): Signature {
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
const isDecorator = node.kind === SyntaxKind.Decorator; const isDecorator = node.kind === SyntaxKind.Decorator;
const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
@ -19180,7 +19185,7 @@ namespace ts {
// If we are in signature help, a trailing comma indicates that we intend to provide another argument, // If we are in signature help, a trailing comma indicates that we intend to provide another argument,
// so we will only accept overloads with arity at least 1 higher than the current number of provided arguments. // so we will only accept overloads with arity at least 1 higher than the current number of provided arguments.
const signatureHelpTrailingComma = const signatureHelpTrailingComma =
candidatesOutArray && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma; isForSignatureHelp && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma;
// Section 4.12.1: // Section 4.12.1:
// if the candidate list contains one or more signatures for which the type of each argument // if the candidate list contains one or more signatures for which the type of each argument
@ -19422,7 +19427,7 @@ namespace ts {
return maxParamsIndex; return maxParamsIndex;
} }
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined): Signature { function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
if (node.expression.kind === SyntaxKind.SuperKeyword) { if (node.expression.kind === SyntaxKind.SuperKeyword) {
const superType = checkSuperExpression(node.expression); const superType = checkSuperExpression(node.expression);
if (isTypeAny(superType)) { if (isTypeAny(superType)) {
@ -19437,7 +19442,7 @@ namespace ts {
const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!); const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!);
if (baseTypeNode) { if (baseTypeNode) {
const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode); const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode);
return resolveCall(node, baseConstructors, candidatesOutArray); return resolveCall(node, baseConstructors, candidatesOutArray, isForSignatureHelp);
} }
} }
return resolveUntypedCall(node); return resolveUntypedCall(node);
@ -19490,7 +19495,7 @@ namespace ts {
} }
return resolveErrorCall(node); return resolveErrorCall(node);
} }
return resolveCall(node, callSignatures, candidatesOutArray); return resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
} }
/** /**
@ -19504,7 +19509,7 @@ namespace ts {
!numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & (TypeFlags.Union | TypeFlags.Never)) && isTypeAssignableTo(funcType, globalFunctionType); !numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & (TypeFlags.Union | TypeFlags.Never)) && isTypeAssignableTo(funcType, globalFunctionType);
} }
function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined): Signature { function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
if (node.arguments && languageVersion < ScriptTarget.ES5) { if (node.arguments && languageVersion < ScriptTarget.ES5) {
const spreadIndex = getSpreadArgumentIndex(node.arguments); const spreadIndex = getSpreadArgumentIndex(node.arguments);
if (spreadIndex >= 0) { if (spreadIndex >= 0) {
@ -19557,7 +19562,7 @@ namespace ts {
return resolveErrorCall(node); return resolveErrorCall(node);
} }
return resolveCall(node, constructSignatures, candidatesOutArray); return resolveCall(node, constructSignatures, candidatesOutArray, isForSignatureHelp);
} }
// If expressionType's apparent type is an object type with no construct signatures but // If expressionType's apparent type is an object type with no construct signatures but
@ -19566,7 +19571,7 @@ namespace ts {
// operation is Any. It is an error to have a Void this type. // operation is Any. It is an error to have a Void this type.
const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
if (callSignatures.length) { if (callSignatures.length) {
const signature = resolveCall(node, callSignatures, candidatesOutArray); const signature = resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
if (!isJavaScriptConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) { if (!isJavaScriptConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) {
error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword);
} }
@ -19673,7 +19678,7 @@ namespace ts {
} }
} }
function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined): Signature { function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
const tagType = checkExpression(node.tag); const tagType = checkExpression(node.tag);
const apparentType = getApparentType(tagType); const apparentType = getApparentType(tagType);
@ -19694,7 +19699,7 @@ namespace ts {
return resolveErrorCall(node); return resolveErrorCall(node);
} }
return resolveCall(node, callSignatures, candidatesOutArray); return resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
} }
/** /**
@ -19725,7 +19730,7 @@ namespace ts {
/** /**
* Resolves a decorator as if it were a call expression. * Resolves a decorator as if it were a call expression.
*/ */
function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined): Signature { function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
const funcType = checkExpression(node.expression); const funcType = checkExpression(node.expression);
const apparentType = getApparentType(funcType); const apparentType = getApparentType(funcType);
if (apparentType === errorType) { if (apparentType === errorType) {
@ -19754,7 +19759,7 @@ namespace ts {
return resolveErrorCall(node); return resolveErrorCall(node);
} }
return resolveCall(node, callSignatures, candidatesOutArray, headMessage); return resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp, headMessage);
} }
/** /**
@ -19779,32 +19784,32 @@ namespace ts {
* @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service; * @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service;
* the function will fill it up with appropriate candidate signatures * the function will fill it up with appropriate candidate signatures
*/ */
function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[] | undefined): Signature | undefined { function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature | undefined {
Debug.assert(!(elementType.flags & TypeFlags.Union)); Debug.assert(!(elementType.flags & TypeFlags.Union));
const callSignatures = elementType && getSignaturesOfType(elementType, SignatureKind.Call); const callSignatures = elementType && getSignaturesOfType(elementType, SignatureKind.Call);
if (callSignatures && callSignatures.length > 0) { if (callSignatures && callSignatures.length > 0) {
return resolveCall(openingLikeElement, callSignatures, candidatesOutArray); return resolveCall(openingLikeElement, callSignatures, candidatesOutArray, isForSignatureHelp);
} }
return undefined; return undefined;
} }
function resolveSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature { function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
switch (node.kind) { switch (node.kind) {
case SyntaxKind.CallExpression: case SyntaxKind.CallExpression:
return resolveCallExpression(node, candidatesOutArray); return resolveCallExpression(node, candidatesOutArray, isForSignatureHelp);
case SyntaxKind.NewExpression: case SyntaxKind.NewExpression:
return resolveNewExpression(node, candidatesOutArray); return resolveNewExpression(node, candidatesOutArray, isForSignatureHelp);
case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.TaggedTemplateExpression:
return resolveTaggedTemplateExpression(node, candidatesOutArray); return resolveTaggedTemplateExpression(node, candidatesOutArray, isForSignatureHelp);
case SyntaxKind.Decorator: case SyntaxKind.Decorator:
return resolveDecorator(node, candidatesOutArray); return resolveDecorator(node, candidatesOutArray, isForSignatureHelp);
case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.JsxSelfClosingElement:
// This code-path is called by language service // This code-path is called by language service
const exprTypes = checkExpression(node.tagName); const exprTypes = checkExpression(node.tagName);
return forEachType(exprTypes, exprType => { return forEachType(exprTypes, exprType => {
const sfcResult = getResolvedJsxStatelessFunctionSignature(node, exprType, candidatesOutArray); const sfcResult = getResolvedJsxStatelessFunctionSignature(node, exprType, candidatesOutArray, isForSignatureHelp);
if (sfcResult && sfcResult !== unknownSignature) { if (sfcResult && sfcResult !== unknownSignature) {
return sfcResult; return sfcResult;
} }
@ -19825,7 +19830,7 @@ namespace ts {
* the function will fill it up with appropriate candidate signatures * the function will fill it up with appropriate candidate signatures
* @return a signature of the call-like expression or undefined if one can't be found * @return a signature of the call-like expression or undefined if one can't be found
*/ */
function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature { function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, isForSignatureHelp = false): Signature {
const links = getNodeLinks(node); const links = getNodeLinks(node);
// If getResolvedSignature has already been called, we will have cached the resolvedSignature. // If getResolvedSignature has already been called, we will have cached the resolvedSignature.
// However, it is possible that either candidatesOutArray was not passed in the first time, // However, it is possible that either candidatesOutArray was not passed in the first time,
@ -19836,7 +19841,7 @@ namespace ts {
return cached; return cached;
} }
links.resolvedSignature = resolvingSignature; links.resolvedSignature = resolvingSignature;
const result = resolveSignature(node, candidatesOutArray); const result = resolveSignature(node, candidatesOutArray, isForSignatureHelp);
// If signature resolution originated in control flow type analysis (for example to compute the // If signature resolution originated in control flow type analysis (for example to compute the
// assigned type in a flow assignment) we don't cache the result as it may be based on temporary // assigned type in a flow assignment) we don't cache the result as it may be based on temporary
// types from the control flow analysis. // types from the control flow analysis.

View file

@ -2972,6 +2972,7 @@ namespace ts {
* @param argumentCount Apparent number of arguments, passed in case of a possibly incomplete call. This should come from an ArgumentListInfo. See `signatureHelp.ts`. * @param argumentCount Apparent number of arguments, passed in case of a possibly incomplete call. This should come from an ArgumentListInfo. See `signatureHelp.ts`.
*/ */
getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined; getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
/* @internal */ getResolvedSignatureForSignatureHelp(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined;
getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature | undefined; getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature | undefined;
isImplementationOfOverload(node: SignatureDeclaration): boolean | undefined; isImplementationOfOverload(node: SignatureDeclaration): boolean | undefined;
isUndefinedSymbol(symbol: Symbol): boolean; isUndefinedSymbol(symbol: Symbol): boolean;

View file

@ -66,7 +66,7 @@ namespace ts.SignatureHelp {
return undefined; return undefined;
} }
const candidates: Signature[] = []; const candidates: Signature[] = [];
const resolvedSignature = checker.getResolvedSignature(invocation.node, candidates, argumentInfo.argumentCount)!; // TODO: GH#18217 const resolvedSignature = checker.getResolvedSignatureForSignatureHelp(invocation.node, candidates, argumentInfo.argumentCount)!; // TODO: GH#18217
return candidates.length === 0 ? undefined : { candidates, resolvedSignature }; return candidates.length === 0 ? undefined : { candidates, resolvedSignature };
} }
else if (invocation.kind === InvocationKind.TypeArgs) { else if (invocation.kind === InvocationKind.TypeArgs) {

View file

@ -0,0 +1,6 @@
/// <reference path='fourslash.ts'/>
////declare function f<T>(a: T): T;
/////**/f(2,);
verify.quickInfoAt("", "function f<2>(a: 2): 2");

View file

@ -7,12 +7,24 @@
//// */ //// */
////function str(n: number, radix: number): string; ////function str(n: number, radix: number): string;
////function str(n: number, radix?: number): string { return ""; } ////function str(n: number, radix?: number): string { return ""; }
////
////str(1, /*a*/)
////
////declare function f<T>(a: T): T;
////f(2, /*b*/);
edit.insert("str(1,"); verify.signatureHelp(
verify.signatureHelp({ {
text: "str(n: number, radix: number): string", marker: "a",
parameterName: "radix", text: "str(n: number, radix: number): string",
parameterDocComment: "The radix", parameterName: "radix",
docComment: "Stringifies a number with radix", parameterDocComment: "The radix",
tags: [{ name: "param", text: "radix The radix" }], docComment: "Stringifies a number with radix",
}); tags: [{ name: "param", text: "radix The radix" }],
overloadsCount: 2,
},
{
marker: "b",
text: "f(a: {}): {}",
},
);