TypeScript/src/services/signatureHelp.ts

589 lines
30 KiB
TypeScript
Raw Normal View History

2014-09-22 23:49:33 +02:00
///<reference path='services.ts' />
/** @internal */
2014-09-22 20:38:01 +02:00
module ts.SignatureHelp {
// A partially written generic type expression is not guaranteed to have the correct syntax tree. the expression could be parsed as less than/greater than expression or a comma expression
// or some other combination depending on what the user has typed so far. For the purposes of signature help we need to consider any location after "<" as a possible generic type reference.
// To do this, the method will back parse the expression starting at the position required. it will try to parse the current expression as a generic type expression, if it did succeed it
// will return the generic identifier that started the expression (e.g. "foo" in "foo<any, |"). It is then up to the caller to ensure that this is a valid generic expression through
// looking up the type. The method will also keep track of the parameter index inside the expression.
//public static isInPartiallyWrittenTypeArgumentList(syntaxTree: TypeScript.SyntaxTree, position: number): any {
2015-03-13 21:11:17 +01:00
// let token = Syntax.findTokenOnLeft(syntaxTree.sourceUnit(), position, /*includeSkippedTokens*/ true);
2014-09-22 20:38:01 +02:00
// if (token && TypeScript.Syntax.hasAncestorOfKind(token, TypeScript.SyntaxKind.TypeParameterList)) {
// // We are in the wrong generic list. bail out
// return null;
// }
2015-03-13 21:11:17 +01:00
// let stack = 0;
// let argumentIndex = 0;
2014-09-22 20:38:01 +02:00
// whileLoop:
// while (token) {
// switch (token.kind()) {
// case TypeScript.SyntaxKind.LessThanToken:
// if (stack === 0) {
// // Found the beginning of the generic argument expression
2015-03-13 21:11:17 +01:00
// let lessThanToken = token;
2014-09-22 20:38:01 +02:00
// token = previousToken(token, /*includeSkippedTokens*/ true);
// if (!token || token.kind() !== TypeScript.SyntaxKind.IdentifierName) {
// break whileLoop;
// }
// // Found the name, return the data
// return {
// genericIdentifer: token,
// lessThanToken: lessThanToken,
// argumentIndex: argumentIndex
// };
// }
// else if (stack < 0) {
// // Seen one too many less than tokens, bail out
// break whileLoop;
// }
// else {
// stack--;
// }
// break;
// case TypeScript.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
// stack++;
// // Intentaion fall through
// case TypeScript.SyntaxKind.GreaterThanToken:
// stack++;
// break;
// case TypeScript.SyntaxKind.CommaToken:
// if (stack == 0) {
// argumentIndex++;
// }
// break;
// case TypeScript.SyntaxKind.CloseBraceToken:
// // This can be object type, skip untill we find the matching open brace token
2015-03-13 21:11:17 +01:00
// let unmatchedOpenBraceTokens = 0;
2014-09-22 20:38:01 +02:00
// // Skip untill the matching open brace token
// token = SignatureInfoHelpers.moveBackUpTillMatchingTokenKind(token, TypeScript.SyntaxKind.CloseBraceToken, TypeScript.SyntaxKind.OpenBraceToken);
// if (!token) {
// // No matching token was found. bail out
// break whileLoop;
// }
// break;
// case TypeScript.SyntaxKind.EqualsGreaterThanToken:
// // This can be a function type or a constructor type. In either case, we want to skip the function defintion
// token = previousToken(token, /*includeSkippedTokens*/ true);
// if (token && token.kind() === TypeScript.SyntaxKind.CloseParenToken) {
// // Skip untill the matching open paren token
// token = SignatureInfoHelpers.moveBackUpTillMatchingTokenKind(token, TypeScript.SyntaxKind.CloseParenToken, TypeScript.SyntaxKind.OpenParenToken);
// if (token && token.kind() === TypeScript.SyntaxKind.GreaterThanToken) {
// // Another generic type argument list, skip it\
// token = SignatureInfoHelpers.moveBackUpTillMatchingTokenKind(token, TypeScript.SyntaxKind.GreaterThanToken, TypeScript.SyntaxKind.LessThanToken);
// }
// if (token && token.kind() === TypeScript.SyntaxKind.NewKeyword) {
// // In case this was a constructor type, skip the new keyword
// token = previousToken(token, /*includeSkippedTokens*/ true);
// }
// if (!token) {
// // No matching token was found. bail out
// break whileLoop;
// }
// }
// else {
// // This is not a funtion type. exit the main loop
// break whileLoop;
// }
// break;
// case TypeScript.SyntaxKind.IdentifierName:
// case TypeScript.SyntaxKind.AnyKeyword:
// case TypeScript.SyntaxKind.NumberKeyword:
// case TypeScript.SyntaxKind.StringKeyword:
// case TypeScript.SyntaxKind.VoidKeyword:
// case TypeScript.SyntaxKind.BooleanKeyword:
// case TypeScript.SyntaxKind.DotToken:
// case TypeScript.SyntaxKind.OpenBracketToken:
// case TypeScript.SyntaxKind.CloseBracketToken:
// // Valid tokens in a type name. Skip.
// break;
// default:
// break whileLoop;
// }
// token = previousToken(token, /*includeSkippedTokens*/ true);
// }
// return null;
//}
2014-07-19 01:55:11 +02:00
2014-09-22 20:38:01 +02:00
//private static moveBackUpTillMatchingTokenKind(token: TypeScript.ISyntaxToken, tokenKind: TypeScript.SyntaxKind, matchingTokenKind: TypeScript.SyntaxKind): TypeScript.ISyntaxToken {
// if (!token || token.kind() !== tokenKind) {
// throw TypeScript.Errors.invalidOperation();
// }
// // Skip the current token
// token = previousToken(token, /*includeSkippedTokens*/ true);
2015-03-13 21:11:17 +01:00
// let stack = 0;
2014-09-22 20:38:01 +02:00
// while (token) {
// if (token.kind() === matchingTokenKind) {
// if (stack === 0) {
// // Found the matching token, return
// return token;
// }
// else if (stack < 0) {
// // tokens overlapped.. bail out.
// break;
// }
// else {
// stack--;
// }
// }
// else if (token.kind() === tokenKind) {
// stack++;
// }
// // Move back
// token = previousToken(token, /*includeSkippedTokens*/ true);
// }
// // Did not find matching token
// return null;
//}
2015-03-13 21:11:17 +01:00
let emptyArray: any[] = [];
2014-09-22 20:38:01 +02:00
2014-11-18 22:40:42 +01:00
const enum ArgumentListKind {
TypeArguments,
CallArguments,
TaggedTemplateArguments
}
2014-11-18 22:40:42 +01:00
interface ArgumentListInfo {
kind: ArgumentListKind;
invocation: CallLikeExpression;
argumentsSpan: TextSpan;
2014-11-13 01:30:25 +01:00
argumentIndex?: number;
argumentCount: number;
}
2014-09-24 01:18:47 +02:00
export function getSignatureHelpItems(sourceFile: SourceFile, position: number, typeInfoResolver: TypeChecker, cancellationToken: CancellationTokenObject): SignatureHelpItems {
2014-09-22 20:38:01 +02:00
// Decide whether to show signature help
2015-03-13 21:11:17 +01:00
let startingToken = findTokenOnLeftOfPosition(sourceFile, position);
if (!startingToken) {
// We are at the beginning of the file
return undefined;
}
2015-03-13 21:11:17 +01:00
let argumentInfo = getContainingArgumentInfo(startingToken);
2014-09-22 22:49:48 +02:00
cancellationToken.throwIfCancellationRequested();
2014-09-22 20:38:01 +02:00
// Semantic filtering of signature help
2014-09-30 22:46:07 +02:00
if (!argumentInfo) {
2014-09-24 00:34:42 +02:00
return undefined;
2014-09-22 20:38:01 +02:00
}
2015-03-13 21:11:17 +01:00
let call = argumentInfo.invocation;
let candidates = <Signature[]>[];
let resolvedSignature = typeInfoResolver.getResolvedSignature(call, candidates);
2014-09-24 00:34:42 +02:00
cancellationToken.throwIfCancellationRequested();
if (!candidates.length) {
return undefined;
}
2014-09-22 20:38:01 +02:00
2014-09-30 22:46:07 +02:00
return createSignatureHelpItems(candidates, resolvedSignature, argumentInfo);
2014-09-22 20:38:01 +02:00
2014-09-24 04:00:45 +02:00
/**
2014-11-21 02:00:01 +01:00
* Returns relevant information for the argument list and the current argument if we are
* in the argument of an invocation; returns undefined otherwise.
2014-09-24 04:00:45 +02:00
*/
2014-11-25 01:24:15 +01:00
function getImmediatelyContainingArgumentInfo(node: Node): ArgumentListInfo {
if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) {
2015-03-13 21:11:17 +01:00
let callExpression = <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<T, U>#(a, b)# -> The token is either not associated with a list, or ends a list, so the session should end
// Case 3:
// foo<T#, U#>(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.
2015-03-13 21:11:17 +01:00
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
2015-03-13 21:11:17 +01:00
let listItemInfo = findListItemInfo(node);
if (listItemInfo) {
2015-03-13 21:11:17 +01:00
let list = listItemInfo.list;
let isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos;
2014-11-13 01:30:25 +01:00
2015-03-13 21:11:17 +01:00
let argumentIndex = getArgumentIndex(list, node);
let argumentCount = getArgumentCount(list);
2014-11-13 01:30:25 +01:00
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount,
`argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
return {
kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments,
invocation: callExpression,
argumentsSpan: getApplicableSpanForArguments(list),
2014-11-13 01:30:25 +01:00
argumentIndex: argumentIndex,
argumentCount: argumentCount
};
}
2014-09-22 20:38:01 +02:00
}
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(<LiteralExpression>node, position)) {
return getArgumentListInfoForTemplate(<TaggedTemplateExpression>node.parent, /*argumentIndex*/ 0);
}
}
else if (node.kind === SyntaxKind.TemplateHead && node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
2015-03-13 21:11:17 +01:00
let templateExpression = <TemplateExpression>node.parent;
let tagExpression = <TaggedTemplateExpression>templateExpression.parent;
2014-11-25 01:24:15 +01:00
Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
2015-03-13 21:11:17 +01:00
let argumentIndex = isInsideTemplateLiteral(<LiteralExpression>node, position) ? 0 : 1;
return getArgumentListInfoForTemplate(tagExpression, argumentIndex);
2014-09-22 20:38:01 +02:00
}
else if (node.parent.kind === SyntaxKind.TemplateSpan && node.parent.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
2015-03-13 21:11:17 +01:00
let templateSpan = <TemplateSpan>node.parent;
let templateExpression = <TemplateExpression>templateSpan.parent;
let tagExpression = <TaggedTemplateExpression>templateExpression.parent;
Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression);
2015-03-02 23:46:15 +01:00
// If we're just after a template tail, don't show signature help.
if (node.kind === SyntaxKind.TemplateTail && !isInsideTemplateLiteral(<LiteralExpression>node, position)) {
return undefined;
}
2015-03-13 21:11:17 +01:00
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.
2015-03-11 23:30:33 +01:00
//
// 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.
2015-03-13 21:11:17 +01:00
let argumentIndex = 0;
let listChildren = argumentsList.getChildren();
2015-03-13 18:03:01 +01:00
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' '<comma>' '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' '<comma>'. So, in the case where the last child is a comma, we increase the
// arg count by one to compensate.
2015-03-11 23:30:33 +01:00
//
// Note: this subtlety only applies to the last comma. If you had "Foo(a,," then
// we'll have: 'a' '<comma>' '<missing>'
// That will give us 2 non-commas. We then add one for the last comma, givin us an
// arg count of 3.
2015-03-13 21:11:17 +01:00
let listChildren = argumentsList.getChildren();
2015-03-13 21:11:17 +01:00
let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken);
if (listChildren.length > 0 && lastOrUndefined(listChildren).kind === SyntaxKind.CommaToken) {
argumentCount++;
}
return argumentCount;
}
2014-11-25 01:24:15 +01:00
// spanIndex is either the index for a given template span.
// This does not give appropriate results for a NoSubstitutionTemplateLiteral
2014-11-25 01:24:15 +01:00
function getArgumentIndexForTemplatePiece(spanIndex: number, node: Node): number {
2014-11-21 02:00:01 +01:00
// Because the TemplateStringsArray is the first argument, we have to offset each substitution expression by 1.
// There are three cases we can encounter:
2014-11-21 02:00:01 +01:00
// 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"# } # `
// ^ ^ ^ ^ ^ ^ ^ ^ ^
2014-11-18 22:40:42 +01:00
// 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(<LiteralExpression>node, position)) {
return 0;
}
return spanIndex + 2;
}
return spanIndex + 1;
}
function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number): ArgumentListInfo {
2014-11-21 02:00:01 +01:00
// argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument.
2015-03-13 21:11:17 +01:00
let argumentCount = tagExpression.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral
? 1
: (<TemplateExpression>tagExpression.template).templateSpans.length + 1;
Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`);
return {
kind: ArgumentListKind.TaggedTemplateArguments,
invocation: tagExpression,
2014-11-25 01:24:15 +01:00
argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression),
argumentIndex: argumentIndex,
argumentCount: argumentCount
};
}
2014-09-22 20:38:01 +02:00
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)
2015-03-13 21:11:17 +01:00
let applicableSpanStart = argumentsList.getFullStart();
let applicableSpanEnd = skipTrivia(sourceFile.text, argumentsList.getEnd(), /*stopAfterLineBreak*/ false);
2014-12-10 22:45:08 +01:00
return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
}
2014-11-25 01:24:15 +01:00
function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression): TextSpan {
2015-03-13 21:11:17 +01:00
let template = taggedTemplate.template;
let applicableSpanStart = template.getStart();
let applicableSpanEnd = template.getEnd();
2014-11-25 01:24:15 +01:00
// 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) {
2015-03-13 21:11:17 +01:00
let lastSpan = lastOrUndefined((<TemplateExpression>template).templateSpans);
2014-12-02 01:17:04 +01:00
if (lastSpan.literal.getFullWidth() === 0) {
2014-11-25 01:24:15 +01:00
applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false);
}
}
2014-12-10 22:45:08 +01:00
return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
2014-09-22 20:38:01 +02:00
}
2014-11-25 01:24:15 +01:00
function getContainingArgumentInfo(node: Node): ArgumentListInfo {
2015-03-13 21:11:17 +01:00
for (let n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) {
if (isFunctionBlock(n)) {
2014-09-22 20:38:01 +02:00
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) {
2014-11-04 08:31:38 +01:00
Debug.fail("Node of kind " + n.kind + " is not a subspan of its parent of kind " + n.parent.kind);
}
2015-03-13 21:11:17 +01:00
let argumentInfo = getImmediatelyContainingArgumentInfo(n);
2014-09-30 22:46:07 +02:00
if (argumentInfo) {
return argumentInfo;
2014-09-22 20:38:01 +02:00
}
// TODO: Handle generic call with incomplete syntax
}
return undefined;
}
function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node {
2015-03-13 21:11:17 +01:00
let children = parent.getChildren(sourceFile);
let indexOfOpenerToken = children.indexOf(openerToken);
2014-10-20 20:51:45 +02:00
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 {
2015-03-13 21:11:17 +01:00
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 {
2015-03-13 21:11:17 +01:00
let applicableSpan = argumentListInfo.argumentsSpan;
let isTypeParameterList = argumentListInfo.kind === ArgumentListKind.TypeArguments;
let invocation = argumentListInfo.invocation;
let callTarget = getInvokedExpression(invocation)
let callTargetSymbol = typeInfoResolver.getSymbolAtLocation(callTarget);
let callTargetDisplayParts = callTargetSymbol && symbolToDisplayParts(typeInfoResolver, callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined);
let items: SignatureHelpItem[] = map(candidates, candidateSignature => {
let signatureHelpParameters: SignatureHelpParameter[];
let prefixDisplayParts: SymbolDisplayPart[] = [];
let suffixDisplayParts: SymbolDisplayPart[] = [];
2014-11-25 01:24:15 +01:00
if (callTargetDisplayParts) {
prefixDisplayParts.push.apply(prefixDisplayParts, callTargetDisplayParts);
}
if (isTypeParameterList) {
prefixDisplayParts.push(punctuationPart(SyntaxKind.LessThanToken));
2015-03-13 21:11:17 +01:00
let typeParameters = candidateSignature.typeParameters;
signatureHelpParameters = typeParameters && typeParameters.length > 0 ? map(typeParameters, createSignatureHelpParameterForTypeParameter) : emptyArray;
suffixDisplayParts.push(punctuationPart(SyntaxKind.GreaterThanToken));
2015-03-13 21:11:17 +01:00
let parameterParts = mapToDisplayParts(writer =>
typeInfoResolver.getSymbolDisplayBuilder().buildDisplayForParametersAndDelimiters(candidateSignature.parameters, writer, invocation));
suffixDisplayParts.push.apply(suffixDisplayParts, parameterParts);
}
else {
2015-03-13 21:11:17 +01:00
let typeParameterParts = mapToDisplayParts(writer =>
typeInfoResolver.getSymbolDisplayBuilder().buildDisplayForTypeParametersAndDelimiters(candidateSignature.typeParameters, writer, invocation));
prefixDisplayParts.push.apply(prefixDisplayParts, typeParameterParts);
prefixDisplayParts.push(punctuationPart(SyntaxKind.OpenParenToken));
2015-03-13 21:11:17 +01:00
let parameters = candidateSignature.parameters;
signatureHelpParameters = parameters.length > 0 ? map(parameters, createSignatureHelpParameterForParameter) : emptyArray;
suffixDisplayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
}
2015-03-13 21:11:17 +01:00
let returnTypeParts = mapToDisplayParts(writer =>
typeInfoResolver.getSymbolDisplayBuilder().buildReturnTypeDisplay(candidateSignature, writer, invocation));
suffixDisplayParts.push.apply(suffixDisplayParts, returnTypeParts);
return {
isVariadic: candidateSignature.hasRestParameter,
prefixDisplayParts,
suffixDisplayParts,
separatorDisplayParts: [punctuationPart(SyntaxKind.CommaToken), spacePart()],
parameters: signatureHelpParameters,
documentation: candidateSignature.getDocumentationComment()
};
2014-09-22 20:38:01 +02:00
});
2015-03-13 21:11:17 +01:00
let argumentIndex = argumentListInfo.argumentIndex;
// argumentCount is the *apparent* number of arguments.
2015-03-13 21:11:17 +01:00
let argumentCount = argumentListInfo.argumentCount;
2015-03-13 21:11:17 +01:00
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 {
2015-03-13 21:11:17 +01:00
let displayParts = mapToDisplayParts(writer =>
typeInfoResolver.getSymbolDisplayBuilder().buildParameterDisplay(parameter, writer, invocation));
2015-03-13 21:11:17 +01:00
let isOptional = hasQuestionToken(parameter.valueDeclaration);
return {
name: parameter.name,
documentation: parameter.getDocumentationComment(),
displayParts,
isOptional
};
}
function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter): SignatureHelpParameter {
2015-03-13 21:11:17 +01:00
let displayParts = mapToDisplayParts(writer =>
typeInfoResolver.getSymbolDisplayBuilder().buildTypeParameterDisplay(typeParameter, writer, invocation));
return {
name: typeParameter.symbol.name,
documentation: emptyArray,
displayParts,
isOptional: false
};
}
2014-09-22 20:38:01 +02:00
}
}
2014-07-19 01:55:11 +02:00
}