2014-09-22 23:49:33 +02:00
///<reference path='services.ts' />
2014-07-19 01:55:11 +02:00
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 {
// var token = Syntax.findTokenOnLeft(syntaxTree.sourceUnit(), position, /*includeSkippedTokens*/ true);
// if (token && TypeScript.Syntax.hasAncestorOfKind(token, TypeScript.SyntaxKind.TypeParameterList)) {
// // We are in the wrong generic list. bail out
// return null;
// }
// var stack = 0;
// var argumentIndex = 0;
// whileLoop:
// while (token) {
// switch (token.kind()) {
// case TypeScript.SyntaxKind.LessThanToken:
// if (stack === 0) {
// // Found the beginning of the generic argument expression
// var lessThanToken = token;
// 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
// var unmatchedOpenBraceTokens = 0;
// // 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-09-04 19:24:11 +02:00
//}
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);
// var stack = 0;
// 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;
2014-09-04 19:24:11 +02:00
//}
2014-09-22 20:38:01 +02:00
var emptyArray : any [ ] = [ ] ;
2014-11-11 22:58:50 +01:00
export const enum ArgumentListKind {
TypeArguments ,
CallArguments ,
TaggedTemplateArguments
}
export interface ArgumentListInfo {
kind : ArgumentListKind ;
invocation : CallLikeExpression ;
arguments : Node | NodeArray < TemplateSpan > ;
2014-11-13 01:30:25 +01:00
argumentIndex? : number ;
2014-11-11 22:58:50 +01:00
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
2014-09-24 03:38:13 +02:00
var startingToken = findTokenOnLeftOfPosition ( sourceFile , position ) ;
if ( ! startingToken ) {
// We are at the beginning of the file
return undefined ;
}
2014-09-30 22:46:07 +02:00
var 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
}
2014-11-13 01:30:25 +01:00
var call = argumentInfo . invocation ;
2014-09-24 00:34:42 +02:00
var candidates = < Signature [ ] > [ ] ;
var resolvedSignature = typeInfoResolver . getResolvedSignature ( call , candidates ) ;
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
/ * *
* If node is an argument , returns its index in the argument list .
* If not , returns - 1 .
* /
2014-11-11 22:58:50 +01:00
function getImmediatelyContainingArgumentInfo ( node : Node ) : ArgumentListInfo {
var callLikeExpr : CallLikeExpression ;
if ( node . parent . kind === SyntaxKind . CallExpression || node . parent . kind === SyntaxKind . NewExpression ) {
var 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.
var list = getChildListThatStartsWithOpenerToken ( callExpression , node , sourceFile ) ;
var isTypeArgList = callExpression . typeArguments && callExpression . typeArguments . pos === list . pos ;
Debug . assert ( list !== undefined ) ;
return {
kind : isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments ,
invocation : callExpression ,
arguments : list ,
argumentIndex : 0 ,
argumentCount : getCommaBasedArgCount ( 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
var listItemInfo = findListItemInfo ( node ) ;
if ( listItemInfo ) {
var list = listItemInfo . list ;
var isTypeArgList = callExpression . typeArguments && callExpression . typeArguments . pos === list . pos ;
2014-11-13 01:30:25 +01:00
// The listItemIndex we got back includes commas. Our goal is to return the index of the proper
// item (not including commas). Here are some examples:
// 1. foo(a, b, c #) -> the listItemIndex is 4, we want to return 2
// 2. foo(a, b, # c) -> listItemIndex is 3, we want to return 2
// 3. foo(#a) -> listItemIndex is 0, we want to return 0
//
// In general, we want to subtract the number of commas before the current index.
// But if we are on a comma, we also want to pretend we are on the argument *following*
// the comma. That amounts to taking the ceiling of half the index.
var argumentIndex = ( listItemInfo . listItemIndex + 1 ) >> 1 ;
2014-11-11 22:58:50 +01:00
return {
kind : isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments ,
invocation : callExpression ,
arguments : list ,
2014-11-13 01:30:25 +01:00
argumentIndex : argumentIndex ,
2014-11-11 22:58:50 +01:00
argumentCount : getCommaBasedArgCount ( list )
} ;
}
2014-09-22 20:38:01 +02:00
}
2014-11-11 22:58:50 +01:00
else if ( node . parent . kind === SyntaxKind . TemplateExpression && node . parent . parent . kind === SyntaxKind . TaggedTemplateExpression ) {
2014-11-13 01:30:25 +01:00
// TODO (drosen): Can't get sig help to trigger within the template head itself; only when directly to the right.
// Also, need to ensure that this works on NoSubstitutionTemplateExpressions when unterminated.
2014-11-11 22:58:50 +01:00
Debug . assert ( node . kind === SyntaxKind . TemplateHead , "Expected 'TemplateHead' as token." ) ;
2014-09-22 20:38:01 +02:00
2014-11-11 22:58:50 +01:00
var templateExpression = < TemplateExpression > node . parent ;
var tagExpression = < TaggedTemplateExpression > templateExpression . parent ;
// argumentIndex is 1 to adjust for the TemplateStringsArray
return getArgumentListInfoForTemplate ( tagExpression , templateExpression . templateSpans , /*argumentIndex*/ 1 ) ;
2014-09-22 20:38:01 +02:00
}
2014-11-11 22:58:50 +01:00
else if ( node . parent . kind === SyntaxKind . TemplateSpan && node . parent . parent . parent . kind === SyntaxKind . TaggedTemplateExpression ) {
var templateSpan = < TemplateSpan > node . parent ;
var templateExpression = < TemplateExpression > templateSpan . parent ;
var tagExpression = < TaggedTemplateExpression > templateExpression . parent ;
Debug . assert ( templateExpression . kind === SyntaxKind . TemplateExpression ) ;
// We need to account for the TemplateStringsArray, so we add at least 1.
// Then, if we're on the template literal, we want to jump to the next argument,
var spanIndex = templateExpression . templateSpans . indexOf ( templateSpan ) ;
var adjustedIndex = isTemplateLiteralKind ( node . kind ) ? spanIndex + 2 : spanIndex + 1
return getArgumentListInfoForTemplate ( tagExpression , templateExpression . templateSpans , adjustedIndex ) ;
}
return undefined ;
}
function getArgumentListInfoForTemplate ( tagExpression : TaggedTemplateExpression , spans : NodeArray < TemplateSpan > , argumentIndex : number ) : ArgumentListInfo {
var argumentCount = tagExpression . template . kind === SyntaxKind . NoSubstitutionTemplateLiteral
? 1
: ( < TemplateExpression > tagExpression . template ) . templateSpans . length + 1 ;
return {
kind : ArgumentListKind.TaggedTemplateArguments ,
invocation : tagExpression ,
arguments : spans ,
argumentIndex : argumentIndex ,
argumentCount : argumentCount
} ;
}
2014-09-22 20:38:01 +02:00
2014-11-11 22:58:50 +01:00
function getCommaBasedArgCount ( argumentsList : Node ) {
2014-11-13 01:30:25 +01:00
// The number of arguments is the number of commas plus one, unless the list
// is completely empty, in which case there are 0 arguments.
2014-11-11 22:58:50 +01:00
return argumentsList . getChildCount ( ) === 0
? 0
: 1 + countWhere ( argumentsList . getChildren ( ) , arg = > arg . kind === SyntaxKind . CommaToken ) ;
2014-09-22 20:38:01 +02:00
}
2014-11-11 22:58:50 +01:00
function getContainingArgumentInfo ( node : Node ) : ArgumentListInfo {
2014-09-22 20:38:01 +02:00
for ( var n = node ; n . kind !== SyntaxKind . SourceFile ; n = n . parent ) {
if ( n . kind === SyntaxKind . FunctionBlock ) {
return undefined ;
}
2014-10-07 22:37:57 +02:00
// 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 ) ;
2014-10-07 22:37:57 +02:00
}
2014-09-30 22:46:07 +02:00
var argumentInfo = getImmediatelyContainingArgumentInfo ( n ) ;
if ( argumentInfo ) {
return argumentInfo ;
2014-09-22 20:38:01 +02:00
}
// TODO: Handle generic call with incomplete syntax
}
return undefined ;
}
2014-10-02 02:09:50 +02:00
function getChildListThatStartsWithOpenerToken ( parent : Node , openerToken : Node , sourceFile : SourceFile ) : Node {
var children = parent . getChildren ( sourceFile ) ;
var indexOfOpenerToken = children . indexOf ( openerToken ) ;
2014-10-20 20:51:45 +02:00
Debug . assert ( indexOfOpenerToken >= 0 && children . length > indexOfOpenerToken + 1 ) ;
2014-10-02 02:09:50 +02:00
return children [ indexOfOpenerToken + 1 ] ;
}
2014-10-01 02:13:27 +02:00
/ * *
* 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 {
var maxParamsSignatureIndex = - 1 ;
var maxParams = - 1 ;
for ( var i = 0 ; i < candidates . length ; i ++ ) {
var 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 ;
}
2014-11-11 22:58:50 +01:00
function createSignatureHelpItems ( candidates : Signature [ ] , bestSignature : Signature , argumentListInfo : ArgumentListInfo ) : SignatureHelpItems {
var argumentsList = argumentListInfo . arguments ;
var isTypeParameterList = argumentListInfo . kind === ArgumentListKind . TypeArguments ;
2014-09-25 08:44:13 +02:00
2014-11-11 22:58:50 +01:00
var invocation = argumentListInfo . invocation ;
2014-11-13 01:30:25 +01:00
var invokerNode = getCallLikeInvoker ( invocation )
2014-11-11 22:58:50 +01:00
var invokerSymbol = typeInfoResolver . getSymbolInfo ( invokerNode ) ;
2014-11-13 01:30:25 +01:00
var invokerDisplayParts = invokerSymbol && symbolToDisplayParts ( typeInfoResolver , invokerSymbol , /*enclosingDeclaration*/ undefined , /*meaning*/ undefined ) ;
2014-10-16 19:35:34 +02:00
var items : SignatureHelpItem [ ] = map ( candidates , candidateSignature = > {
var signatureHelpParameters : SignatureHelpParameter [ ] ;
var prefixParts : SymbolDisplayPart [ ] = [ ] ;
var suffixParts : SymbolDisplayPart [ ] = [ ] ;
2014-09-25 08:44:13 +02:00
2014-11-13 01:30:25 +01:00
if ( invokerDisplayParts ) {
prefixParts . push . apply ( prefixParts , invokerDisplayParts ) ;
2014-10-16 19:35:34 +02:00
}
2014-09-25 08:44:13 +02:00
2014-11-11 22:58:50 +01:00
if ( isTypeParameterList ) {
2014-10-16 19:35:34 +02:00
prefixParts . push ( punctuationPart ( SyntaxKind . LessThanToken ) ) ;
var typeParameters = candidateSignature . typeParameters ;
signatureHelpParameters = typeParameters && typeParameters . length > 0 ? map ( typeParameters , createSignatureHelpParameterForTypeParameter ) : emptyArray ;
suffixParts . push ( punctuationPart ( SyntaxKind . GreaterThanToken ) ) ;
var parameterParts = mapToDisplayParts ( writer = >
2014-11-11 22:58:50 +01:00
typeInfoResolver . getSymbolDisplayBuilder ( ) . buildDisplayForParametersAndDelimiters ( candidateSignature . parameters , writer , invocation ) ) ;
2014-10-16 19:35:34 +02:00
suffixParts . push . apply ( suffixParts , parameterParts ) ;
}
else {
var typeParameterParts = mapToDisplayParts ( writer = >
2014-11-11 22:58:50 +01:00
typeInfoResolver . getSymbolDisplayBuilder ( ) . buildDisplayForTypeParametersAndDelimiters ( candidateSignature . typeParameters , writer , invocation ) ) ;
2014-10-16 19:35:34 +02:00
prefixParts . push . apply ( prefixParts , typeParameterParts ) ;
prefixParts . push ( punctuationPart ( SyntaxKind . OpenParenToken ) ) ;
var parameters = candidateSignature . parameters ;
signatureHelpParameters = parameters . length > 0 ? map ( parameters , createSignatureHelpParameterForParameter ) : emptyArray ;
suffixParts . push ( punctuationPart ( SyntaxKind . CloseParenToken ) ) ;
}
2014-09-25 08:44:13 +02:00
2014-10-16 00:25:29 +02:00
var returnTypeParts = mapToDisplayParts ( writer = >
2014-11-11 22:58:50 +01:00
typeInfoResolver . getSymbolDisplayBuilder ( ) . buildReturnTypeDisplay ( candidateSignature , writer , invocation ) ) ;
2014-10-16 00:25:29 +02:00
suffixParts . push . apply ( suffixParts , returnTypeParts ) ;
2014-09-25 08:44:13 +02:00
2014-09-24 22:58:03 +02:00
return {
isVariadic : candidateSignature.hasRestParameter ,
2014-09-25 23:27:42 +02:00
prefixDisplayParts : prefixParts ,
suffixDisplayParts : suffixParts ,
2014-10-16 00:25:29 +02:00
separatorDisplayParts : [ punctuationPart ( SyntaxKind . CommaToken ) , spacePart ( ) ] ,
2014-10-16 19:35:34 +02:00
parameters : signatureHelpParameters ,
2014-09-27 00:40:55 +02:00
documentation : candidateSignature.getDocumentationComment ( )
2014-09-24 22:58:03 +02:00
} ;
2014-09-22 20:38:01 +02:00
} ) ;
2014-09-25 08:44:13 +02:00
2014-09-24 00:34:42 +02:00
// 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)
2014-11-11 22:58:50 +01:00
var applicableSpanStart = argumentsList . pos ;
var applicableSpanEnd = skipTrivia ( sourceFile . text , argumentsList . end , /*stopAfterLineBreak*/ false ) ;
2014-09-22 20:38:01 +02:00
var applicableSpan = new TypeScript . TextSpan ( applicableSpanStart , applicableSpanEnd - applicableSpanStart ) ;
2014-09-26 20:10:46 +02:00
2014-11-11 22:58:50 +01:00
var argumentIndex = argumentListInfo . argumentIndex ;
2014-10-01 02:13:27 +02:00
2014-11-11 22:58:50 +01:00
// argumentCount is the *apparent* number of arguments.
var argumentCount = argumentListInfo . argumentCount ;
2014-10-01 02:13:27 +02:00
var selectedItemIndex = candidates . indexOf ( bestSignature ) ;
if ( selectedItemIndex < 0 ) {
selectedItemIndex = selectBestInvalidOverloadIndex ( candidates , argumentCount ) ;
}
2014-09-24 22:58:03 +02:00
return {
items : items ,
applicableSpan : applicableSpan ,
2014-09-26 20:10:46 +02:00
selectedItemIndex : selectedItemIndex ,
2014-09-30 22:46:07 +02:00
argumentIndex : argumentIndex ,
argumentCount : argumentCount
2014-09-24 22:58:03 +02:00
} ;
2014-10-16 19:35:34 +02:00
function createSignatureHelpParameterForParameter ( parameter : Symbol ) : SignatureHelpParameter {
var displayParts = mapToDisplayParts ( writer = >
2014-11-11 22:58:50 +01:00
typeInfoResolver . getSymbolDisplayBuilder ( ) . buildParameterDisplay ( parameter , writer , invocation ) ) ;
2014-10-16 19:35:34 +02:00
var isOptional = ! ! ( parameter . valueDeclaration . flags & NodeFlags . QuestionMark ) ;
return {
name : parameter.name ,
documentation : parameter.getDocumentationComment ( ) ,
displayParts : displayParts ,
isOptional : isOptional
} ;
}
function createSignatureHelpParameterForTypeParameter ( typeParameter : TypeParameter ) : SignatureHelpParameter {
var displayParts = mapToDisplayParts ( writer = >
2014-11-11 22:58:50 +01:00
typeInfoResolver . getSymbolDisplayBuilder ( ) . buildTypeParameterDisplay ( typeParameter , writer , invocation ) ) ;
2014-10-16 19:35:34 +02:00
return {
name : typeParameter.symbol.name ,
documentation : emptyArray ,
displayParts : displayParts ,
isOptional : false
} ;
}
2014-09-22 20:38:01 +02:00
}
}
2014-07-19 01:55:11 +02:00
}