diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 9e8a2f14ed8..fa7cc42e0d1 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -170,7 +170,7 @@ namespace ts.SignatureHelp { TaggedTemplateArguments } - interface ArgumentListInfo { + export interface ArgumentListInfo { kind: ArgumentListKind; invocation: CallLikeExpression; argumentsSpan: TextSpan; @@ -188,7 +188,7 @@ namespace ts.SignatureHelp { return undefined; } - let argumentInfo = getContainingArgumentInfo(startingToken); + let argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile); cancellationToken.throwIfCancellationRequested(); // Semantic filtering of signature help @@ -205,431 +205,431 @@ namespace ts.SignatureHelp { // We didn't have any sig help items produced by the TS compiler. If this is a JS // file, then see if we can figure out anything better. if (isSourceFileJavaScript(sourceFile)) { - return createJavaScriptSignatureHelpItems(argumentInfo); + return createJavaScriptSignatureHelpItems(argumentInfo, program); } return undefined; } - return createSignatureHelpItems(candidates, resolvedSignature, argumentInfo); + return createSignatureHelpItems(candidates, resolvedSignature, argumentInfo, typeChecker); + } - function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo): SignatureHelpItems { - if (argumentInfo.invocation.kind !== SyntaxKind.CallExpression) { - return undefined; - } + function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program): SignatureHelpItems { + if (argumentInfo.invocation.kind !== SyntaxKind.CallExpression) { + return undefined; + } - // See if we can find some symbol with the call expression name that has call signatures. - let callExpression = argumentInfo.invocation; - let expression = callExpression.expression; - let name = expression.kind === SyntaxKind.Identifier - ? expression - : expression.kind === SyntaxKind.PropertyAccessExpression - ? (expression).name - : undefined; + // See if we can find some symbol with the call expression name that has call signatures. + let callExpression = argumentInfo.invocation; + let expression = callExpression.expression; + let name = expression.kind === SyntaxKind.Identifier + ? expression + : expression.kind === SyntaxKind.PropertyAccessExpression + ? (expression).name + : undefined; - if (!name || !name.text) { - return undefined; - } + if (!name || !name.text) { + return undefined; + } - let typeChecker = program.getTypeChecker(); - for (let sourceFile of program.getSourceFiles()) { - let nameToDeclarations = sourceFile.getNamedDeclarations(); - let declarations = getProperty(nameToDeclarations, name.text); + let typeChecker = program.getTypeChecker(); + for (let sourceFile of program.getSourceFiles()) { + let nameToDeclarations = sourceFile.getNamedDeclarations(); + let declarations = getProperty(nameToDeclarations, name.text); - if (declarations) { - for (let declaration of declarations) { - let symbol = declaration.symbol; - if (symbol) { - let type = typeChecker.getTypeOfSymbolAtLocation(symbol, declaration); - if (type) { - let callSignatures = type.getCallSignatures(); - if (callSignatures && callSignatures.length) { - return createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo); - } + if (declarations) { + for (let declaration of declarations) { + let symbol = declaration.symbol; + if (symbol) { + let type = typeChecker.getTypeOfSymbolAtLocation(symbol, declaration); + if (type) { + let callSignatures = type.getCallSignatures(); + if (callSignatures && callSignatures.length) { + return createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo, typeChecker); } } } } } } + } - /** - * Returns relevant information for the argument list and the current argument if we are - * in the argument of an invocation; returns undefined otherwise. - */ - function getImmediatelyContainingArgumentInfo(node: Node): ArgumentListInfo { - if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) { - let callExpression = node.parent; - // There are 3 cases to handle: - // 1. The token introduces a list, and should begin a sig help session - // 2. The token is either not associated with a list, or ends a list, so the session should end - // 3. The token is buried inside a list, and should give sig help - // - // The following are examples of each: - // - // Case 1: - // foo<#T, U>(#a, b) -> The token introduces a list, and should begin a sig help session - // Case 2: - // fo#o#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end - // Case 3: - // foo(a#, #b#) -> The token is buried inside a list, and should give sig help - // Find out if 'node' is an argument, a type argument, or neither - if (node.kind === SyntaxKind.LessThanToken || - node.kind === SyntaxKind.OpenParenToken) { - // Find the list that starts right *after* the < or ( token. - // If the user has just opened a list, consider this item 0. - let list = getChildListThatStartsWithOpenerToken(callExpression, node, sourceFile); - let isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos; - Debug.assert(list !== undefined); - return { - kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments, - invocation: callExpression, - argumentsSpan: getApplicableSpanForArguments(list), - argumentIndex: 0, - argumentCount: getArgumentCount(list) - }; - } - - // findListItemInfo can return undefined if we are not in parent's argument list - // or type argument list. This includes cases where the cursor is: - // - To the right of the closing paren, non-substitution template, or template tail. - // - Between the type arguments and the arguments (greater than token) - // - On the target of the call (parent.func) - // - On the 'new' keyword in a 'new' expression - let listItemInfo = findListItemInfo(node); - if (listItemInfo) { - let list = listItemInfo.list; - let isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos; - - let argumentIndex = getArgumentIndex(list, node); - let argumentCount = getArgumentCount(list); - - Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, - `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); - - return { - kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments, - invocation: callExpression, - argumentsSpan: getApplicableSpanForArguments(list), - argumentIndex: argumentIndex, - argumentCount: argumentCount - }; - } - } - else if (node.kind === SyntaxKind.NoSubstitutionTemplateLiteral && node.parent.kind === SyntaxKind.TaggedTemplateExpression) { - // Check if we're actually inside the template; - // otherwise we'll fall out and return undefined. - if (isInsideTemplateLiteral(node, position)) { - return getArgumentListInfoForTemplate(node.parent, /*argumentIndex*/ 0); - } - } - else if (node.kind === SyntaxKind.TemplateHead && node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) { - let templateExpression = node.parent; - let tagExpression = templateExpression.parent; - Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); - - let argumentIndex = isInsideTemplateLiteral(node, position) ? 0 : 1; - - return getArgumentListInfoForTemplate(tagExpression, argumentIndex); - } - else if (node.parent.kind === SyntaxKind.TemplateSpan && node.parent.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) { - let templateSpan = node.parent; - let templateExpression = templateSpan.parent; - let tagExpression = templateExpression.parent; - Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); - - // If we're just after a template tail, don't show signature help. - if (node.kind === SyntaxKind.TemplateTail && !isInsideTemplateLiteral(node, position)) { - return undefined; - } - - let spanIndex = templateExpression.templateSpans.indexOf(templateSpan); - let argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node); - - return getArgumentListInfoForTemplate(tagExpression, argumentIndex); - } - - return undefined; - } - - function getArgumentIndex(argumentsList: Node, node: Node) { - // The list we got back can include commas. In the presence of errors it may - // also just have nodes without commas. For example "Foo(a b c)" will have 3 - // args without commas. We want to find what index we're at. So we count - // forward until we hit ourselves, only incrementing the index if it isn't a - // comma. + /** + * Returns relevant information for the argument list and the current argument if we are + * in the argument of an invocation; returns undefined otherwise. + */ + function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo { + if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) { + let callExpression = node.parent; + // There are 3 cases to handle: + // 1. The token introduces a list, and should begin a sig help session + // 2. The token is either not associated with a list, or ends a list, so the session should end + // 3. The token is buried inside a list, and should give sig help // - // Note: the subtlety around trailing commas (in getArgumentCount) does not apply - // here. That's because we're only walking forward until we hit the node we're - // on. In that case, even if we're after the trailing comma, we'll still see - // that trailing comma in the list, and we'll have generated the appropriate - // arg index. - let argumentIndex = 0; - let listChildren = argumentsList.getChildren(); - for (let child of listChildren) { - if (child === node) { - break; - } - if (child.kind !== SyntaxKind.CommaToken) { - argumentIndex++; - } - } - - return argumentIndex; - } - - function getArgumentCount(argumentsList: Node) { - // The argument count for a list is normally the number of non-comma children it has. - // For example, if you have "Foo(a,b)" then there will be three children of the arg - // list 'a' '' 'b'. So, in this case the arg count will be 2. However, there - // is a small subtlety. If you have "Foo(a,)", then the child list will just have - // 'a' ''. So, in the case where the last child is a comma, we increase the - // arg count by one to compensate. + // The following are examples of each: // - // Note: this subtlety only applies to the last comma. If you had "Foo(a,," then - // we'll have: 'a' '' '' - // That will give us 2 non-commas. We then add one for the last comma, givin us an - // arg count of 3. - let listChildren = argumentsList.getChildren(); - - let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken); - if (listChildren.length > 0 && lastOrUndefined(listChildren).kind === SyntaxKind.CommaToken) { - argumentCount++; + // Case 1: + // foo<#T, U>(#a, b) -> The token introduces a list, and should begin a sig help session + // Case 2: + // fo#o#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end + // Case 3: + // foo(a#, #b#) -> The token is buried inside a list, and should give sig help + // Find out if 'node' is an argument, a type argument, or neither + if (node.kind === SyntaxKind.LessThanToken || + node.kind === SyntaxKind.OpenParenToken) { + // Find the list that starts right *after* the < or ( token. + // If the user has just opened a list, consider this item 0. + let list = getChildListThatStartsWithOpenerToken(callExpression, node, sourceFile); + let isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos; + Debug.assert(list !== undefined); + return { + kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments, + invocation: callExpression, + argumentsSpan: getApplicableSpanForArguments(list, sourceFile), + argumentIndex: 0, + argumentCount: getArgumentCount(list) + }; } - return argumentCount; - } + // findListItemInfo can return undefined if we are not in parent's argument list + // or type argument list. This includes cases where the cursor is: + // - To the right of the closing paren, non-substitution template, or template tail. + // - Between the type arguments and the arguments (greater than token) + // - On the target of the call (parent.func) + // - On the 'new' keyword in a 'new' expression + let listItemInfo = findListItemInfo(node); + if (listItemInfo) { + let list = listItemInfo.list; + let isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos; - // spanIndex is either the index for a given template span. - // This does not give appropriate results for a NoSubstitutionTemplateLiteral - function getArgumentIndexForTemplatePiece(spanIndex: number, node: Node): number { - // Because the TemplateStringsArray is the first argument, we have to offset each substitution expression by 1. - // There are three cases we can encounter: - // 1. We are precisely in the template literal (argIndex = 0). - // 2. We are in or to the right of the substitution expression (argIndex = spanIndex + 1). - // 3. We are directly to the right of the template literal, but because we look for the token on the left, - // not enough to put us in the substitution expression; we should consider ourselves part of - // the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1). - // - // Example: f `# abcd $#{# 1 + 1# }# efghi ${ #"#hello"# } # ` - // ^ ^ ^ ^ ^ ^ ^ ^ ^ - // Case: 1 1 3 2 1 3 2 2 1 - Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node."); - if (isTemplateLiteralKind(node.kind)) { - if (isInsideTemplateLiteral(node, position)) { - return 0; - } - return spanIndex + 2; + let argumentIndex = getArgumentIndex(list, node); + let argumentCount = getArgumentCount(list); + + Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, + `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); + + return { + kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments, + invocation: callExpression, + argumentsSpan: getApplicableSpanForArguments(list, sourceFile), + argumentIndex: argumentIndex, + argumentCount: argumentCount + }; } - return spanIndex + 1; + } + else if (node.kind === SyntaxKind.NoSubstitutionTemplateLiteral && node.parent.kind === SyntaxKind.TaggedTemplateExpression) { + // Check if we're actually inside the template; + // otherwise we'll fall out and return undefined. + if (isInsideTemplateLiteral(node, position)) { + return getArgumentListInfoForTemplate(node.parent, /*argumentIndex*/ 0, sourceFile); + } + } + else if (node.kind === SyntaxKind.TemplateHead && node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) { + let templateExpression = node.parent; + let tagExpression = templateExpression.parent; + Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); + + let argumentIndex = isInsideTemplateLiteral(node, position) ? 0 : 1; + + return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); + } + else if (node.parent.kind === SyntaxKind.TemplateSpan && node.parent.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) { + let templateSpan = node.parent; + let templateExpression = templateSpan.parent; + let tagExpression = templateExpression.parent; + Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); + + // If we're just after a template tail, don't show signature help. + if (node.kind === SyntaxKind.TemplateTail && !isInsideTemplateLiteral(node, position)) { + return undefined; + } + + let spanIndex = templateExpression.templateSpans.indexOf(templateSpan); + let argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position); + + return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); } - function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number): ArgumentListInfo { - // argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument. - let argumentCount = tagExpression.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral - ? 1 - : (tagExpression.template).templateSpans.length + 1; + return undefined; + } - Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); + function getArgumentIndex(argumentsList: Node, node: Node) { + // The list we got back can include commas. In the presence of errors it may + // also just have nodes without commas. For example "Foo(a b c)" will have 3 + // args without commas. We want to find what index we're at. So we count + // forward until we hit ourselves, only incrementing the index if it isn't a + // comma. + // + // Note: the subtlety around trailing commas (in getArgumentCount) does not apply + // here. That's because we're only walking forward until we hit the node we're + // on. In that case, even if we're after the trailing comma, we'll still see + // that trailing comma in the list, and we'll have generated the appropriate + // arg index. + let argumentIndex = 0; + let listChildren = argumentsList.getChildren(); + for (let child of listChildren) { + if (child === node) { + break; + } + if (child.kind !== SyntaxKind.CommaToken) { + argumentIndex++; + } + } + + return argumentIndex; + } + + function getArgumentCount(argumentsList: Node) { + // The argument count for a list is normally the number of non-comma children it has. + // For example, if you have "Foo(a,b)" then there will be three children of the arg + // list 'a' '' 'b'. So, in this case the arg count will be 2. However, there + // is a small subtlety. If you have "Foo(a,)", then the child list will just have + // 'a' ''. So, in the case where the last child is a comma, we increase the + // arg count by one to compensate. + // + // Note: this subtlety only applies to the last comma. If you had "Foo(a,," then + // we'll have: 'a' '' '' + // That will give us 2 non-commas. We then add one for the last comma, givin us an + // arg count of 3. + let listChildren = argumentsList.getChildren(); + + let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken); + if (listChildren.length > 0 && lastOrUndefined(listChildren).kind === SyntaxKind.CommaToken) { + argumentCount++; + } + + return argumentCount; + } + + // spanIndex is either the index for a given template span. + // This does not give appropriate results for a NoSubstitutionTemplateLiteral + function getArgumentIndexForTemplatePiece(spanIndex: number, node: Node, position: number): number { + // Because the TemplateStringsArray is the first argument, we have to offset each substitution expression by 1. + // There are three cases we can encounter: + // 1. We are precisely in the template literal (argIndex = 0). + // 2. We are in or to the right of the substitution expression (argIndex = spanIndex + 1). + // 3. We are directly to the right of the template literal, but because we look for the token on the left, + // not enough to put us in the substitution expression; we should consider ourselves part of + // the *next* span's expression by offsetting the index (argIndex = (spanIndex + 1) + 1). + // + // Example: f `# abcd $#{# 1 + 1# }# efghi ${ #"#hello"# } # ` + // ^ ^ ^ ^ ^ ^ ^ ^ ^ + // Case: 1 1 3 2 1 3 2 2 1 + Debug.assert(position >= node.getStart(), "Assumed 'position' could not occur before node."); + if (isTemplateLiteralKind(node.kind)) { + if (isInsideTemplateLiteral(node, position)) { + return 0; + } + return spanIndex + 2; + } + return spanIndex + 1; + } + + function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number, sourceFile: SourceFile): ArgumentListInfo { + // argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument. + let argumentCount = tagExpression.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral + ? 1 + : (tagExpression.template).templateSpans.length + 1; + + Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); + + return { + kind: ArgumentListKind.TaggedTemplateArguments, + invocation: tagExpression, + argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression, sourceFile), + argumentIndex: argumentIndex, + argumentCount: argumentCount + }; + } + + function getApplicableSpanForArguments(argumentsList: Node, sourceFile: SourceFile): TextSpan { + // We use full start and skip trivia on the end because we want to include trivia on + // both sides. For example, + // + // foo( /*comment */ a, b, c /*comment*/ ) + // | | + // + // The applicable span is from the first bar to the second bar (inclusive, + // but not including parentheses) + let applicableSpanStart = argumentsList.getFullStart(); + let applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false); + return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); + } + + function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression, sourceFile: SourceFile): TextSpan { + let template = taggedTemplate.template; + let applicableSpanStart = template.getStart(); + let applicableSpanEnd = template.getEnd(); + + // We need to adjust the end position for the case where the template does not have a tail. + // Otherwise, we will not show signature help past the expression. + // For example, + // + // ` ${ 1 + 1 foo(10) + // | | + // + // This is because a Missing node has no width. However, what we actually want is to include trivia + // leading up to the next token in case the user is about to type in a TemplateMiddle or TemplateTail. + if (template.kind === SyntaxKind.TemplateExpression) { + let lastSpan = lastOrUndefined((template).templateSpans); + if (lastSpan.literal.getFullWidth() === 0) { + applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false); + } + } + + return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); + } + + export function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo { + for (let n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) { + if (isFunctionBlock(n)) { + return undefined; + } + + // If the node is not a subspan of its parent, this is a big problem. + // There have been crashes that might be caused by this violation. + if (n.pos < n.parent.pos || n.end > n.parent.end) { + Debug.fail("Node of kind " + n.kind + " is not a subspan of its parent of kind " + n.parent.kind); + } + + let argumentInfo = getImmediatelyContainingArgumentInfo(n, position, sourceFile); + if (argumentInfo) { + return argumentInfo; + } + + + // TODO: Handle generic call with incomplete syntax + } + return undefined; + } + + function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node { + let children = parent.getChildren(sourceFile); + let indexOfOpenerToken = children.indexOf(openerToken); + Debug.assert(indexOfOpenerToken >= 0 && children.length > indexOfOpenerToken + 1); + return children[indexOfOpenerToken + 1]; + } + + /** + * The selectedItemIndex could be negative for several reasons. + * 1. There are too many arguments for all of the overloads + * 2. None of the overloads were type compatible + * The solution here is to try to pick the best overload by picking + * either the first one that has an appropriate number of parameters, + * or the one with the most parameters. + */ + function selectBestInvalidOverloadIndex(candidates: Signature[], argumentCount: number): number { + let maxParamsSignatureIndex = -1; + let maxParams = -1; + for (let i = 0; i < candidates.length; i++) { + let candidate = candidates[i]; + + if (candidate.hasRestParameter || candidate.parameters.length >= argumentCount) { + return i; + } + + if (candidate.parameters.length > maxParams) { + maxParams = candidate.parameters.length; + maxParamsSignatureIndex = i; + } + } + + return maxParamsSignatureIndex; + } + + function createSignatureHelpItems(candidates: Signature[], bestSignature: Signature, argumentListInfo: ArgumentListInfo, typeChecker: TypeChecker): SignatureHelpItems { + let applicableSpan = argumentListInfo.argumentsSpan; + let isTypeParameterList = argumentListInfo.kind === ArgumentListKind.TypeArguments; + + let invocation = argumentListInfo.invocation; + let callTarget = getInvokedExpression(invocation) + let callTargetSymbol = typeChecker.getSymbolAtLocation(callTarget); + let callTargetDisplayParts = callTargetSymbol && symbolToDisplayParts(typeChecker, callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined); + let items: SignatureHelpItem[] = map(candidates, candidateSignature => { + let signatureHelpParameters: SignatureHelpParameter[]; + let prefixDisplayParts: SymbolDisplayPart[] = []; + let suffixDisplayParts: SymbolDisplayPart[] = []; + + if (callTargetDisplayParts) { + addRange(prefixDisplayParts, callTargetDisplayParts); + } + + if (isTypeParameterList) { + prefixDisplayParts.push(punctuationPart(SyntaxKind.LessThanToken)); + let typeParameters = candidateSignature.typeParameters; + signatureHelpParameters = typeParameters && typeParameters.length > 0 ? map(typeParameters, createSignatureHelpParameterForTypeParameter) : emptyArray; + suffixDisplayParts.push(punctuationPart(SyntaxKind.GreaterThanToken)); + let parameterParts = mapToDisplayParts(writer => + typeChecker.getSymbolDisplayBuilder().buildDisplayForParametersAndDelimiters(candidateSignature.thisType, candidateSignature.parameters, writer, invocation)); + addRange(suffixDisplayParts, parameterParts); + } + else { + let typeParameterParts = mapToDisplayParts(writer => + typeChecker.getSymbolDisplayBuilder().buildDisplayForTypeParametersAndDelimiters(candidateSignature.typeParameters, writer, invocation)); + addRange(prefixDisplayParts, typeParameterParts); + prefixDisplayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); + + let parameters = candidateSignature.parameters; + signatureHelpParameters = parameters.length > 0 ? map(parameters, createSignatureHelpParameterForParameter) : emptyArray; + suffixDisplayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); + } + + let returnTypeParts = mapToDisplayParts(writer => + typeChecker.getSymbolDisplayBuilder().buildReturnTypeDisplay(candidateSignature, writer, invocation)); + addRange(suffixDisplayParts, returnTypeParts); return { - kind: ArgumentListKind.TaggedTemplateArguments, - invocation: tagExpression, - argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression), - argumentIndex: argumentIndex, - argumentCount: argumentCount + isVariadic: candidateSignature.hasRestParameter, + prefixDisplayParts, + suffixDisplayParts, + separatorDisplayParts: [punctuationPart(SyntaxKind.CommaToken), spacePart()], + parameters: signatureHelpParameters, + documentation: candidateSignature.getDocumentationComment() + }; + }); + + let argumentIndex = argumentListInfo.argumentIndex; + + // argumentCount is the *apparent* number of arguments. + let argumentCount = argumentListInfo.argumentCount; + + let selectedItemIndex = candidates.indexOf(bestSignature); + if (selectedItemIndex < 0) { + selectedItemIndex = selectBestInvalidOverloadIndex(candidates, argumentCount); + } + + Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); + + return { + items, + applicableSpan, + selectedItemIndex, + argumentIndex, + argumentCount + }; + + function createSignatureHelpParameterForParameter(parameter: Symbol): SignatureHelpParameter { + let displayParts = mapToDisplayParts(writer => + typeChecker.getSymbolDisplayBuilder().buildParameterDisplay(parameter, writer, invocation)); + + return { + name: parameter.name, + documentation: parameter.getDocumentationComment(), + displayParts, + isOptional: typeChecker.isOptionalParameter(parameter.valueDeclaration) }; } - function getApplicableSpanForArguments(argumentsList: Node): TextSpan { - // We use full start and skip trivia on the end because we want to include trivia on - // both sides. For example, - // - // foo( /*comment */ a, b, c /*comment*/ ) - // | | - // - // The applicable span is from the first bar to the second bar (inclusive, - // but not including parentheses) - let applicableSpanStart = argumentsList.getFullStart(); - let applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false); - return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); - } - - function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression): TextSpan { - let template = taggedTemplate.template; - let applicableSpanStart = template.getStart(); - let applicableSpanEnd = template.getEnd(); - - // We need to adjust the end position for the case where the template does not have a tail. - // Otherwise, we will not show signature help past the expression. - // For example, - // - // ` ${ 1 + 1 foo(10) - // | | - // - // This is because a Missing node has no width. However, what we actually want is to include trivia - // leading up to the next token in case the user is about to type in a TemplateMiddle or TemplateTail. - if (template.kind === SyntaxKind.TemplateExpression) { - let lastSpan = lastOrUndefined((template).templateSpans); - if (lastSpan.literal.getFullWidth() === 0) { - applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false); - } - } - - return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); - } - - function getContainingArgumentInfo(node: Node): ArgumentListInfo { - for (let n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) { - if (isFunctionBlock(n)) { - return undefined; - } - - // If the node is not a subspan of its parent, this is a big problem. - // There have been crashes that might be caused by this violation. - if (n.pos < n.parent.pos || n.end > n.parent.end) { - Debug.fail("Node of kind " + n.kind + " is not a subspan of its parent of kind " + n.parent.kind); - } - - let argumentInfo = getImmediatelyContainingArgumentInfo(n); - if (argumentInfo) { - return argumentInfo; - } - - - // TODO: Handle generic call with incomplete syntax - } - return undefined; - } - - function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node { - let children = parent.getChildren(sourceFile); - let indexOfOpenerToken = children.indexOf(openerToken); - Debug.assert(indexOfOpenerToken >= 0 && children.length > indexOfOpenerToken + 1); - return children[indexOfOpenerToken + 1]; - } - - /** - * The selectedItemIndex could be negative for several reasons. - * 1. There are too many arguments for all of the overloads - * 2. None of the overloads were type compatible - * The solution here is to try to pick the best overload by picking - * either the first one that has an appropriate number of parameters, - * or the one with the most parameters. - */ - function selectBestInvalidOverloadIndex(candidates: Signature[], argumentCount: number): number { - let maxParamsSignatureIndex = -1; - let maxParams = -1; - for (let i = 0; i < candidates.length; i++) { - let candidate = candidates[i]; - - if (candidate.hasRestParameter || candidate.parameters.length >= argumentCount) { - return i; - } - - if (candidate.parameters.length > maxParams) { - maxParams = candidate.parameters.length; - maxParamsSignatureIndex = i; - } - } - - return maxParamsSignatureIndex; - } - - function createSignatureHelpItems(candidates: Signature[], bestSignature: Signature, argumentListInfo: ArgumentListInfo): SignatureHelpItems { - let applicableSpan = argumentListInfo.argumentsSpan; - let isTypeParameterList = argumentListInfo.kind === ArgumentListKind.TypeArguments; - - let invocation = argumentListInfo.invocation; - let callTarget = getInvokedExpression(invocation) - let callTargetSymbol = typeChecker.getSymbolAtLocation(callTarget); - let callTargetDisplayParts = callTargetSymbol && symbolToDisplayParts(typeChecker, callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined); - let items: SignatureHelpItem[] = map(candidates, candidateSignature => { - let signatureHelpParameters: SignatureHelpParameter[]; - let prefixDisplayParts: SymbolDisplayPart[] = []; - let suffixDisplayParts: SymbolDisplayPart[] = []; - - if (callTargetDisplayParts) { - addRange(prefixDisplayParts, callTargetDisplayParts); - } - - if (isTypeParameterList) { - prefixDisplayParts.push(punctuationPart(SyntaxKind.LessThanToken)); - let typeParameters = candidateSignature.typeParameters; - signatureHelpParameters = typeParameters && typeParameters.length > 0 ? map(typeParameters, createSignatureHelpParameterForTypeParameter) : emptyArray; - suffixDisplayParts.push(punctuationPart(SyntaxKind.GreaterThanToken)); - let parameterParts = mapToDisplayParts(writer => - typeChecker.getSymbolDisplayBuilder().buildDisplayForParametersAndDelimiters(candidateSignature.thisType, candidateSignature.parameters, writer, invocation)); - addRange(suffixDisplayParts, parameterParts); - } - else { - let typeParameterParts = mapToDisplayParts(writer => - typeChecker.getSymbolDisplayBuilder().buildDisplayForTypeParametersAndDelimiters(candidateSignature.typeParameters, writer, invocation)); - addRange(prefixDisplayParts, typeParameterParts); - prefixDisplayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); - - let parameters = candidateSignature.parameters; - signatureHelpParameters = parameters.length > 0 ? map(parameters, createSignatureHelpParameterForParameter) : emptyArray; - suffixDisplayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); - } - - let returnTypeParts = mapToDisplayParts(writer => - typeChecker.getSymbolDisplayBuilder().buildReturnTypeDisplay(candidateSignature, writer, invocation)); - addRange(suffixDisplayParts, returnTypeParts); - - return { - isVariadic: candidateSignature.hasRestParameter, - prefixDisplayParts, - suffixDisplayParts, - separatorDisplayParts: [punctuationPart(SyntaxKind.CommaToken), spacePart()], - parameters: signatureHelpParameters, - documentation: candidateSignature.getDocumentationComment() - }; - }); - - let argumentIndex = argumentListInfo.argumentIndex; - - // argumentCount is the *apparent* number of arguments. - let argumentCount = argumentListInfo.argumentCount; - - let selectedItemIndex = candidates.indexOf(bestSignature); - if (selectedItemIndex < 0) { - selectedItemIndex = selectBestInvalidOverloadIndex(candidates, argumentCount); - } - - Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); + function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter): SignatureHelpParameter { + let displayParts = mapToDisplayParts(writer => + typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(typeParameter, writer, invocation)); return { - items, - applicableSpan, - selectedItemIndex, - argumentIndex, - argumentCount + name: typeParameter.symbol.name, + documentation: emptyArray, + displayParts, + isOptional: false }; - - function createSignatureHelpParameterForParameter(parameter: Symbol): SignatureHelpParameter { - let displayParts = mapToDisplayParts(writer => - typeChecker.getSymbolDisplayBuilder().buildParameterDisplay(parameter, writer, invocation)); - - return { - name: parameter.name, - documentation: parameter.getDocumentationComment(), - displayParts, - isOptional: typeChecker.isOptionalParameter(parameter.valueDeclaration) - }; - } - - function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter): SignatureHelpParameter { - let displayParts = mapToDisplayParts(writer => - typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(typeParameter, writer, invocation)); - - return { - name: typeParameter.symbol.name, - documentation: emptyArray, - displayParts, - isOptional: false - }; - } } } } \ No newline at end of file