Skip to content

Commit 9c561d2

Browse files
author
Andy Hanson
committed
Supports more types for recommended completions
1 parent 47d84f8 commit 9c561d2

File tree

5 files changed

+83
-32
lines changed

5 files changed

+83
-32
lines changed

src/compiler/checker.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,14 @@ namespace ts {
191191
node = getParseTreeNode(node, isExpression);
192192
return node ? getContextualType(node) : undefined;
193193
},
194+
getContextualTypeForArgumentAtIndex: (node, argIndex) => {
195+
node = getParseTreeNode(node, isCallLikeExpression);
196+
return node && getContextualTypeForArgumentAtIndex(node, argIndex);
197+
},
198+
getContextualTypeForJsxAttribute: (node) => {
199+
node = getParseTreeNode(node, isJsxAttributeLike);
200+
return node && getContextualTypeForJsxAttribute(node);
201+
},
194202
isContextSensitive,
195203
getFullyQualifiedName,
196204
getResolvedSignature: (node, candidatesOutArray, theArgumentCount) => {
@@ -14185,14 +14193,15 @@ namespace ts {
1418514193
// In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter.
1418614194
function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type {
1418714195
const args = getEffectiveCallArguments(callTarget);
14188-
const argIndex = args.indexOf(arg);
14189-
if (argIndex >= 0) {
14190-
// If we're already in the process of resolving the given signature, don't resolve again as
14191-
// that could cause infinite recursion. Instead, return anySignature.
14192-
const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget);
14193-
return getTypeAtPosition(signature, argIndex);
14194-
}
14195-
return undefined;
14196+
const argIndex = args.indexOf(arg); // -1 for e.g. the expression of a CallExpression, or the tag of a TaggedTemplateExpression
14197+
return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex);
14198+
}
14199+
14200+
function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number): Type {
14201+
// If we're already in the process of resolving the given signature, don't resolve again as
14202+
// that could cause infinite recursion. Instead, return anySignature.
14203+
const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget);
14204+
return getTypeAtPosition(signature, argIndex);
1419614205
}
1419714206

1419814207
function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) {
@@ -14326,7 +14335,7 @@ namespace ts {
1432614335
: undefined;
1432714336
}
1432814337

14329-
function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute) {
14338+
function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined {
1433014339
// When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type
1433114340
// which is a type of the parameter of the signature we are trying out.
1433214341
// If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName

src/compiler/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2835,6 +2835,10 @@ namespace ts {
28352835
getAugmentedPropertiesOfType(type: Type): Symbol[];
28362836
getRootSymbols(symbol: Symbol): Symbol[];
28372837
getContextualType(node: Expression): Type | undefined;
2838+
/* @internal */
2839+
getContextualTypeForArgumentAtIndex(call: CallLikeExpression, argIndex: number): Type;
2840+
/* @internal */
2841+
getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined;
28382842
/* @internal */ isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike): boolean;
28392843

28402844
/**

src/services/completions.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -657,32 +657,45 @@ namespace ts.Completions {
657657
None,
658658
}
659659

660-
function getRecommendedCompletion(currentToken: Node, checker: TypeChecker): Symbol | undefined {
661-
const ty = getContextualType(currentToken, checker);
660+
function getRecommendedCompletion(currentToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): Symbol | undefined {
661+
const ty = getContextualType(currentToken, position, sourceFile, checker);
662662
const symbol = ty && ty.symbol;
663663
// Don't include make a recommended completion for an abstract class
664664
return symbol && (symbol.flags & SymbolFlags.Enum || symbol.flags & SymbolFlags.Class && !isAbstractConstructorSymbol(symbol))
665665
? getFirstSymbolInChain(symbol, currentToken, checker)
666666
: undefined;
667667
}
668668

669-
function getContextualType(currentToken: Node, checker: ts.TypeChecker): Type | undefined {
669+
function getContextualType(currentToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): Type | undefined {
670670
const { parent } = currentToken;
671671
switch (currentToken.kind) {
672-
case ts.SyntaxKind.Identifier:
673-
return getContextualTypeFromParent(currentToken as ts.Identifier, checker);
674-
case ts.SyntaxKind.EqualsToken:
675-
return ts.isVariableDeclaration(parent) ? checker.getContextualType(parent.initializer) :
676-
ts.isBinaryExpression(parent) ? checker.getTypeAtLocation(parent.left) : undefined;
677-
case ts.SyntaxKind.NewKeyword:
678-
return checker.getContextualType(parent as ts.Expression);
679-
case ts.SyntaxKind.CaseKeyword:
680-
return getSwitchedType(cast(currentToken.parent, isCaseClause), checker);
672+
case SyntaxKind.Identifier:
673+
return getContextualTypeFromParent(currentToken as Identifier, checker);
674+
case SyntaxKind.EqualsToken:
675+
switch (parent.kind) {
676+
case ts.SyntaxKind.VariableDeclaration:
677+
return checker.getContextualType((parent as VariableDeclaration).initializer);
678+
case ts.SyntaxKind.BinaryExpression:
679+
return checker.getTypeAtLocation((parent as BinaryExpression).left);
680+
case ts.SyntaxKind.JsxAttribute:
681+
return checker.getContextualTypeForJsxAttribute(parent as JsxAttribute);
682+
default:
683+
return undefined;
684+
}
685+
case SyntaxKind.NewKeyword:
686+
return checker.getContextualType(parent as Expression);
687+
case SyntaxKind.CaseKeyword:
688+
return getSwitchedType(cast(parent, isCaseClause), checker);
689+
case SyntaxKind.OpenBraceToken:
690+
return isJsxExpression(parent) && parent.parent.kind !== SyntaxKind.JsxElement ? checker.getContextualTypeForJsxAttribute(parent.parent) : undefined;
681691
default:
682-
return isEqualityOperatorKind(currentToken.kind) && ts.isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind)
692+
const argInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(currentToken, position, sourceFile);
693+
return argInfo
694+
? checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex)
695+
: isEqualityOperatorKind(currentToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind)
683696
// completion at `x ===/**/` should be for the right side
684697
? checker.getTypeAtLocation(parent.left)
685-
: checker.getContextualType(currentToken as ts.Expression);
698+
: checker.getContextualType(currentToken as Expression);
686699
}
687700
}
688701

@@ -956,7 +969,7 @@ namespace ts.Completions {
956969

957970
log("getCompletionData: Semantic work: " + (timestamp() - semanticStart));
958971

959-
const recommendedCompletion = previousToken && getRecommendedCompletion(previousToken, typeChecker);
972+
const recommendedCompletion = previousToken && getRecommendedCompletion(previousToken, position, sourceFile, typeChecker);
960973
return { kind: CompletionDataKind.Data, symbols, completionKind, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer };
961974

962975
type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;

src/services/signatureHelp.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ namespace ts.SignatureHelp {
9595
* Returns relevant information for the argument list and the current argument if we are
9696
* in the argument of an invocation; returns undefined otherwise.
9797
*/
98-
export function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo {
98+
export function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined {
9999
if (isCallOrNewExpression(node.parent)) {
100100
const invocation = node.parent;
101101
let list: Node;
@@ -136,7 +136,7 @@ namespace ts.SignatureHelp {
136136

137137
const kind = invocation.typeArguments && invocation.typeArguments.pos === list.pos ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments;
138138
const argumentCount = getArgumentCount(list);
139-
if (argumentIndex !== 0) {
139+
if (argumentIndex !== 0 && node.kind !== SyntaxKind.CommaToken) {
140140
Debug.assertLessThan(argumentIndex, argumentCount);
141141
}
142142
const argumentsSpan = getApplicableSpanForArguments(list, sourceFile);
@@ -207,8 +207,7 @@ namespace ts.SignatureHelp {
207207
// that trailing comma in the list, and we'll have generated the appropriate
208208
// arg index.
209209
let argumentIndex = 0;
210-
const listChildren = argumentsList.getChildren();
211-
for (const child of listChildren) {
210+
for (const child of argumentsList.getChildren()) {
212211
if (child === node) {
213212
break;
214213
}
@@ -217,7 +216,8 @@ namespace ts.SignatureHelp {
217216
}
218217
}
219218

220-
return argumentIndex;
219+
// At `,`, treat this as the next argument after the comma.
220+
return node.kind === SyntaxKind.CommaToken ? argumentIndex + 1 : argumentIndex;
221221
}
222222

223223
function getArgumentCount(argumentsList: Node) {
@@ -270,9 +270,7 @@ namespace ts.SignatureHelp {
270270

271271
function getArgumentListInfoForTemplate(tagExpression: TaggedTemplateExpression, argumentIndex: number, sourceFile: SourceFile): ArgumentListInfo {
272272
// argumentCount is either 1 or (numSpans + 1) to account for the template strings array argument.
273-
const argumentCount = tagExpression.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral
274-
? 1
275-
: (<TemplateExpression>tagExpression.template).templateSpans.length + 1;
273+
const argumentCount = isNoSubstitutionTemplateLiteral(tagExpression.template) ? 1 : tagExpression.template.templateSpans.length + 1;
276274

277275
if (argumentIndex !== 0) {
278276
Debug.assertLessThan(argumentIndex, argumentCount);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @jsx: preserve
4+
5+
// @Filename: /a.tsx
6+
////enum E {}
7+
////enum F {}
8+
////function f(e: E, f: F) {}
9+
////f(/*arg0*/, /*arg1*/);
10+
////
11+
////function tag(arr: TemplateStringsArray, x: E) {}
12+
////tag`${/*tag*/}`;
13+
////
14+
////declare function MainButton(props: { e: E }): any;
15+
////<MainButton e={/*jsx*/} />
16+
////<MainButton e=/*jsx2*/ />
17+
18+
recommended("arg0");
19+
recommended("arg1", "F");
20+
recommended("tag");
21+
recommended("jsx");
22+
recommended("jsx2");
23+
24+
function recommended(markerName: string, enumName = "E") {
25+
goTo.marker(markerName);
26+
verify.completionListContains(enumName, `enum ${enumName}`, "", "enum", undefined, undefined , { isRecommended: true });
27+
}

0 commit comments

Comments
 (0)