Fix(52604): Provide Object member completions without comma; insert a comma (#52899)

Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>
This commit is contained in:
navya9singh
2023-06-26 18:06:27 -07:00
committed by GitHub
parent c58cc39a15
commit 13460aee86
13 changed files with 704 additions and 5 deletions

View File

@@ -7684,6 +7684,10 @@
"category": "Message",
"code": 95186
},
"Add missing comma for object member completion '{0}'.": {
"category": "Message",
"code": 95187
},
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",

View File

@@ -164,6 +164,7 @@ import {
isFunctionLikeDeclaration,
isFunctionLikeKind,
isFunctionTypeNode,
isGetAccessorDeclaration,
isIdentifier,
isIdentifierText,
isImportableFile,
@@ -217,9 +218,11 @@ import {
isPrivateIdentifier,
isPrivateIdentifierClassElementDeclaration,
isPropertyAccessExpression,
isPropertyAssignment,
isPropertyDeclaration,
isPropertyNameLiteral,
isRegularExpressionLiteral,
isSetAccessorDeclaration,
isShorthandPropertyAssignment,
isSingleOrDoubleQuote,
isSourceFile,
@@ -443,6 +446,8 @@ export enum CompletionSource {
ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/",
/** Case completions for switch statements */
SwitchCases = "SwitchCases/",
/** Completions for an Object literal expression */
ObjectLiteralMemberWithComma = "ObjectLiteralMemberWithComma/",
}
/** @internal */
@@ -1683,6 +1688,30 @@ function createCompletionEntry(
hasAction = true;
}
// Provide object member completions when missing commas, and insert missing commas.
// For example:
//
// interface I {
// a: string;
// b: number
// }
//
// const cc: I = { a: "red" | }
//
// Completion should add a comma after "red" and provide completions for b
if (completionKind === CompletionKind.ObjectPropertyDeclaration && contextToken && findPrecedingToken(contextToken.pos, sourceFile, contextToken)?.kind !== SyntaxKind.CommaToken) {
if (isMethodDeclaration(contextToken.parent.parent) ||
isGetAccessorDeclaration(contextToken.parent.parent) ||
isSetAccessorDeclaration(contextToken.parent.parent) ||
isSpreadAssignment(contextToken.parent) ||
findAncestor(contextToken.parent, isPropertyAssignment)?.getLastToken(sourceFile) === contextToken ||
isShorthandPropertyAssignment(contextToken.parent) && getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line) {
source = CompletionSource.ObjectLiteralMemberWithComma;
hasAction = true;
}
}
if (preferences.includeCompletionsWithClassMemberSnippets &&
preferences.includeCompletionsWithInsertText &&
completionKind === CompletionKind.MemberLike &&
@@ -2664,7 +2693,8 @@ function getSymbolCompletionFromEntryId(
return info && info.name === entryId.name && (
entryId.source === CompletionSource.ClassMemberSnippet && symbol.flags & SymbolFlags.ClassMember
|| entryId.source === CompletionSource.ObjectLiteralMethodSnippet && symbol.flags & (SymbolFlags.Property | SymbolFlags.Method)
|| getSourceFromOrigin(origin) === entryId.source)
|| getSourceFromOrigin(origin) === entryId.source
|| entryId.source === CompletionSource.ObjectLiteralMemberWithComma)
? { type: "symbol" as const, symbol, location, origin, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation }
: undefined;
}) || { type: "none" };
@@ -2860,6 +2890,23 @@ function getCompletionEntryCodeActionsAndSourceDisplay(
return { codeActions: [codeAction], sourceDisplay: undefined };
}
if (source === CompletionSource.ObjectLiteralMemberWithComma && contextToken) {
const changes = textChanges.ChangeTracker.with(
{ host, formatContext, preferences },
tracker => tracker.insertText(sourceFile, contextToken.end, ",")
);
if (changes) {
return {
sourceDisplay: undefined,
codeActions: [{
changes,
description: diagnosticToString([Diagnostics.Add_missing_comma_for_object_member_completion_0, name]),
}],
};
}
}
if (!origin || !(originIsExport(origin) || originIsResolvedExport(origin))) {
return { codeActions: undefined, sourceDisplay: undefined };
}
@@ -4156,7 +4203,7 @@ function getCompletionData(
*/
function tryGetObjectLikeCompletionSymbols(): GlobalsSearch | undefined {
const symbolsStartIndex = symbols.length;
const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken);
const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken, position, sourceFile);
if (!objectLikeContainer) return GlobalsSearch.Continue;
// We're looking up possible property names from contextual/inferred/declared type.
@@ -4884,7 +4931,7 @@ function getCompletionData(
* Returns the immediate owning object literal or binding pattern of a context token,
* on the condition that one exists and that the context implies completion should be given.
*/
function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): ObjectLiteralExpression | ObjectBindingPattern | undefined {
function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined, position: number, sourceFile: SourceFile): ObjectLiteralExpression | ObjectBindingPattern | undefined {
if (contextToken) {
const { parent } = contextToken;
switch (contextToken.kind) {
@@ -4899,8 +4946,33 @@ function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): Ob
case SyntaxKind.AsyncKeyword:
return tryCast(parent.parent, isObjectLiteralExpression);
case SyntaxKind.Identifier:
return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent)
? contextToken.parent.parent : undefined;
if ((contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent)) {
return contextToken.parent.parent;
}
else {
if (isObjectLiteralExpression(contextToken.parent.parent) &&
(isSpreadAssignment(contextToken.parent) || isShorthandPropertyAssignment(contextToken.parent) &&
(getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line))) {
return contextToken.parent.parent;
}
const ancestorNode = findAncestor(parent, isPropertyAssignment);
if (ancestorNode?.getLastToken(sourceFile) === contextToken && isObjectLiteralExpression(ancestorNode.parent)) {
return ancestorNode.parent;
}
}
break;
default:
if (parent.parent?.parent && (isMethodDeclaration(parent.parent) || isGetAccessorDeclaration(parent.parent) || isSetAccessorDeclaration(parent.parent)) && isObjectLiteralExpression(parent.parent.parent)) {
return parent.parent.parent;
}
if (isSpreadAssignment(parent) && isObjectLiteralExpression(parent.parent)) {
return parent.parent;
}
const ancestorNode = findAncestor(parent, isPropertyAssignment);
if (contextToken.kind !== SyntaxKind.ColonToken && ancestorNode?.getLastToken(sourceFile) === contextToken &&
isObjectLiteralExpression(ancestorNode.parent)) {
return ancestorNode.parent;
}
}
}