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
co-authored by Daniel Rosenwasser
parent c58cc39a15
commit 13460aee86
13 changed files with 704 additions and 5 deletions
+4
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",
+77 -5
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;
}
}
}
@@ -0,0 +1,43 @@
/// <reference path="fourslash.ts" />
//// interface ColorPalette {
//// primary?: string;
//// secondary?: string;
//// }
//// let colors: ColorPalette = {
//// primary: "red"
//// /**/
//// };
verify.completions({
marker: "",
includes: [{
name: "secondary",
sortText: completion.SortText.OptionalMember,
hasAction: true,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
}],
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.applyCodeActionFromCompletion("", {
name: "secondary",
description: `Add missing comma for object member completion 'secondary'.`,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
newFileContent:
`interface ColorPalette {
primary?: string;
secondary?: string;
}
let colors: ColorPalette = {
primary: "red",
};`,
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
@@ -0,0 +1,47 @@
/// <reference path="fourslash.ts" />
//// interface TTTT {
//// aaa: string,
//// bbb?: number
//// }
//// const uuu: TTTT = {
//// get aaa() {
//// return ""
//// }
//// /**/
//// }
verify.completions({
marker: "",
includes: [{
name: "bbb",
sortText: completion.SortText.OptionalMember,
hasAction: true,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
}],
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.applyCodeActionFromCompletion("", {
name: "bbb",
description: `Add missing comma for object member completion 'bbb'.`,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
newFileContent:
`interface TTTT {
aaa: string,
bbb?: number
}
const uuu: TTTT = {
get aaa() {
return ""
},
}`,
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
@@ -0,0 +1,48 @@
/// <reference path="fourslash.ts" />
//// interface ColorPalette {
//// primary?: string;
//// secondary?: string;
//// }
//// interface I {
//// color: ColorPalette;
//// }
//// const a: I = {
//// color: {primary: "red" /**/}
//// }
verify.completions({
marker: "",
includes: [{
name: "secondary",
sortText: completion.SortText.OptionalMember,
hasAction: true,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
}],
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
}
});
verify.applyCodeActionFromCompletion("", {
name: "secondary",
description: `Add missing comma for object member completion 'secondary'.`,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
newFileContent:
`interface ColorPalette {
primary?: string;
secondary?: string;
}
interface I {
color: ColorPalette;
}
const a: I = {
color: {primary: "red", }
}`,
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
@@ -0,0 +1,45 @@
/// <reference path="fourslash.ts" />
////interface T {
//// aaa?: string;
//// foo(): void;
//// }
//// const obj: T = {
//// foo() {
//
//// }
//// /**/
//// }
verify.completions({
marker: "",
includes: [{
name: "aaa",
sortText: completion.SortText.OptionalMember,
hasAction: true,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
}],
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.applyCodeActionFromCompletion("", {
name: "aaa",
description: `Add missing comma for object member completion 'aaa'.`,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
newFileContent:
`interface T {
aaa?: string;
foo(): void;
}
const obj: T = {
foo() {
},
}`,
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
@@ -0,0 +1,42 @@
/// <reference path="fourslash.ts" />
////interface T {
//// aaa: number;
//// bbb?: number;
//// }
//// const obj: T = {
//// aaa: 1 * (2 + 3)
//// /**/
//// }
verify.completions({
marker: "",
includes: [{
name: "bbb",
sortText: completion.SortText.OptionalMember,
hasAction: true,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
}],
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.applyCodeActionFromCompletion("", {
name: "bbb",
description: `Add missing comma for object member completion 'bbb'.`,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
newFileContent:
`interface T {
aaa: number;
bbb?: number;
}
const obj: T = {
aaa: 1 * (2 + 3),
}`,
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
@@ -0,0 +1,39 @@
/// <reference path="fourslash.ts" />
//// type E = {}
//// type F = string
//// interface I { e: E, f?: F }
//// const i: I = { e: {}
//// /**/
//// };
verify.completions({
marker: "",
includes: [{
name: "f",
sortText: completion.SortText.OptionalMember,
hasAction: true,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
}],
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.applyCodeActionFromCompletion("", {
name: "f",
description: `Add missing comma for object member completion 'f'.`,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
newFileContent:
`type E = {}
type F = string
interface I { e: E, f?: F }
const i: I = { e: {},
};`,
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
@@ -0,0 +1,45 @@
/// <reference path="fourslash.ts" />
//// type E = {}
//// type F = string
//// const i= { e: {} };
//// interface I { e: E, f?: F }
//// const k: I = {
//// ["e"]: i
//// /**/
//// }
verify.completions({
marker: "",
includes: [{
name: "f",
sortText: completion.SortText.OptionalMember,
hasAction: true,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
}],
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.applyCodeActionFromCompletion("", {
name: "f",
description: `Add missing comma for object member completion 'f'.`,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
newFileContent:
`type E = {}
type F = string
const i= { e: {} };
interface I { e: E, f?: F }
const k: I = {
["e"]: i,
}`,
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
@@ -0,0 +1,129 @@
/// <reference path="fourslash.ts" />
// @Filename: a.ts
//// interface I {
//// aaa: number,
//// bbb: number,
//// }
////
//// interface U {
//// a: number,
//// b: {
//// c: {
//// d: {
//// aaa: number,
//// }
//// }
////
//// },
//// }
//// const num: U = {} as any;
////
//// const l: I = {
//// ...num.b.c.d
//// /*a*/
//// }
// @Filename: b.ts
//// interface pp {
//// aaa: string;
//// bbb: number;
//// }
////
//// const abc: pp = {
//// aaa: "",
//// bbb: 1,
//// }
////
//// const cab: pp = {
//// ...abc
//// /*b*/
//// }
verify.completions({
marker: "a",
includes: [{
name: "bbb",
sortText: completion.SortText.LocationPriority,
hasAction: true,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
}],
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.applyCodeActionFromCompletion("a", {
name: "bbb",
description: `Add missing comma for object member completion 'bbb'.`,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
newFileContent:
`interface I {
aaa: number,
bbb: number,
}
interface U {
a: number,
b: {
c: {
d: {
aaa: number,
}
}
},
}
const num: U = {} as any;
const l: I = {
...num.b.c.d,
}`,
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.completions({
marker: "b",
includes: [{
name: "aaa",
sortText: completion.SortText.MemberDeclaredBySpreadAssignment,
hasAction: true,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
}],
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.applyCodeActionFromCompletion("b", {
name: "aaa",
description: `Add missing comma for object member completion 'aaa'.`,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
newFileContent:
`interface pp {
aaa: string;
bbb: number;
}
const abc: pp = {
aaa: "",
bbb: 1,
}
const cab: pp = {
...abc,
}`,
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
@@ -0,0 +1,45 @@
/// <reference path="fourslash.ts" />
//// interface A {
//// b: string,
//// c?: number,
//// }
//// const b = ""
//// const a: A = {
//// b
//// /**/
//// }
verify.completions({
marker: "",
includes: [{
name: "c",
sortText: completion.SortText.OptionalMember,
hasAction: true,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
}],
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.applyCodeActionFromCompletion("", {
name: "c",
description: `Add missing comma for object member completion 'c'.`,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
newFileContent:
`interface A {
b: string,
c?: number,
}
const b = ""
const a: A = {
b,
}`,
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
@@ -0,0 +1,139 @@
/// <reference path="fourslash.ts" />
// @Filename: a.ts
////interface T {
//// aaa: number;
//// bbb?: number;
//// ccc?: number;
//// }
//// const obj: T = {
//// aaa: 1 * (2 + 3)
//// c/*a*/
//// }
// @Filename: b.ts
////interface T {
//// aaa?: string;
//// foo(): void;
//// }
//// const obj: T = {
//// foo() {
//
//// }
//// aa/*b*/
//// }
// @Filename: c.ts
//// interface ColorPalette {
//// primary?: string;
//// secondary?: string;
//// }
//// interface I {
//// color: ColorPalette;
//// }
//// const a: I = {
//// color: {primary: "red" sec/**/}
//// }
verify.completions({
marker: "a",
includes: [{
name: "bbb",
sortText: completion.SortText.OptionalMember,
hasAction: true,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
}],
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.applyCodeActionFromCompletion("a", {
name: "bbb",
description: `Add missing comma for object member completion 'bbb'.`,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
newFileContent:
`interface T {
aaa: number;
bbb?: number;
ccc?: number;
}
const obj: T = {
aaa: 1 * (2 + 3),
c
}`,
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.completions({
marker: "b",
includes: [{
name: "aaa",
sortText: completion.SortText.OptionalMember,
hasAction: true,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
}],
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.applyCodeActionFromCompletion("b", {
name: "aaa",
description: `Add missing comma for object member completion 'aaa'.`,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
newFileContent:
`interface T {
aaa?: string;
foo(): void;
}
const obj: T = {
foo() {
},
aa
}`,
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
verify.completions({
marker: "",
includes: [{
name: "secondary",
sortText: completion.SortText.OptionalMember,
hasAction: true,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
}],
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
}
});
verify.applyCodeActionFromCompletion("", {
name: "secondary",
description: `Add missing comma for object member completion 'secondary'.`,
source: completion.CompletionSource.ObjectLiteralMemberWithComma,
newFileContent:
`interface ColorPalette {
primary?: string;
secondary?: string;
}
interface I {
color: ColorPalette;
}
const a: I = {
color: {primary: "red", sec}
}`,
preferences: {
allowIncompleteCompletions: true,
includeInsertTextCompletions: true,
},
});
+1
View File
@@ -914,6 +914,7 @@ declare namespace completion {
TypeOnlyAlias = "TypeOnlyAlias/",
ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/",
SwitchCases = "SwitchCases/",
ObjectLiteralMemberWithComma = "ObjectLiteralMemberWithComma/",
}
export const globalThisEntry: Entry;
export const undefinedVarEntry: Entry;