From 49fdb98081ceba84e38591db7846d7f8162f05bd Mon Sep 17 00:00:00 2001 From: Jason Freeman Date: Thu, 4 Sep 2014 10:24:11 -0700 Subject: [PATCH] Signature help present with completed signatures --- Jakefile | 1 + src/services/services.ts | 120 +++- src/services/signatureInfoHelpers.ts | 662 ++++++++++-------- .../cases/fourslash/signatureHelpEmptyList.ts | 20 + 4 files changed, 508 insertions(+), 295 deletions(-) create mode 100644 tests/cases/fourslash/signatureHelpEmptyList.ts diff --git a/Jakefile b/Jakefile index 1307601fb81..bb481fbcd1e 100644 --- a/Jakefile +++ b/Jakefile @@ -54,6 +54,7 @@ var servicesSources = [ }).concat([ "services.ts", "shims.ts", + "signatureInfoHelpers.ts" ].map(function (f) { return path.join(servicesDirectory, f); })); diff --git a/src/services/services.ts b/src/services/services.ts index 04e63f6dbe1..d763785ae1e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -10,6 +10,7 @@ /// /// /// +/// /// /// @@ -95,6 +96,7 @@ module ts { public flags: NodeFlags; public parent: Node; private _children: Node[]; + private _syntheticParent: Node; public getSourceFile(): SourceFile { var node: Node = this; @@ -150,6 +152,9 @@ module ts { if (pos < node.pos) { pos = this.addSyntheticNodes(list._children, pos, node.pos); } + else { + (node)._syntheticParent = list; + } list._children.push(node); pos = node.end; } @@ -202,8 +207,13 @@ module ts { return this._children; } + public getIndexOfChild(child: Node): number { + if (!this._children) this.createChildren(); + return this._children.indexOf(child); + } + public getFirstToken(sourceFile?: SourceFile): Node { - var children = this.getChildren(sourceFile); + var children = this.getChildren(); for (var i = 0; i < children.length; i++) { var child = children[i]; if (child.kind < SyntaxKind.Missing) return child; @@ -219,6 +229,10 @@ module ts { if (child.kind > SyntaxKind.Missing) return child.getLastToken(sourceFile); } } + + public getSyntheticParentOrParent(): Node { + return this._syntheticParent || this.parent; + } } class SymbolObject implements Symbol { @@ -3474,7 +3488,105 @@ module ts { // Reset writer back to undefined to make sure that we produce an error message if CompilerHost.writeFile method is called when we are not in getEmitOutput writer = undefined; - return emitOutput; + return emitOutput; + } + + // Signature help + function getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems { + // If node is an argument, returns its index in the argument list + // If not, returns -1 + function getArgumentIndex(node: Node): number { + // Treat the open paren / angle bracket of a call as the introduction of parameter slot 0 + var parent = (node).getSyntheticParentOrParent(); + if (parent.kind === SyntaxKind.SyntaxList) { + var grandparent = parent.parent; + if (grandparent.kind === SyntaxKind.CallExpression || grandparent.kind === SyntaxKind.NewExpression) { + var index = (parent).getIndexOfChild(node); + Debug.assert(index >= 0); + return index; + } + } + + if (node.kind === SyntaxKind.LessThanToken || node.kind === SyntaxKind.OpenParenToken) { + return parent.kind === SyntaxKind.CallExpression || parent.kind === SyntaxKind.NewExpression + ? 0 + : -1; + } + + // TODO: Handle close paren or close angle bracket on nonempty list + + return -1; + } + //// Technically signature help should only be triggered on these characters + //if (node.kind !== SyntaxKind.CommaToken && node.kind !== SyntaxKind.OpenParenToken && node.kind !== SyntaxKind.LessThanToken) { + // return false; + //} + + //if (node.kind === SyntaxKind.CommaToken) { + // if (node.parent.kind !== SyntaxKind.SyntaxList) { + // return false; + // } + + // // node becomes the containing SyntaxList + // node = node.parent; + //} + + //// node is open paren, less than, or a syntax list containing a comma + //if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) { + // return true; + //} + + synchronizeHostData(); + + // Decide whether to show signature help + var sourceFile = getSourceFile(fileName); + var node = getNodeAtPosition(sourceFile, position); + // We only want this node if it is a token and it strictly contains the current position. + // Otherwise we want the previous token + var isToken = node.kind < SyntaxKind.Missing; + if (!isToken || position <= node.getStart() || position >= node.getEnd()) { + // This is a temporary hack until we figure out our token story. + // The correct solution is to get the previous token + node = SignatureInfoHelpers.findClosestRightmostSiblingFromLeft(position, sourceFile); + + if (!node) { + return undefined; + } + if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) { + if (node === (node.parent).func) { + node = node.parent.getChildAt(1); + } + } + } + + var signatureHelpAvailable = false; + for (var n = node; n.kind !== SyntaxKind.SourceFile; n = n.parent) { + if (n.kind === SyntaxKind.FunctionBlock) { + break; + } + + var index = getArgumentIndex(n); + if (index >= 0) { + signatureHelpAvailable = true; + break; + } + + + // TODO: Handle previous token logic + // TODO: Handle generic call with incomplete + } + + + return signatureHelpAvailable + ? new SignatureHelpItems(undefined, undefined, undefined) + : undefined; + + + } + + function getSignatureHelpCurrentArgumentState(fileName: string, position: number, applicableSpanStart: number): SignatureHelpState { + synchronizeHostData(); + return null; } /// Syntactic features @@ -4026,8 +4138,8 @@ module ts { getCompletionsAtPosition: getCompletionsAtPosition, getCompletionEntryDetails: getCompletionEntryDetails, getTypeAtPosition: getTypeAtPosition, - getSignatureHelpItems: (filename, position): SignatureHelpItems => null, - getSignatureHelpCurrentArgumentState: (fileName, position, applicableSpanStart): SignatureHelpState => null, + getSignatureHelpItems: getSignatureHelpItems, + getSignatureHelpCurrentArgumentState: getSignatureHelpCurrentArgumentState, getDefinitionAtPosition: getDefinitionAtPosition, getReferencesAtPosition: getReferencesAtPosition, getOccurrencesAtPosition: getOccurrencesAtPosition, diff --git a/src/services/signatureInfoHelpers.ts b/src/services/signatureInfoHelpers.ts index 8594a5dd320..875aa3cfcd7 100644 --- a/src/services/signatureInfoHelpers.ts +++ b/src/services/signatureInfoHelpers.ts @@ -3,344 +3,424 @@ /// -module TypeScript.Services { +module ts { - export interface IPartiallyWrittenTypeArgumentListInformation { - genericIdentifer: TypeScript.ISyntaxToken; - lessThanToken: TypeScript.ISyntaxToken; - argumentIndex: number; - } + //export interface IPartiallyWrittenTypeArgumentListInformation { + // genericIdentifer: TypeScript.ISyntaxToken; + // lessThanToken: TypeScript.ISyntaxToken; + // argumentIndex: number; + //} - export interface IExpressionWithArgumentListSyntax extends IExpressionSyntax { - expression: IExpressionSyntax; - argumentList: ArgumentListSyntax; - } + //export interface IExpressionWithArgumentListSyntax extends IExpressionSyntax { + // expression: IExpressionSyntax; + // argumentList: ArgumentListSyntax; + //} - export class SignatureInfoHelpers { + export module SignatureInfoHelpers { // 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 1; + //// var hasOverloads = signatures.length > 1; - for (var i = 0, n = signatures.length; i < n; i++) { - var signature = signatures[i]; + //// for (var i = 0, n = signatures.length; i < n; i++) { + //// var signature = signatures[i]; - // filter out the definition signature if there are overloads - if (hasOverloads && signature.isDefinition()) { - continue; - } + //// // filter out the definition signature if there are overloads + //// if (hasOverloads && signature.isDefinition()) { + //// continue; + //// } - var signatureGroupInfo = new FormalSignatureItemInfo(); - var paramIndexInfo: number[] = []; - var functionName = signature.getScopedNameEx(enclosingScopeSymbol).toString(); - if (!functionName && (!symbol.isType() || (symbol).isNamedTypeSymbol())) { - functionName = symbol.getScopedNameEx(enclosingScopeSymbol).toString(); - } + //// var signatureGroupInfo = new FormalSignatureItemInfo(); + //// var paramIndexInfo: number[] = []; + //// var functionName = signature.getScopedNameEx(enclosingScopeSymbol).toString(); + //// if (!functionName && (!symbol.isType() || (symbol).isNamedTypeSymbol())) { + //// functionName = symbol.getScopedNameEx(enclosingScopeSymbol).toString(); + //// } - var signatureMemberName = signature.getSignatureTypeNameEx(functionName, /*shortform*/ false, /*brackets*/ false, enclosingScopeSymbol, /*getParamMarkerInfo*/ true, /*getTypeParameterMarkerInfo*/ true); - signatureGroupInfo.signatureInfo = TypeScript.MemberName.memberNameToString(signatureMemberName, paramIndexInfo); - signatureGroupInfo.docComment = signature.docComments(); + //// var signatureMemberName = signature.getSignatureTypeNameEx(functionName, /*shortform*/ false, /*brackets*/ false, enclosingScopeSymbol, /*getParamMarkerInfo*/ true, /*getTypeParameterMarkerInfo*/ true); + //// signatureGroupInfo.signatureInfo = TypeScript.MemberName.memberNameToString(signatureMemberName, paramIndexInfo); + //// signatureGroupInfo.docComment = signature.docComments(); - var parameterMarkerIndex = 0; + //// var parameterMarkerIndex = 0; - if (signature.isGeneric()) { - var typeParameters = signature.getTypeParameters(); - for (var j = 0, m = typeParameters.length; j < m; j++) { - var typeParameter = typeParameters[j]; - var signatureTypeParameterInfo = new FormalTypeParameterInfo(); - signatureTypeParameterInfo.name = typeParameter.getDisplayName(); - signatureTypeParameterInfo.docComment = typeParameter.docComments(); - signatureTypeParameterInfo.minChar = paramIndexInfo[2 * parameterMarkerIndex]; - signatureTypeParameterInfo.limChar = paramIndexInfo[2 * parameterMarkerIndex + 1]; - parameterMarkerIndex++; - signatureGroupInfo.typeParameters.push(signatureTypeParameterInfo); + //// if (signature.isGeneric()) { + //// var typeParameters = signature.getTypeParameters(); + //// for (var j = 0, m = typeParameters.length; j < m; j++) { + //// var typeParameter = typeParameters[j]; + //// var signatureTypeParameterInfo = new FormalTypeParameterInfo(); + //// signatureTypeParameterInfo.name = typeParameter.getDisplayName(); + //// signatureTypeParameterInfo.docComment = typeParameter.docComments(); + //// signatureTypeParameterInfo.minChar = paramIndexInfo[2 * parameterMarkerIndex]; + //// signatureTypeParameterInfo.limChar = paramIndexInfo[2 * parameterMarkerIndex + 1]; + //// parameterMarkerIndex++; + //// signatureGroupInfo.typeParameters.push(signatureTypeParameterInfo); + //// } + //// } + + //// var parameters = signature.parameters; + //// for (var j = 0, m = parameters.length; j < m; j++) { + //// var parameter = parameters[j]; + //// var signatureParameterInfo = new FormalParameterInfo(); + //// signatureParameterInfo.isVariable = signature.hasVarArgs && (j === parameters.length - 1); + //// signatureParameterInfo.name = parameter.getDisplayName(); + //// signatureParameterInfo.docComment = parameter.docComments(); + //// signatureParameterInfo.minChar = paramIndexInfo[2 * parameterMarkerIndex]; + //// signatureParameterInfo.limChar = paramIndexInfo[2 * parameterMarkerIndex + 1]; + //// parameterMarkerIndex++; + //// signatureGroupInfo.parameters.push(signatureParameterInfo); + //// } + + //// signatureGroup.push(signatureGroupInfo); + //// } + + //// return signatureGroup; + ////} + + ////public static getSignatureInfoFromGenericSymbol(symbol: TypeScript.PullSymbol, enclosingScopeSymbol: TypeScript.PullSymbol, compilerState: LanguageServiceCompiler) { + //// var signatureGroupInfo = new FormalSignatureItemInfo(); + + //// var paramIndexInfo: number[] = []; + //// var symbolName = symbol.getScopedNameEx(enclosingScopeSymbol, /*skipTypeParametersInName*/ false, /*useConstaintInName*/ true, /*getPrettyTypeName*/ false, /*getTypeParamMarkerInfo*/ true); + + //// signatureGroupInfo.signatureInfo = TypeScript.MemberName.memberNameToString(symbolName, paramIndexInfo); + //// signatureGroupInfo.docComment = symbol.docComments(); + + //// var parameterMarkerIndex = 0; + + //// var typeSymbol = symbol.type; + + //// var typeParameters = typeSymbol.getTypeParameters(); + //// for (var i = 0, n = typeParameters.length; i < n; i++) { + //// var typeParameter = typeParameters[i]; + //// var signatureTypeParameterInfo = new FormalTypeParameterInfo(); + //// signatureTypeParameterInfo.name = typeParameter.getDisplayName(); + //// signatureTypeParameterInfo.docComment = typeParameter.docComments(); + //// signatureTypeParameterInfo.minChar = paramIndexInfo[2 * i]; + //// signatureTypeParameterInfo.limChar = paramIndexInfo[2 * i + 1]; + //// signatureGroupInfo.typeParameters.push(signatureTypeParameterInfo); + //// } + + //// return [signatureGroupInfo]; + ////} + + ////public static getActualSignatureInfoFromCallExpression(ast: IExpressionWithArgumentListSyntax, caretPosition: number, typeParameterInformation: IPartiallyWrittenTypeArgumentListInformation): ActualSignatureInfo { + //// if (!ast) { + //// return null; + //// } + + //// var result = new ActualSignatureInfo(); + + //// // The expression is not guaranteed to be complete, we need to populate the min and lim with the most accurate information we have about + //// // type argument and argument lists + //// var parameterMinChar = caretPosition; + //// var parameterLimChar = caretPosition; + + //// if (ast.argumentList.typeArgumentList) { + //// parameterMinChar = Math.min(start(ast.argumentList.typeArgumentList)); + //// parameterLimChar = Math.max(Math.max(start(ast.argumentList.typeArgumentList), end(ast.argumentList.typeArgumentList) + trailingTriviaWidth(ast.argumentList.typeArgumentList))); + //// } + + //// if (ast.argumentList.arguments) { + //// parameterMinChar = Math.min(parameterMinChar, end(ast.argumentList.openParenToken)); + //// parameterLimChar = Math.max(parameterLimChar, + //// ast.argumentList.closeParenToken.fullWidth() > 0 ? start(ast.argumentList.closeParenToken) : fullEnd(ast.argumentList)); + //// } + + //// result.parameterMinChar = parameterMinChar; + //// result.parameterLimChar = parameterLimChar; + //// result.currentParameterIsTypeParameter = false; + //// result.currentParameter = -1; + + //// if (typeParameterInformation) { + //// result.currentParameterIsTypeParameter = true; + //// result.currentParameter = typeParameterInformation.argumentIndex; + //// } + //// else if (ast.argumentList.arguments && ast.argumentList.arguments.length > 0) { + //// result.currentParameter = 0; + //// for (var index = 0; index < ast.argumentList.arguments.length; index++) { + //// if (caretPosition > end(ast.argumentList.arguments[index]) + lastToken(ast.argumentList.arguments[index]).trailingTriviaWidth()) { + //// result.currentParameter++; + //// } + //// } + //// } + + //// return result; + ////} + + ////public static getActualSignatureInfoFromPartiallyWritenGenericExpression(caretPosition: number, typeParameterInformation: IPartiallyWrittenTypeArgumentListInformation): ActualSignatureInfo { + //// var result = new ActualSignatureInfo(); + + //// result.parameterMinChar = start(typeParameterInformation.lessThanToken); + //// result.parameterLimChar = Math.max(fullEnd(typeParameterInformation.lessThanToken), caretPosition); + //// result.currentParameterIsTypeParameter = true; + //// result.currentParameter = typeParameterInformation.argumentIndex; + + //// return result; + ////} + + ////public static isSignatureHelpBlocker(sourceUnit: TypeScript.SourceUnitSyntax, position: number): boolean { + //// // We shouldn't be getting a possition that is outside the file because + //// // isEntirelyInsideComment can't handle when the position is out of bounds, + //// // callers should be fixed, however we should be resiliant to bad inputs + //// // so we return true (this position is a blocker for getting signature help) + //// if (position < 0 || position > fullWidth(sourceUnit)) { + //// return true; + //// } + + //// return TypeScript.Syntax.isEntirelyInsideComment(sourceUnit, position); + ////} + + ////public static isTargetOfObjectCreationExpression(positionedToken: TypeScript.ISyntaxToken): boolean { + //// var positionedParent = TypeScript.Syntax.getAncestorOfKind(positionedToken, TypeScript.SyntaxKind.ObjectCreationExpression); + //// if (positionedParent) { + //// var objectCreationExpression = positionedParent; + //// var expressionRelativeStart = objectCreationExpression.newKeyword.fullWidth(); + //// var tokenRelativeStart = positionedToken.fullStart() - fullStart(positionedParent); + //// return tokenRelativeStart >= expressionRelativeStart && + //// tokenRelativeStart <= (expressionRelativeStart + fullWidth(objectCreationExpression.expression)); + //// } + + //// return false; + ////} + + //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; + //} + + export function findClosestRightmostSiblingFromLeft(position: number, sourceFile: SourceFile): Node { + return search(sourceFile); + + function search(n: Node): Node { + + var childCandidate: Node; + forEachChild(n, c => { + if (c.kind === SyntaxKind.OmittedExpression || c.kind === SyntaxKind.Missing) { + return; } - } - - var parameters = signature.parameters; - for (var j = 0, m = parameters.length; j < m; j++) { - var parameter = parameters[j]; - var signatureParameterInfo = new FormalParameterInfo(); - signatureParameterInfo.isVariable = signature.hasVarArgs && (j === parameters.length - 1); - signatureParameterInfo.name = parameter.getDisplayName(); - signatureParameterInfo.docComment = parameter.docComments(); - signatureParameterInfo.minChar = paramIndexInfo[2 * parameterMarkerIndex]; - signatureParameterInfo.limChar = paramIndexInfo[2 * parameterMarkerIndex + 1]; - parameterMarkerIndex++; - signatureGroupInfo.parameters.push(signatureParameterInfo); - } - - signatureGroup.push(signatureGroupInfo); - } - - return signatureGroup; - } - - public static getSignatureInfoFromGenericSymbol(symbol: TypeScript.PullSymbol, enclosingScopeSymbol: TypeScript.PullSymbol, compilerState: LanguageServiceCompiler) { - var signatureGroupInfo = new FormalSignatureItemInfo(); - - var paramIndexInfo: number[] = []; - var symbolName = symbol.getScopedNameEx(enclosingScopeSymbol, /*skipTypeParametersInName*/ false, /*useConstaintInName*/ true, /*getPrettyTypeName*/ false, /*getTypeParamMarkerInfo*/ true); - - signatureGroupInfo.signatureInfo = TypeScript.MemberName.memberNameToString(symbolName, paramIndexInfo); - signatureGroupInfo.docComment = symbol.docComments(); - - var typeSymbol = symbol.type; - - var typeParameters = typeSymbol.getTypeParameters(); - for (var i = 0, n = typeParameters.length; i < n; i++) { - var typeParameter = typeParameters[i]; - var signatureTypeParameterInfo = new FormalTypeParameterInfo(); - signatureTypeParameterInfo.name = typeParameter.getDisplayName(); - signatureTypeParameterInfo.docComment = typeParameter.docComments(); - signatureTypeParameterInfo.minChar = paramIndexInfo[2 * i]; - signatureTypeParameterInfo.limChar = paramIndexInfo[2 * i + 1]; - signatureGroupInfo.typeParameters.push(signatureTypeParameterInfo); - } - - return [signatureGroupInfo]; - } - - public static getActualSignatureInfoFromCallExpression(ast: IExpressionWithArgumentListSyntax, caretPosition: number, typeParameterInformation: IPartiallyWrittenTypeArgumentListInformation): ActualSignatureInfo { - if (!ast) { - return null; - } - - var result = new ActualSignatureInfo(); - - // The expression is not guaranteed to be complete, we need to populate the min and lim with the most accurate information we have about - // type argument and argument lists - var parameterMinChar = caretPosition; - var parameterLimChar = caretPosition; - - if (ast.argumentList.typeArgumentList) { - parameterMinChar = Math.min(start(ast.argumentList.typeArgumentList)); - parameterLimChar = Math.max(Math.max(start(ast.argumentList.typeArgumentList), end(ast.argumentList.typeArgumentList) + trailingTriviaWidth(ast.argumentList.typeArgumentList))); - } - - if (ast.argumentList.arguments) { - parameterMinChar = Math.min(parameterMinChar, end(ast.argumentList.openParenToken)); - parameterLimChar = Math.max(parameterLimChar, - ast.argumentList.closeParenToken.fullWidth() > 0 ? start(ast.argumentList.closeParenToken) : fullEnd(ast.argumentList)); - } - - result.parameterMinChar = parameterMinChar; - result.parameterLimChar = parameterLimChar; - result.currentParameterIsTypeParameter = false; - result.currentParameter = -1; - - if (typeParameterInformation) { - result.currentParameterIsTypeParameter = true; - result.currentParameter = typeParameterInformation.argumentIndex; - } - else if (ast.argumentList.arguments && ast.argumentList.arguments.length > 0) { - result.currentParameter = 0; - for (var index = 0; index < ast.argumentList.arguments.length; index++) { - if (caretPosition > end(ast.argumentList.arguments[index]) + lastToken(ast.argumentList.arguments[index]).trailingTriviaWidth()) { - result.currentParameter++; + if (c.end <= position || c.getStart() < position) { + childCandidate = c; } - } - } + return c.end > position; + }); - return result; - } - - public static getActualSignatureInfoFromPartiallyWritenGenericExpression(caretPosition: number, typeParameterInformation: IPartiallyWrittenTypeArgumentListInformation): ActualSignatureInfo { - var result = new ActualSignatureInfo(); - - result.parameterMinChar = start(typeParameterInformation.lessThanToken); - result.parameterLimChar = Math.max(fullEnd(typeParameterInformation.lessThanToken), caretPosition); - result.currentParameterIsTypeParameter = true; - result.currentParameter = typeParameterInformation.argumentIndex; - - return result; - } - - public static isSignatureHelpBlocker(sourceUnit: TypeScript.SourceUnitSyntax, position: number): boolean { - // We shouldn't be getting a possition that is outside the file because - // isEntirelyInsideComment can't handle when the position is out of bounds, - // callers should be fixed, however we should be resiliant to bad inputs - // so we return true (this position is a blocker for getting signature help) - if (position < 0 || position > fullWidth(sourceUnit)) { - return true; - } - - return TypeScript.Syntax.isEntirelyInsideComment(sourceUnit, position); - } - - public static isTargetOfObjectCreationExpression(positionedToken: TypeScript.ISyntaxToken): boolean { - var positionedParent = TypeScript.Syntax.getAncestorOfKind(positionedToken, TypeScript.SyntaxKind.ObjectCreationExpression); - if (positionedParent) { - var objectCreationExpression = positionedParent; - var expressionRelativeStart = objectCreationExpression.newKeyword.fullWidth(); - var tokenRelativeStart = positionedToken.fullStart() - fullStart(positionedParent); - return tokenRelativeStart >= expressionRelativeStart && - tokenRelativeStart <= (expressionRelativeStart + fullWidth(objectCreationExpression.expression)); - } - - return false; - } - - 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; + if (childCandidate) { + if (childCandidate.end > position || !isCompletedNode(childCandidate, position, sourceFile)) { + return search(childCandidate) || childCandidate; } else { - stack--; + return childCandidate; } } - else if (token.kind() === tokenKind) { - stack++; - } - - // Move back - token = previousToken(token, /*includeSkippedTokens*/ true); } - - // Did not find matching token - return null; } + + function isCompletedNode(n: Node, position: number, sourceFile: SourceFile): boolean { + switch (n.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ObjectLiteral: + case SyntaxKind.Block: + case SyntaxKind.CatchBlock: + case SyntaxKind.FinallyBlock: + case SyntaxKind.FunctionBlock: + case SyntaxKind.ModuleBlock: + return isNodeEndWith(n, sourceFile, CharacterCodes.closeBrace); + case SyntaxKind.ParenExpression: + case SyntaxKind.CallSignature: + case SyntaxKind.CallExpression: + return isNodeEndWith(n, sourceFile, CharacterCodes.closeParen); + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.Method: + case SyntaxKind.ArrowFunction: + return !(n).body || isCompletedNode((n).body, position, sourceFile); + case SyntaxKind.ModuleDeclaration: + return (n).body && isCompletedNode((n).body, position, sourceFile); + case SyntaxKind.IfStatement: + if ((n).thenStatement && (n).thenStatement.end > position) { + return isCompletedNode((n).thenStatement, position, sourceFile); + } + else if ((n).elseStatement) { + return isCompletedNode((n).elseStatement, position, sourceFile); + } + else { + return true; + } + break; + case SyntaxKind.ExpressionStatement: + return isCompletedNode((n).expression, position, sourceFile); + case SyntaxKind.ArrayLiteral: + return isNodeEndWith(n, sourceFile, CharacterCodes.closeBracket); + case SyntaxKind.Missing: + return false; + default: + return true; + } + } + + function isNodeEndWith(n: Node, sourceFile: SourceFile, charCode: CharacterCodes): boolean { + var pos = n.end - 1; // Node.end is exclusive + return pos < sourceFile.text.length && sourceFile.text.charCodeAt(pos) === charCode; + } + } } \ No newline at end of file diff --git a/tests/cases/fourslash/signatureHelpEmptyList.ts b/tests/cases/fourslash/signatureHelpEmptyList.ts new file mode 100644 index 00000000000..6ea70159dcc --- /dev/null +++ b/tests/cases/fourslash/signatureHelpEmptyList.ts @@ -0,0 +1,20 @@ +/// + +////function Foo(arg1: string, arg2: string) { +////} +//// +////Foo(/*1*/); +////function Bar(arg1: string, arg2: string) { } +////Bar(); + +goTo.marker('1'); +verify.signatureHelpPresent(); +verify.signatureHelpCountIs(1); + +verify.currentSignatureHelpIs("Foo(arg1: string, arg2: string): void"); +verify.currentSignatureParamterCountIs(2); +verify.currentParameterHelpArgumentNameIs("arg1"); +verify.currentParameterSpanIs("arg1: string"); + +goTo.marker('2'); +verify.signatureHelpPresent(); \ No newline at end of file