From 21587aaae3be8bf7840ab8be66793d4d4b32e1f2 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Mon, 2 May 2016 16:29:53 -0700 Subject: [PATCH 1/9] Rewrite isInStringLiteral to accomodate for unterminated strings --- src/services/utilities.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 270c7d85d45bf..a625a2d647693 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -401,9 +401,27 @@ namespace ts { } } - export function isInString(sourceFile: SourceFile, position: number) { - let token = getTokenAtPosition(sourceFile, position); - return token && (token.kind === SyntaxKind.StringLiteral || token.kind === SyntaxKind.StringLiteralType) && position > token.getStart(sourceFile); + export function isInString(sourceFile: SourceFile, position: number): boolean { + const previousToken = findPrecedingToken(position, sourceFile); + if (previousToken && + (previousToken.kind === SyntaxKind.StringLiteral || previousToken.kind === SyntaxKind.StringLiteralType)) { + const start = previousToken.getStart(); + const end = previousToken.getEnd(); + + // To be "in" one of these literals, the position has to be: + // 1. entirely within the token text. + // 2. at the end position of an unterminated token. + // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). + if (start < position && position < end) { + return true; + } + + if (position === end) { + return !!(previousToken).isUnterminated; + } + } + + return false; } export function isInComment(sourceFile: SourceFile, position: number) { From 0a277a1c601153c355dc76454cfebd770bf4cea9 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Mon, 2 May 2016 16:30:18 -0700 Subject: [PATCH 2/9] Refactor signatureHelp to expose helper functions --- src/services/signatureHelp.ts | 720 +++++++++++++++++----------------- 1 file changed, 360 insertions(+), 360 deletions(-) diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 9e8a2f14ed8c0..fa7cc42e0d10f 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); - - 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); - } + 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, 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); - } + /** + * 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 + // + // 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, sourceFile), + argumentIndex: 0, + argumentCount: getArgumentCount(list) + }; } - 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; + // 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; - 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 argumentIndex = getArgumentIndex(list, node); + let argumentCount = getArgumentCount(list); - let spanIndex = templateExpression.templateSpans.indexOf(templateSpan); - let argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node); + Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, + `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); - return getArgumentListInfoForTemplate(tagExpression, argumentIndex); + return { + kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments, + invocation: callExpression, + argumentsSpan: getApplicableSpanForArguments(list, sourceFile), + argumentIndex: argumentIndex, + argumentCount: argumentCount + }; } - - return undefined; } + 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); - 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++; - } + 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; } - return argumentIndex; + let spanIndex = templateExpression.templateSpans.indexOf(templateSpan); + let argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position); + + return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); } - 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 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. + // + // 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; + } - return argumentCount; + 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++; } - // 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; + 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 + 1; + return spanIndex + 2; } + return spanIndex + 1; + } - 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; + 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 + }; + } - Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); + 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); + } - return { - kind: ArgumentListKind.TaggedTemplateArguments, - invocation: tagExpression, - argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression), - argumentIndex: argumentIndex, - argumentCount: argumentCount - }; + 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); + } } - 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); - } + return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); + } - function getApplicableSpanForTaggedTemplate(taggedTemplate: TaggedTemplateExpression): TextSpan { - let template = taggedTemplate.template; - let applicableSpanStart = template.getStart(); - let applicableSpanEnd = template.getEnd(); + 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; + } - // 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); - } + // 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); } - return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart); - } + let argumentInfo = getImmediatelyContainingArgumentInfo(n, position, sourceFile); + if (argumentInfo) { + return argumentInfo; + } - 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); - } + // TODO: Handle generic call with incomplete syntax + } + return undefined; + } - let argumentInfo = getImmediatelyContainingArgumentInfo(n); - if (argumentInfo) { - return argumentInfo; - } + 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; + } - // TODO: Handle generic call with incomplete syntax + if (candidate.parameters.length > maxParams) { + maxParams = candidate.parameters.length; + maxParamsSignatureIndex = i; } - 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]; - } + return maxParamsSignatureIndex; + } - /** - * 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; - } + 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 (candidate.parameters.length > maxParams) { - maxParams = candidate.parameters.length; - maxParamsSignatureIndex = i; - } + 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)); } - return maxParamsSignatureIndex; - } + let returnTypeParts = mapToDisplayParts(writer => + typeChecker.getSymbolDisplayBuilder().buildReturnTypeDisplay(candidateSignature, writer, invocation)); + addRange(suffixDisplayParts, returnTypeParts); - 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); - } + return { + isVariadic: candidateSignature.hasRestParameter, + prefixDisplayParts, + suffixDisplayParts, + separatorDisplayParts: [punctuationPart(SyntaxKind.CommaToken), spacePart()], + parameters: signatureHelpParameters, + documentation: candidateSignature.getDocumentationComment() + }; + }); - 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 argumentIndex = argumentListInfo.argumentIndex; - 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() - }; - }); + // argumentCount is the *apparent* number of arguments. + let argumentCount = argumentListInfo.argumentCount; - let argumentIndex = argumentListInfo.argumentIndex; + let selectedItemIndex = candidates.indexOf(bestSignature); + if (selectedItemIndex < 0) { + selectedItemIndex = selectBestInvalidOverloadIndex(candidates, argumentCount); + } - // argumentCount is the *apparent* number of arguments. - let argumentCount = argumentListInfo.argumentCount; + Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); - let selectedItemIndex = candidates.indexOf(bestSignature); - if (selectedItemIndex < 0) { - selectedItemIndex = selectBestInvalidOverloadIndex(candidates, argumentCount); - } + return { + items, + applicableSpan, + selectedItemIndex, + argumentIndex, + argumentCount + }; - Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); + function createSignatureHelpParameterForParameter(parameter: Symbol): SignatureHelpParameter { + let displayParts = mapToDisplayParts(writer => + typeChecker.getSymbolDisplayBuilder().buildParameterDisplay(parameter, writer, invocation)); return { - items, - applicableSpan, - selectedItemIndex, - argumentIndex, - argumentCount + name: parameter.name, + documentation: parameter.getDocumentationComment(), + displayParts, + isOptional: typeChecker.isOptionalParameter(parameter.valueDeclaration) }; + } - 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)); + 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 - }; - } + return { + name: typeParameter.symbol.name, + documentation: emptyArray, + displayParts, + isOptional: false + }; } } } \ No newline at end of file From 060f2a85637efdc79c7af3126fc8cef25d87a241 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Mon, 2 May 2016 16:30:50 -0700 Subject: [PATCH 3/9] Add support for completion in string literals --- src/services/services.ts | 118 +++++++++++++++--- .../fourslash/completionForStringLiteral.ts | 15 +++ .../fourslash/completionForStringLiteral2.ts | 20 +++ .../fourslash/completionForStringLiteral3.ts | 20 +++ 4 files changed, 158 insertions(+), 15 deletions(-) create mode 100644 tests/cases/fourslash/completionForStringLiteral.ts create mode 100644 tests/cases/fourslash/completionForStringLiteral2.ts create mode 100644 tests/cases/fourslash/completionForStringLiteral3.ts diff --git a/src/services/services.ts b/src/services/services.ts index 8d28d5d958a73..eeaef60afca7a 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1032,10 +1032,10 @@ namespace ts { getCancellationToken?(): HostCancellationToken; getCurrentDirectory(): string; getDefaultLibFileName(options: CompilerOptions): string; - log? (s: string): void; - trace? (s: string): void; - error? (s: string): void; - useCaseSensitiveFileNames? (): boolean; + log?(s: string): void; + trace?(s: string): void; + error?(s: string): void; + useCaseSensitiveFileNames?(): boolean; /* * LS host can optionally implement this method if it wants to be completely in charge of module name resolution. @@ -2416,7 +2416,7 @@ namespace ts { } // should be start of dependency list - if (token !== SyntaxKind.OpenBracketToken) { + if (token !== SyntaxKind.OpenBracketToken) { return true; } @@ -3912,10 +3912,15 @@ namespace ts { } } - function getCompletionsAtPosition(fileName: string, position: number): CompletionInfo { synchronizeHostData(); + const sourceFile = getValidSourceFile(fileName); + + if (isInString(sourceFile, position)) { + return getStringLiteralCompletionEntries(sourceFile, position); + } + const completionData = getCompletionData(fileName, position); if (!completionData) { return undefined; @@ -3928,12 +3933,10 @@ namespace ts { return { isMemberCompletion: false, isNewIdentifierLocation: false, entries: getAllJsDocCompletionEntries() }; } - const sourceFile = getValidSourceFile(fileName); - const entries: CompletionEntry[] = []; if (isSourceFileJavaScript(sourceFile)) { - const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries); + const uniqueNames = getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ false); addRange(entries, getJavaScriptCompletionEntries(sourceFile, location.pos, uniqueNames)); } else { @@ -3957,7 +3960,7 @@ namespace ts { } } - getCompletionEntriesFromSymbols(symbols, entries); + getCompletionEntriesFromSymbols(symbols, entries, location, /*performCharacterChecks*/ true); } // Add keywords if this is not a member completion list @@ -4007,11 +4010,11 @@ namespace ts { })); } - function createCompletionEntry(symbol: Symbol, location: Node): CompletionEntry { + function createCompletionEntry(symbol: Symbol, location: Node, performCharacterChecks: boolean): CompletionEntry { // Try to get a valid display name for this symbol, if we could not find one, then ignore it. // We would like to only show things that can be added after a dot, so for instance numeric properties can // not be accessed with a dot (a.1 <- invalid) - const displayName = getCompletionEntryDisplayNameForSymbol(symbol, program.getCompilerOptions().target, /*performCharacterChecks*/ true, location); + const displayName = getCompletionEntryDisplayNameForSymbol(symbol, program.getCompilerOptions().target, performCharacterChecks, location); if (!displayName) { return undefined; } @@ -4032,12 +4035,12 @@ namespace ts { }; } - function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[]): Map { + function getCompletionEntriesFromSymbols(symbols: Symbol[], entries: CompletionEntry[], location: Node, performCharacterChecks: boolean): Map { const start = new Date().getTime(); const uniqueNames: Map = {}; if (symbols) { for (const symbol of symbols) { - const entry = createCompletionEntry(symbol, location); + const entry = createCompletionEntry(symbol, location, performCharacterChecks); if (entry) { const id = escapeIdentifier(entry.name); if (!lookUp(uniqueNames, id)) { @@ -4051,6 +4054,91 @@ namespace ts { log("getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + (new Date().getTime() - start)); return uniqueNames; } + + function getStringLiteralCompletionEntries(sourceFile: SourceFile, position: number) { + const node = findPrecedingToken(position, sourceFile); + if (!node || node.kind !== SyntaxKind.StringLiteral) { + return undefined; + } + + isNameOfExternalModuleImportOrDeclaration + const argumentInfo = SignatureHelp.getContainingArgumentInfo(node, position, sourceFile); + if (argumentInfo) { + return getStringLiteralCompletionEntriesFromCallExpression(argumentInfo); + } + else if (isElementAccessExpression(node.parent) && node.parent.argumentExpression === node) { + return getStringLiteralCompletionEntriesFromElementAccess(node.parent); + } + else { + return getStringLiteralCompletionEntriesFromContextualType(node); + } + } + + function getStringLiteralCompletionEntriesFromCallExpression(argumentInfo: SignatureHelp.ArgumentListInfo) { + const typeChecker = program.getTypeChecker(); + const candidates: Signature[] = []; + const entries: CompletionEntry[] = []; + + typeChecker.getResolvedSignature(argumentInfo.invocation, candidates); + + for (const candidate of candidates) { + if (candidate.parameters.length > argumentInfo.argumentIndex) { + const parameter = candidate.parameters[argumentInfo.argumentIndex]; + addStringLiteralCompletionsFromType(typeChecker.getTypeAtLocation(parameter.valueDeclaration), entries); + } + } + + if (entries.length) { + return { isMemberCompletion: false, isNewIdentifierLocation: true, entries: entries }; + } + + return undefined; + } + + function getStringLiteralCompletionEntriesFromElementAccess(node: ElementAccessExpression) { + const typeChecker = program.getTypeChecker(); + const type = typeChecker.getTypeAtLocation(node.expression); + const entries: CompletionEntry[] = []; + if (type) { + getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, node, /*performCharacterChecks*/false); + if (entries.length) { + return { isMemberCompletion: true, isNewIdentifierLocation: true, entries: entries }; + } + } + return undefined; + } + + function getStringLiteralCompletionEntriesFromContextualType(node: StringLiteral) { + const typeChecker = program.getTypeChecker(); + const type = typeChecker.getContextualType(node); + if (type) { + const entries: CompletionEntry[] = []; + addStringLiteralCompletionsFromType(type, entries); + if (entries.length) { + return { isMemberCompletion: false, isNewIdentifierLocation: false, entries: entries }; + } + } + return undefined; + } + + function addStringLiteralCompletionsFromType(type: Type, result: CompletionEntry[]): void { + if (!type) { + return; + } + if (type.flags & TypeFlags.Union) { + forEach((type).types, t => addStringLiteralCompletionsFromType(t, result)); + } + else { + if (type.flags & TypeFlags.StringLiteral) { + result.push({ + name: (type).text, + kindModifiers: ScriptElementKindModifier.none, + kind: ScriptElementKind.variableElement, + sortText: "0" + }); + } + } + } } function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails { @@ -4215,7 +4303,7 @@ namespace ts { // try get the call/construct signature from the type if it matches let callExpression: CallExpression; if (location.kind === SyntaxKind.CallExpression || location.kind === SyntaxKind.NewExpression) { - callExpression = location; + callExpression = location; } else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) { callExpression = location.parent; diff --git a/tests/cases/fourslash/completionForStringLiteral.ts b/tests/cases/fourslash/completionForStringLiteral.ts new file mode 100644 index 0000000000000..cdbe1589f730d --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteral.ts @@ -0,0 +1,15 @@ +/// + +////type Options = "Option 1" | "Option 2" | "Option 3"; +////var x: Options = "/*1*/Option 3"; +//// +////function f(a: Options) { }; +////f("/*2*/ + +goTo.marker('1'); +verify.completionListContains("Option 1"); +verify.memberListCount(3); + +goTo.marker('2'); +verify.completionListContains("Option 2"); +verify.memberListCount(3); diff --git a/tests/cases/fourslash/completionForStringLiteral2.ts b/tests/cases/fourslash/completionForStringLiteral2.ts new file mode 100644 index 0000000000000..6f0768d6c6bd9 --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteral2.ts @@ -0,0 +1,20 @@ +/// + +////var o = { +//// foo() { }, +//// bar: 0, +//// "some other name": 1 +////}; +//// +////o["/*1*/bar"]; +////o["/*2*/ + +goTo.marker('1'); +verify.completionListContains("foo"); +verify.completionListAllowsNewIdentifier(); +verify.memberListCount(3); + +goTo.marker('2'); +verify.completionListContains("some other name"); +verify.completionListAllowsNewIdentifier(); +verify.memberListCount(3); diff --git a/tests/cases/fourslash/completionForStringLiteral3.ts b/tests/cases/fourslash/completionForStringLiteral3.ts new file mode 100644 index 0000000000000..8c1a7cab2edff --- /dev/null +++ b/tests/cases/fourslash/completionForStringLiteral3.ts @@ -0,0 +1,20 @@ +/// + +////declare function f(a: "A", b: number): void; +////declare function f(a: "B", b: number): void; +////declare function f(a: "C", b: number): void; +////declare function f(a: string, b: number): void; +//// +////f("/*1*/C", 2); +//// +////f("/*2*/ + +goTo.marker('1'); +verify.completionListContains("A"); +verify.completionListAllowsNewIdentifier(); +verify.memberListCount(3); + +goTo.marker('2'); +verify.completionListContains("A"); +verify.completionListAllowsNewIdentifier(); +verify.memberListCount(3); From 7b0d664394c3a3742e6c52d9fec65e3f3e445267 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Mon, 2 May 2016 16:44:11 -0700 Subject: [PATCH 4/9] Remove unused check --- src/services/services.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/services.ts b/src/services/services.ts index eeaef60afca7a..967e9949d49fb 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4061,7 +4061,6 @@ namespace ts { return undefined; } - isNameOfExternalModuleImportOrDeclaration const argumentInfo = SignatureHelp.getContainingArgumentInfo(node, position, sourceFile); if (argumentInfo) { return getStringLiteralCompletionEntriesFromCallExpression(argumentInfo); From 291ad3360751ee0669912fe5d55ba2f3461fd7c7 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 3 May 2016 12:30:27 -0700 Subject: [PATCH 5/9] Use const instead of let --- src/services/signatureHelp.ts | 132 +++++++++++++++++----------------- src/services/utilities.ts | 10 +-- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index fa7cc42e0d10f..b6c328dcea530 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -162,7 +162,7 @@ namespace ts.SignatureHelp { // // Did not find matching token // return null; //} - let emptyArray: any[] = []; + const emptyArray: any[] = []; const enum ArgumentListKind { TypeArguments, @@ -179,16 +179,16 @@ namespace ts.SignatureHelp { } export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, cancellationToken: CancellationToken): SignatureHelpItems { - let typeChecker = program.getTypeChecker(); + const typeChecker = program.getTypeChecker(); // Decide whether to show signature help - let startingToken = findTokenOnLeftOfPosition(sourceFile, position); + const startingToken = findTokenOnLeftOfPosition(sourceFile, position); if (!startingToken) { // We are at the beginning of the file return undefined; } - let argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile); + const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile); cancellationToken.throwIfCancellationRequested(); // Semantic filtering of signature help @@ -196,9 +196,9 @@ namespace ts.SignatureHelp { return undefined; } - let call = argumentInfo.invocation; - let candidates = []; - let resolvedSignature = typeChecker.getResolvedSignature(call, candidates); + const call = argumentInfo.invocation; + const candidates = []; + const resolvedSignature = typeChecker.getResolvedSignature(call, candidates); cancellationToken.throwIfCancellationRequested(); if (!candidates.length) { @@ -220,9 +220,9 @@ namespace ts.SignatureHelp { } // 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 + const callExpression = argumentInfo.invocation; + const expression = callExpression.expression; + const name = expression.kind === SyntaxKind.Identifier ? expression : expression.kind === SyntaxKind.PropertyAccessExpression ? (expression).name @@ -232,18 +232,18 @@ namespace ts.SignatureHelp { return undefined; } - let typeChecker = program.getTypeChecker(); - for (let sourceFile of program.getSourceFiles()) { - let nameToDeclarations = sourceFile.getNamedDeclarations(); - let declarations = getProperty(nameToDeclarations, name.text); + const typeChecker = program.getTypeChecker(); + for (const sourceFile of program.getSourceFiles()) { + const nameToDeclarations = sourceFile.getNamedDeclarations(); + const declarations = getProperty(nameToDeclarations, name.text); if (declarations) { - for (let declaration of declarations) { - let symbol = declaration.symbol; + for (const declaration of declarations) { + const symbol = declaration.symbol; if (symbol) { - let type = typeChecker.getTypeOfSymbolAtLocation(symbol, declaration); + const type = typeChecker.getTypeOfSymbolAtLocation(symbol, declaration); if (type) { - let callSignatures = type.getCallSignatures(); + const callSignatures = type.getCallSignatures(); if (callSignatures && callSignatures.length) { return createSignatureHelpItems(callSignatures, callSignatures[0], argumentInfo, typeChecker); } @@ -260,7 +260,7 @@ namespace ts.SignatureHelp { */ function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo { if (node.parent.kind === SyntaxKind.CallExpression || node.parent.kind === SyntaxKind.NewExpression) { - let callExpression = node.parent; + const 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 @@ -279,8 +279,8 @@ namespace ts.SignatureHelp { 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; + const list = getChildListThatStartsWithOpenerToken(callExpression, node, sourceFile); + const isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos; Debug.assert(list !== undefined); return { kind: isTypeArgList ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments, @@ -297,13 +297,13 @@ namespace ts.SignatureHelp { // - 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); + const listItemInfo = findListItemInfo(node); if (listItemInfo) { - let list = listItemInfo.list; - let isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos; + const list = listItemInfo.list; + const isTypeArgList = callExpression.typeArguments && callExpression.typeArguments.pos === list.pos; - let argumentIndex = getArgumentIndex(list, node); - let argumentCount = getArgumentCount(list); + const argumentIndex = getArgumentIndex(list, node); + const argumentCount = getArgumentCount(list); Debug.assert(argumentIndex === 0 || argumentIndex < argumentCount, `argumentCount < argumentIndex, ${argumentCount} < ${argumentIndex}`); @@ -325,18 +325,18 @@ namespace ts.SignatureHelp { } } else if (node.kind === SyntaxKind.TemplateHead && node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) { - let templateExpression = node.parent; - let tagExpression = templateExpression.parent; + const templateExpression = node.parent; + const tagExpression = templateExpression.parent; Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); - let argumentIndex = isInsideTemplateLiteral(node, position) ? 0 : 1; + const 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; + const templateSpan = node.parent; + const templateExpression = templateSpan.parent; + const tagExpression = templateExpression.parent; Debug.assert(templateExpression.kind === SyntaxKind.TemplateExpression); // If we're just after a template tail, don't show signature help. @@ -344,8 +344,8 @@ namespace ts.SignatureHelp { return undefined; } - let spanIndex = templateExpression.templateSpans.indexOf(templateSpan); - let argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position); + const spanIndex = templateExpression.templateSpans.indexOf(templateSpan); + const argumentIndex = getArgumentIndexForTemplatePiece(spanIndex, node, position); return getArgumentListInfoForTemplate(tagExpression, argumentIndex, sourceFile); } @@ -366,8 +366,8 @@ namespace ts.SignatureHelp { // 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) { + const listChildren = argumentsList.getChildren(); + for (const child of listChildren) { if (child === node) { break; } @@ -391,7 +391,7 @@ namespace ts.SignatureHelp { // 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(); + const listChildren = argumentsList.getChildren(); let argumentCount = countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken); if (listChildren.length > 0 && lastOrUndefined(listChildren).kind === SyntaxKind.CommaToken) { @@ -427,7 +427,7 @@ namespace ts.SignatureHelp { 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 + const argumentCount = tagExpression.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral ? 1 : (tagExpression.template).templateSpans.length + 1; @@ -451,14 +451,14 @@ namespace ts.SignatureHelp { // // 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); + const applicableSpanStart = argumentsList.getFullStart(); + const 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(); + const template = taggedTemplate.template; + const 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. @@ -471,7 +471,7 @@ namespace ts.SignatureHelp { // 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); + const lastSpan = lastOrUndefined((template).templateSpans); if (lastSpan.literal.getFullWidth() === 0) { applicableSpanEnd = skipTrivia(sourceFile.text, applicableSpanEnd, /*stopAfterLineBreak*/ false); } @@ -492,7 +492,7 @@ namespace ts.SignatureHelp { 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); + const argumentInfo = getImmediatelyContainingArgumentInfo(n, position, sourceFile); if (argumentInfo) { return argumentInfo; } @@ -504,8 +504,8 @@ namespace ts.SignatureHelp { } function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node, sourceFile: SourceFile): Node { - let children = parent.getChildren(sourceFile); - let indexOfOpenerToken = children.indexOf(openerToken); + const children = parent.getChildren(sourceFile); + const indexOfOpenerToken = children.indexOf(openerToken); Debug.assert(indexOfOpenerToken >= 0 && children.length > indexOfOpenerToken + 1); return children[indexOfOpenerToken + 1]; } @@ -522,7 +522,7 @@ namespace ts.SignatureHelp { let maxParamsSignatureIndex = -1; let maxParams = -1; for (let i = 0; i < candidates.length; i++) { - let candidate = candidates[i]; + const candidate = candidates[i]; if (candidate.hasRestParameter || candidate.parameters.length >= argumentCount) { return i; @@ -538,17 +538,17 @@ namespace ts.SignatureHelp { } 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 => { + const applicableSpan = argumentListInfo.argumentsSpan; + const isTypeParameterList = argumentListInfo.kind === ArgumentListKind.TypeArguments; + + const invocation = argumentListInfo.invocation; + const callTarget = getInvokedExpression(invocation) + const callTargetSymbol = typeChecker.getSymbolAtLocation(callTarget); + const callTargetDisplayParts = callTargetSymbol && symbolToDisplayParts(typeChecker, callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined); + const items: SignatureHelpItem[] = map(candidates, candidateSignature => { let signatureHelpParameters: SignatureHelpParameter[]; - let prefixDisplayParts: SymbolDisplayPart[] = []; - let suffixDisplayParts: SymbolDisplayPart[] = []; + const prefixDisplayParts: SymbolDisplayPart[] = []; + const suffixDisplayParts: SymbolDisplayPart[] = []; if (callTargetDisplayParts) { addRange(prefixDisplayParts, callTargetDisplayParts); @@ -556,25 +556,25 @@ namespace ts.SignatureHelp { if (isTypeParameterList) { prefixDisplayParts.push(punctuationPart(SyntaxKind.LessThanToken)); - let typeParameters = candidateSignature.typeParameters; + const typeParameters = candidateSignature.typeParameters; signatureHelpParameters = typeParameters && typeParameters.length > 0 ? map(typeParameters, createSignatureHelpParameterForTypeParameter) : emptyArray; suffixDisplayParts.push(punctuationPart(SyntaxKind.GreaterThanToken)); - let parameterParts = mapToDisplayParts(writer => + const parameterParts = mapToDisplayParts(writer => typeChecker.getSymbolDisplayBuilder().buildDisplayForParametersAndDelimiters(candidateSignature.thisType, candidateSignature.parameters, writer, invocation)); addRange(suffixDisplayParts, parameterParts); } else { - let typeParameterParts = mapToDisplayParts(writer => + const typeParameterParts = mapToDisplayParts(writer => typeChecker.getSymbolDisplayBuilder().buildDisplayForTypeParametersAndDelimiters(candidateSignature.typeParameters, writer, invocation)); addRange(prefixDisplayParts, typeParameterParts); prefixDisplayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); - let parameters = candidateSignature.parameters; + const parameters = candidateSignature.parameters; signatureHelpParameters = parameters.length > 0 ? map(parameters, createSignatureHelpParameterForParameter) : emptyArray; suffixDisplayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); } - let returnTypeParts = mapToDisplayParts(writer => + const returnTypeParts = mapToDisplayParts(writer => typeChecker.getSymbolDisplayBuilder().buildReturnTypeDisplay(candidateSignature, writer, invocation)); addRange(suffixDisplayParts, returnTypeParts); @@ -588,10 +588,10 @@ namespace ts.SignatureHelp { }; }); - let argumentIndex = argumentListInfo.argumentIndex; + const argumentIndex = argumentListInfo.argumentIndex; // argumentCount is the *apparent* number of arguments. - let argumentCount = argumentListInfo.argumentCount; + const argumentCount = argumentListInfo.argumentCount; let selectedItemIndex = candidates.indexOf(bestSignature); if (selectedItemIndex < 0) { @@ -609,7 +609,7 @@ namespace ts.SignatureHelp { }; function createSignatureHelpParameterForParameter(parameter: Symbol): SignatureHelpParameter { - let displayParts = mapToDisplayParts(writer => + const displayParts = mapToDisplayParts(writer => typeChecker.getSymbolDisplayBuilder().buildParameterDisplay(parameter, writer, invocation)); return { @@ -621,7 +621,7 @@ namespace ts.SignatureHelp { } function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter): SignatureHelpParameter { - let displayParts = mapToDisplayParts(writer => + const displayParts = mapToDisplayParts(writer => typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(typeParameter, writer, invocation)); return { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index a625a2d647693..4f9632afde5e0 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -508,8 +508,8 @@ namespace ts { return forEach(commentRanges, jsDocPrefix); function jsDocPrefix(c: CommentRange): boolean { - var text = sourceFile.text; - return text.length >= c.pos + 3 && text[c.pos] === '/' && text[c.pos + 1] === '*' && text[c.pos + 2] === '*'; + const text = sourceFile.text; + return text.length >= c.pos + 3 && text[c.pos] === "/" && text[c.pos + 1] === "*" && text[c.pos + 2] === "*"; } } @@ -564,7 +564,7 @@ namespace ts { if (flags & NodeFlags.Export) result.push(ScriptElementKindModifier.exportedModifier); if (isInAmbientContext(node)) result.push(ScriptElementKindModifier.ambientModifier); - return result.length > 0 ? result.join(',') : ScriptElementKindModifier.none; + return result.length > 0 ? result.join(",") : ScriptElementKindModifier.none; } export function getTypeArgumentOrTypeParameterList(node: Node): NodeArray { @@ -647,7 +647,7 @@ namespace ts { // [a,b,c] from: // [a, b, c] = someExpression; if (node.parent.kind === SyntaxKind.BinaryExpression && - (node.parent).left === node && + (node.parent).left === node && (node.parent).operatorToken.kind === SyntaxKind.EqualsToken) { return true; } @@ -786,7 +786,7 @@ namespace ts { } export function textOrKeywordPart(text: string) { - var kind = stringToToken(text); + const kind = stringToToken(text); return kind === undefined ? textPart(text) : keywordPart(kind); From e5a32b71aa537e2c5b241b5b0e6f60895ea1cea6 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 3 May 2016 12:30:53 -0700 Subject: [PATCH 6/9] Fix error --- tests/cases/fourslash/fourslash.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 48d69f0a85eb8..c0df85f0f7bec 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -52,13 +52,13 @@ declare module ts { None = 0, Block = 1, Smart = 2, - } + } interface OutputFile { name: string; writeByteOrderMark: boolean; text: string; - } + } } declare namespace FourSlashInterface { @@ -139,7 +139,7 @@ declare namespace FourSlashInterface { isValidBraceCompletionAtPostion(openingBrace?: string): void; } class verify extends verifyNegatable { - assertHasRanges(ranges: FourSlash.Range[]): void; + assertHasRanges(ranges: Range[]): void; caretAtMarker(markerName?: string): void; indentationIs(numberOfSpaces: number): void; indentationAtPositionIs(fileName: string, position: number, numberOfSpaces: number, indentStyle?: ts.IndentStyle): void; From 02f30ff04c5559da41de9d3a28f3c8fb8a3c00b6 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 3 May 2016 12:31:12 -0700 Subject: [PATCH 7/9] Formatting changes --- src/services/breakpoints.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/services/breakpoints.ts b/src/services/breakpoints.ts index c79a34b1bfa90..bc3530b782207 100644 --- a/src/services/breakpoints.ts +++ b/src/services/breakpoints.ts @@ -259,13 +259,13 @@ namespace ts.BreakpointResolver { if (isArrayLiteralOrObjectLiteralDestructuringPattern(node)) { return spanInArrayLiteralOrObjectLiteralDestructuringPattern(node); } - + // Set breakpoint on identifier element of destructuring pattern // a or ...c or d: x from // [a, b, ...c] or { a, b } or { d: x } from destructuring pattern if ((node.kind === SyntaxKind.Identifier || - node.kind == SyntaxKind.SpreadElementExpression || - node.kind === SyntaxKind.PropertyAssignment || + node.kind == SyntaxKind.SpreadElementExpression || + node.kind === SyntaxKind.PropertyAssignment || node.kind === SyntaxKind.ShorthandPropertyAssignment) && isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent)) { return textSpan(node); @@ -325,14 +325,14 @@ namespace ts.BreakpointResolver { break; } } - + // If this is name of property assignment, set breakpoint in the initializer if (node.parent.kind === SyntaxKind.PropertyAssignment && - (node.parent).name === node && + (node.parent).name === node && !isArrayLiteralOrObjectLiteralDestructuringPattern(node.parent.parent)) { return spanInNode((node.parent).initializer); } - + // Breakpoint in type assertion goes to its operand if (node.parent.kind === SyntaxKind.TypeAssertionExpression && (node.parent).type === node) { return spanInNextNode((node.parent).type); @@ -386,7 +386,7 @@ namespace ts.BreakpointResolver { if (variableDeclaration.parent.parent.kind === SyntaxKind.ForInStatement) { return spanInNode(variableDeclaration.parent.parent); } - + // If this is a destructuring pattern set breakpoint in binding pattern if (isBindingPattern(variableDeclaration.name)) { return spanInBindingPattern(variableDeclaration.name); @@ -686,7 +686,7 @@ namespace ts.BreakpointResolver { function spanInColonToken(node: Node): TextSpan { // Is this : specifying return annotation of the function declaration if (isFunctionLike(node.parent) || - node.parent.kind === SyntaxKind.PropertyAssignment || + node.parent.kind === SyntaxKind.PropertyAssignment || node.parent.kind === SyntaxKind.Parameter) { return spanInPreviousNode(node); } @@ -722,5 +722,5 @@ namespace ts.BreakpointResolver { return spanInNode(node.parent); } } - } + } } \ No newline at end of file From b9ab4d3e08115516bd099c54aa04c81ca4f59a57 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 3 May 2016 12:34:47 -0700 Subject: [PATCH 8/9] Use shorthand properties --- src/services/services.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 967e9949d49fb..d2a9a3988e6c1 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4088,7 +4088,7 @@ namespace ts { } if (entries.length) { - return { isMemberCompletion: false, isNewIdentifierLocation: true, entries: entries }; + return { isMemberCompletion: false, isNewIdentifierLocation: true, entries }; } return undefined; @@ -4101,7 +4101,7 @@ namespace ts { if (type) { getCompletionEntriesFromSymbols(type.getApparentProperties(), entries, node, /*performCharacterChecks*/false); if (entries.length) { - return { isMemberCompletion: true, isNewIdentifierLocation: true, entries: entries }; + return { isMemberCompletion: true, isNewIdentifierLocation: true, entries }; } } return undefined; @@ -4114,7 +4114,7 @@ namespace ts { const entries: CompletionEntry[] = []; addStringLiteralCompletionsFromType(type, entries); if (entries.length) { - return { isMemberCompletion: false, isNewIdentifierLocation: false, entries: entries }; + return { isMemberCompletion: false, isNewIdentifierLocation: false, entries }; } } return undefined; From cc5dd5bf797dddb75304eedff9fd9aa61d39d7b1 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 8 Jun 2016 13:22:15 -0700 Subject: [PATCH 9/9] Add comments --- src/services/services.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/services/services.ts b/src/services/services.ts index 7c743aa86de1c..fa4333b749286 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4164,12 +4164,15 @@ namespace ts { const argumentInfo = SignatureHelp.getContainingArgumentInfo(node, position, sourceFile); if (argumentInfo) { + // Get string literal completions from specialized signatures of the target return getStringLiteralCompletionEntriesFromCallExpression(argumentInfo); } else if (isElementAccessExpression(node.parent) && node.parent.argumentExpression === node) { + // Get all names of properties on the expression return getStringLiteralCompletionEntriesFromElementAccess(node.parent); } else { + // Otherwise, get the completions from the contextual type if one exists return getStringLiteralCompletionEntriesFromContextualType(node); } }