mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Supports more locations for completions contextual types (#21946)
This commit is contained in:
+18
-9
@@ -191,6 +191,14 @@ namespace ts {
|
||||
node = getParseTreeNode(node, isExpression);
|
||||
return node ? getContextualType(node) : undefined;
|
||||
},
|
||||
getContextualTypeForArgumentAtIndex: (node, argIndex) => {
|
||||
node = getParseTreeNode(node, isCallLikeExpression);
|
||||
return node && getContextualTypeForArgumentAtIndex(node, argIndex);
|
||||
},
|
||||
getContextualTypeForJsxAttribute: (node) => {
|
||||
node = getParseTreeNode(node, isJsxAttributeLike);
|
||||
return node && getContextualTypeForJsxAttribute(node);
|
||||
},
|
||||
isContextSensitive,
|
||||
getFullyQualifiedName,
|
||||
getResolvedSignature: (node, candidatesOutArray, theArgumentCount) => {
|
||||
@@ -14183,14 +14191,15 @@ namespace ts {
|
||||
// In a typed function call, an argument or substitution expression is contextually typed by the type of the corresponding parameter.
|
||||
function getContextualTypeForArgument(callTarget: CallLikeExpression, arg: Expression): Type {
|
||||
const args = getEffectiveCallArguments(callTarget);
|
||||
const argIndex = args.indexOf(arg);
|
||||
if (argIndex >= 0) {
|
||||
// If we're already in the process of resolving the given signature, don't resolve again as
|
||||
// that could cause infinite recursion. Instead, return anySignature.
|
||||
const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget);
|
||||
return getTypeAtPosition(signature, argIndex);
|
||||
}
|
||||
return undefined;
|
||||
const argIndex = args.indexOf(arg); // -1 for e.g. the expression of a CallExpression, or the tag of a TaggedTemplateExpression
|
||||
return argIndex === -1 ? undefined : getContextualTypeForArgumentAtIndex(callTarget, argIndex);
|
||||
}
|
||||
|
||||
function getContextualTypeForArgumentAtIndex(callTarget: CallLikeExpression, argIndex: number): Type {
|
||||
// If we're already in the process of resolving the given signature, don't resolve again as
|
||||
// that could cause infinite recursion. Instead, return anySignature.
|
||||
const signature = getNodeLinks(callTarget).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(callTarget);
|
||||
return getTypeAtPosition(signature, argIndex);
|
||||
}
|
||||
|
||||
function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) {
|
||||
@@ -14324,7 +14333,7 @@ namespace ts {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute) {
|
||||
function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined {
|
||||
// When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type
|
||||
// which is a type of the parameter of the signature we are trying out.
|
||||
// If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName
|
||||
|
||||
@@ -2835,6 +2835,8 @@ namespace ts {
|
||||
getAugmentedPropertiesOfType(type: Type): Symbol[];
|
||||
getRootSymbols(symbol: Symbol): Symbol[];
|
||||
getContextualType(node: Expression): Type | undefined;
|
||||
/* @internal */ getContextualTypeForArgumentAtIndex(call: CallLikeExpression, argIndex: number): Type;
|
||||
/* @internal */ getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined;
|
||||
/* @internal */ isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike): boolean;
|
||||
|
||||
/**
|
||||
|
||||
+29
-15
@@ -657,8 +657,8 @@ namespace ts.Completions {
|
||||
None,
|
||||
}
|
||||
|
||||
function getRecommendedCompletion(currentToken: Node, checker: TypeChecker): Symbol | undefined {
|
||||
const ty = getContextualType(currentToken, checker);
|
||||
function getRecommendedCompletion(currentToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): Symbol | undefined {
|
||||
const ty = getContextualType(currentToken, position, sourceFile, checker);
|
||||
const symbol = ty && ty.symbol;
|
||||
// Don't include make a recommended completion for an abstract class
|
||||
return symbol && (symbol.flags & SymbolFlags.Enum || symbol.flags & SymbolFlags.Class && !isAbstractConstructorSymbol(symbol))
|
||||
@@ -666,23 +666,37 @@ namespace ts.Completions {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function getContextualType(currentToken: Node, checker: ts.TypeChecker): Type | undefined {
|
||||
function getContextualType(currentToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): Type | undefined {
|
||||
const { parent } = currentToken;
|
||||
switch (currentToken.kind) {
|
||||
case ts.SyntaxKind.Identifier:
|
||||
return getContextualTypeFromParent(currentToken as ts.Identifier, checker);
|
||||
case ts.SyntaxKind.EqualsToken:
|
||||
return ts.isVariableDeclaration(parent) ? checker.getContextualType(parent.initializer) :
|
||||
ts.isBinaryExpression(parent) ? checker.getTypeAtLocation(parent.left) : undefined;
|
||||
case ts.SyntaxKind.NewKeyword:
|
||||
return checker.getContextualType(parent as ts.Expression);
|
||||
case ts.SyntaxKind.CaseKeyword:
|
||||
return getSwitchedType(cast(currentToken.parent, isCaseClause), checker);
|
||||
case SyntaxKind.Identifier:
|
||||
return getContextualTypeFromParent(currentToken as Identifier, checker);
|
||||
case SyntaxKind.EqualsToken:
|
||||
switch (parent.kind) {
|
||||
case ts.SyntaxKind.VariableDeclaration:
|
||||
return checker.getContextualType((parent as VariableDeclaration).initializer);
|
||||
case ts.SyntaxKind.BinaryExpression:
|
||||
return checker.getTypeAtLocation((parent as BinaryExpression).left);
|
||||
case ts.SyntaxKind.JsxAttribute:
|
||||
return checker.getContextualTypeForJsxAttribute(parent as JsxAttribute);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
case SyntaxKind.NewKeyword:
|
||||
return checker.getContextualType(parent as Expression);
|
||||
case SyntaxKind.CaseKeyword:
|
||||
return getSwitchedType(cast(parent, isCaseClause), checker);
|
||||
case SyntaxKind.OpenBraceToken:
|
||||
return isJsxExpression(parent) && parent.parent.kind !== SyntaxKind.JsxElement ? checker.getContextualTypeForJsxAttribute(parent.parent) : undefined;
|
||||
default:
|
||||
return isEqualityOperatorKind(currentToken.kind) && ts.isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind)
|
||||
const argInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(currentToken, position, sourceFile);
|
||||
return argInfo
|
||||
// At `,`, treat this as the next argument after the comma.
|
||||
? checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (currentToken.kind === SyntaxKind.CommaToken ? 1 : 0))
|
||||
: isEqualityOperatorKind(currentToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind)
|
||||
// completion at `x ===/**/` should be for the right side
|
||||
? checker.getTypeAtLocation(parent.left)
|
||||
: checker.getContextualType(currentToken as ts.Expression);
|
||||
: checker.getContextualType(currentToken as Expression);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -956,7 +970,7 @@ namespace ts.Completions {
|
||||
|
||||
log("getCompletionData: Semantic work: " + (timestamp() - semanticStart));
|
||||
|
||||
const recommendedCompletion = previousToken && getRecommendedCompletion(previousToken, typeChecker);
|
||||
const recommendedCompletion = previousToken && getRecommendedCompletion(previousToken, position, sourceFile, typeChecker);
|
||||
return { kind: CompletionDataKind.Data, symbols, completionKind, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer };
|
||||
|
||||
type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag;
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace ts.SignatureHelp {
|
||||
* Returns relevant information for the argument list and the current argument if we are
|
||||
* in the argument of an invocation; returns undefined otherwise.
|
||||
*/
|
||||
export function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo {
|
||||
export function getImmediatelyContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined {
|
||||
if (isCallOrNewExpression(node.parent)) {
|
||||
const invocation = node.parent;
|
||||
let list: Node;
|
||||
@@ -207,8 +207,7 @@ namespace ts.SignatureHelp {
|
||||
// that trailing comma in the list, and we'll have generated the appropriate
|
||||
// arg index.
|
||||
let argumentIndex = 0;
|
||||
const listChildren = argumentsList.getChildren();
|
||||
for (const child of listChildren) {
|
||||
for (const child of argumentsList.getChildren()) {
|
||||
if (child === node) {
|
||||
break;
|
||||
}
|
||||
@@ -270,9 +269,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.
|
||||
const argumentCount = tagExpression.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral
|
||||
? 1
|
||||
: (<TemplateExpression>tagExpression.template).templateSpans.length + 1;
|
||||
const argumentCount = isNoSubstitutionTemplateLiteral(tagExpression.template) ? 1 : tagExpression.template.templateSpans.length + 1;
|
||||
|
||||
if (argumentIndex !== 0) {
|
||||
Debug.assertLessThan(argumentIndex, argumentCount);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @jsx: preserve
|
||||
|
||||
// @Filename: /a.tsx
|
||||
////enum E {}
|
||||
////enum F {}
|
||||
////function f(e: E, f: F) {}
|
||||
////f(/*arg0*/, /*arg1*/);
|
||||
////
|
||||
////function tag(arr: TemplateStringsArray, x: E) {}
|
||||
////tag`${/*tag*/}`;
|
||||
////
|
||||
////declare function MainButton(props: { e: E }): any;
|
||||
////<MainButton e={/*jsx*/} />
|
||||
////<MainButton e=/*jsx2*/ />
|
||||
|
||||
recommended("arg0");
|
||||
recommended("arg1", "F");
|
||||
recommended("tag");
|
||||
recommended("jsx");
|
||||
recommended("jsx2");
|
||||
|
||||
function recommended(markerName: string, enumName = "E") {
|
||||
goTo.marker(markerName);
|
||||
verify.completionListContains(enumName, `enum ${enumName}`, "", "enum", undefined, undefined , { isRecommended: true });
|
||||
}
|
||||
@@ -27,5 +27,5 @@ verify.currentSignatureParameterCountIs(2);
|
||||
verify.currentSignatureHelpIs("f3(n: number, s: string): string");
|
||||
|
||||
verify.currentParameterHelpArgumentNameIs("s");
|
||||
verify.currentParameterSpanIs("s: string");
|
||||
verify.currentParameterSpanIs("s: string");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user