-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Support completions contextual types in more places #20768
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13943,7 +13943,7 @@ namespace ts { | |
// the contextual type of an initializer expression is the type annotation of the containing declaration, if present. | ||
function getContextualTypeForInitializerExpression(node: Expression): Type { | ||
const declaration = <VariableLikeDeclaration>node.parent; | ||
if (hasInitializer(declaration) && node === declaration.initializer || node.kind === SyntaxKind.EqualsToken) { | ||
if (hasInitializer(declaration) && node === declaration.initializer) { | ||
const typeNode = getEffectiveTypeAnnotationNode(declaration); | ||
if (typeNode) { | ||
return getTypeFromTypeNode(typeNode); | ||
|
@@ -14075,12 +14075,6 @@ namespace ts { | |
case SyntaxKind.AmpersandAmpersandToken: | ||
case SyntaxKind.CommaToken: | ||
return node === right ? getContextualType(binaryExpression) : undefined; | ||
case SyntaxKind.EqualsEqualsEqualsToken: | ||
case SyntaxKind.EqualsEqualsToken: | ||
case SyntaxKind.ExclamationEqualsEqualsToken: | ||
case SyntaxKind.ExclamationEqualsToken: | ||
// For completions after `x === ` | ||
return node === operatorToken ? getTypeOfExpression(binaryExpression.left) : undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this really not used anywhere else besides services? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this was also added by #20020. |
||
default: | ||
return undefined; | ||
} | ||
|
@@ -14296,12 +14290,8 @@ namespace ts { | |
return getContextualTypeForReturnExpression(node); | ||
case SyntaxKind.YieldExpression: | ||
return getContextualTypeForYieldOperand(<YieldExpression>parent); | ||
case SyntaxKind.CallExpression: | ||
case SyntaxKind.NewExpression: | ||
if (node.kind === SyntaxKind.NewKeyword) { // for completions after `new ` | ||
return getContextualType(parent as NewExpression); | ||
} | ||
// falls through | ||
case SyntaxKind.CallExpression: | ||
return getContextualTypeForArgument(<CallExpression | NewExpression>parent, node); | ||
case SyntaxKind.TypeAssertionExpression: | ||
case SyntaxKind.AsExpression: | ||
|
@@ -14336,12 +14326,6 @@ namespace ts { | |
case SyntaxKind.JsxOpeningElement: | ||
case SyntaxKind.JsxSelfClosingElement: | ||
return getAttributesTypeFromJsxOpeningLikeElement(<JsxOpeningLikeElement>parent); | ||
case SyntaxKind.CaseClause: { | ||
if (node.kind === SyntaxKind.CaseKeyword) { // for completions after `case ` | ||
const switchStatement = (parent as CaseClause).parent.parent; | ||
return getTypeOfExpression(switchStatement.expression); | ||
} | ||
} | ||
} | ||
return undefined; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -238,7 +238,7 @@ namespace ts.Completions { | |
|
||
function getStringLiteralCompletionEntries(sourceFile: SourceFile, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, log: Log): CompletionInfo | undefined { | ||
const node = findPrecedingToken(position, sourceFile); | ||
if (!node || (node.kind !== SyntaxKind.StringLiteral && node.kind !== SyntaxKind.NoSubstitutionTemplateLiteral)) { | ||
if (!node || !isStringLiteral(node) && !isNoSubstitutionTemplateLiteral(node)) { | ||
return undefined; | ||
} | ||
|
||
|
@@ -277,21 +277,9 @@ namespace ts.Completions { | |
// import x = require("/*completion position*/"); | ||
// var y = require("/*completion position*/"); | ||
// export * from "/*completion position*/"; | ||
const entries = PathCompletions.getStringLiteralCompletionsFromModuleNames(<StringLiteral>node, compilerOptions, host, typeChecker); | ||
const entries = PathCompletions.getStringLiteralCompletionsFromModuleNames(node, compilerOptions, host, typeChecker); | ||
return pathCompletionsInfo(entries); | ||
} | ||
else if (isEqualityExpression(node.parent)) { | ||
// Get completions from the type of the other operand | ||
// i.e. switch (a) { | ||
// case '/*completion position*/' | ||
// } | ||
return getStringLiteralCompletionEntriesFromType(typeChecker.getTypeAtLocation(node.parent.left === node ? node.parent.right : node.parent.left), typeChecker); | ||
} | ||
else if (isCaseOrDefaultClause(node.parent)) { | ||
// Get completions from the type of the switch expression | ||
// i.e. x === '/*completion position' | ||
return getStringLiteralCompletionEntriesFromType(typeChecker.getTypeAtLocation((<SwitchStatement>node.parent.parent.parent).expression), typeChecker); | ||
} | ||
else { | ||
const argumentInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(node, position, sourceFile); | ||
if (argumentInfo) { | ||
|
@@ -303,7 +291,7 @@ namespace ts.Completions { | |
|
||
// Get completion for string literal from string literal type | ||
// i.e. var x: "hi" | "hello" = "/*completion position*/" | ||
return getStringLiteralCompletionEntriesFromType(typeChecker.getContextualType(<LiteralExpression>node), typeChecker); | ||
return getStringLiteralCompletionEntriesFromType(getContextualTypeFromParent(node, typeChecker), typeChecker); | ||
} | ||
} | ||
|
||
|
@@ -602,15 +590,60 @@ namespace ts.Completions { | |
} | ||
type Request = { kind: "JsDocTagName" } | { kind: "JsDocTag" } | { kind: "JsDocParameterName", tag: JSDocParameterTag }; | ||
|
||
function getRecommendedCompletion(currentToken: Node, checker: TypeChecker/*, symbolToOriginInfoMap: SymbolOriginInfoMap*/): Symbol | undefined { | ||
const ty = checker.getContextualType(currentToken as Expression); | ||
function getRecommendedCompletion(currentToken: Node, checker: TypeChecker): Symbol | undefined { | ||
const ty = getContextualType(currentToken, 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)) | ||
? getFirstSymbolInChain(symbol, currentToken, checker) | ||
: undefined; | ||
} | ||
|
||
function getContextualType(currentToken: Node, checker: ts.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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you reformat like so? return ts.isVariableDeclaration(parent) ? checker.getContextualType(parent.initializer) :
ts.isBinaryExpression(parent) ? checker.getTypeAtLocation(parent.left) : undefined; |
||
? 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); | ||
default: | ||
return isEqualityOperatorKind(currentToken.kind) && ts.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); | ||
} | ||
} | ||
|
||
function getContextualTypeFromParent(node: ts.Expression, checker: ts.TypeChecker): Type | undefined { | ||
const { parent } = node; | ||
switch (parent.kind) { | ||
case ts.SyntaxKind.NewExpression: | ||
return checker.getContextualType(parent as ts.NewExpression); | ||
case ts.SyntaxKind.BinaryExpression: { | ||
const { left, operatorToken, right } = parent as ts.BinaryExpression; | ||
return isEqualityOperatorKind(operatorToken.kind) | ||
? checker.getTypeAtLocation(node === right ? left : right) | ||
: checker.getContextualType(node); | ||
} | ||
case ts.SyntaxKind.CaseClause: | ||
return (parent as ts.CaseClause).expression === node ? getSwitchedType(parent as ts.CaseClause, checker) : undefined; | ||
default: | ||
return checker.getContextualType(node); | ||
} | ||
} | ||
|
||
function getSwitchedType(caseClause: ts.CaseClause, checker: ts.TypeChecker): ts.Type { | ||
return checker.getTypeAtLocation(caseClause.parent.parent.expression); | ||
} | ||
|
||
function getFirstSymbolInChain(symbol: Symbol, enclosingDeclaration: Node, checker: TypeChecker): Symbol | undefined { | ||
const chain = checker.getAccessibleSymbolChain(symbol, enclosingDeclaration, /*meaning*/ SymbolFlags.All, /*useOnlyExternalAliasing*/ false); | ||
if (chain) return first(chain); | ||
|
@@ -851,7 +884,7 @@ namespace ts.Completions { | |
|
||
log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); | ||
|
||
const recommendedCompletion = getRecommendedCompletion(previousToken, typeChecker); | ||
const recommendedCompletion = previousToken && getRecommendedCompletion(previousToken, typeChecker); | ||
return { symbols, isGlobalCompletion, isMemberCompletion, allowStringLiteral, isNewIdentifierLocation, location, isRightOfDot: (isRightOfDot || isRightOfOpenTag), request, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken }; | ||
|
||
type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; | ||
|
@@ -2081,15 +2114,16 @@ namespace ts.Completions { | |
return isConstructorParameterCompletionKeyword(stringToToken(text)); | ||
} | ||
|
||
function isEqualityExpression(node: Node): node is BinaryExpression { | ||
return isBinaryExpression(node) && isEqualityOperatorKind(node.operatorToken.kind); | ||
} | ||
|
||
function isEqualityOperatorKind(kind: SyntaxKind) { | ||
return kind === SyntaxKind.EqualsEqualsToken || | ||
kind === SyntaxKind.ExclamationEqualsToken || | ||
kind === SyntaxKind.EqualsEqualsEqualsToken || | ||
kind === SyntaxKind.ExclamationEqualsEqualsToken; | ||
function isEqualityOperatorKind(kind: ts.SyntaxKind): kind is EqualityOperator { | ||
switch (kind) { | ||
case ts.SyntaxKind.EqualsEqualsEqualsToken: | ||
case ts.SyntaxKind.EqualsEqualsToken: | ||
case ts.SyntaxKind.ExclamationEqualsEqualsToken: | ||
case ts.SyntaxKind.ExclamationEqualsToken: | ||
return true; | ||
default: | ||
return false; | ||
} | ||
} | ||
|
||
/** Get the corresponding JSDocTag node if the position is in a jsDoc comment */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,10 @@ | ||
/// <reference path="fourslash.ts" /> | ||
|
||
////enum E {} | ||
////declare const e: E; | ||
////e === /**/ | ||
////enum Enu {} | ||
////declare const e: Enu; | ||
////e === /*a*/; | ||
////e === E/*b*/ | ||
|
||
goTo.marker(); | ||
verify.completionListContains("E", "enum E", "", "enum", undefined, undefined, { isRecommended: true }); | ||
goTo.eachMarker(["a", "b"], () => { | ||
verify.completionListContains("Enu", "enum Enu", "", "enum", undefined, undefined, { isRecommended: true }); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,37 @@ | ||
/// <reference path="fourslash.ts" /> | ||
|
||
////enum E {} | ||
////class C {} | ||
////abstract class A {} | ||
////const e: E = /*e*/ | ||
////const c: C = new /*c*/ | ||
////const a: A = new /*a*/ | ||
////enum Enu {} | ||
////class Cls {} | ||
////abstract class Abs {} | ||
////const e: Enu = E/*e0*/; | ||
////const e: Enu = /*e1*/; | ||
////const c: Cls = new C/*c0*/; | ||
////const c: Cls = new /*c1*/; | ||
////const a: Abs = new A/*a0*/; | ||
////const a: Abs = new /*a1*/; | ||
|
||
goTo.marker("e"); | ||
verify.completionListContains("E", "enum E", "", "enum", undefined, undefined, { isRecommended: true }); | ||
// Also works on mutations | ||
////let enu: Enu; | ||
////enu = E/*let0*/; | ||
////enu = E/*let1*/; | ||
|
||
goTo.marker("c"); | ||
verify.completionListContains("C", "class C", "", "class", undefined, undefined, { isRecommended: true }); | ||
goTo.eachMarker(["e0"], () => {//, "e1", "let0", "let1" | ||
verify.completionListContains("Enu", "enum Enu", "", "enum", undefined, undefined, { isRecommended: true }); | ||
}); | ||
|
||
goTo.marker("a"); | ||
// Not recommended, because it's an abstract class | ||
verify.completionListContains("A", "class A", "", "class"); | ||
goTo.eachMarker(["c0", "c1"], (_, idx) => { | ||
verify.completionListContains( | ||
"Cls", | ||
idx === 0 ? "constructor Cls(): Cls" : "class Cls", | ||
"", | ||
"class", | ||
undefined, | ||
undefined, { | ||
isRecommended: true, | ||
}); | ||
}); | ||
|
||
goTo.eachMarker(["a0", "a1"], (_, idx) => { | ||
// Not recommended, because it's an abstract class | ||
verify.completionListContains("Abs", idx == 0 ? "constructor Abs(): Abs" : "class Abs", "", "class"); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does
=
go away? I guess that it's because in batch compilation,=
doesn't have a type, andnode
is in fact only ever=
when called from services.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, this code was added in #20020 and this PR moves the special cases to
completions.ts
.