From 7c727847b08f16c523bfcc02fe42e3ffcffbb859 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Thu, 16 Feb 2023 12:55:05 -0800 Subject: [PATCH 01/12] Add condition to fix without adding comma --- src/services/completions.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/services/completions.ts b/src/services/completions.ts index c55783b060a72..deb16ec528b87 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1334,6 +1334,10 @@ function createCompletionEntry( hasAction = true; } + if (contextToken && completionKind === CompletionKind.ObjectPropertyDeclaration && preferences.includeCompletionsWithInsertText) { + hasAction = true; + } + if (preferences.includeCompletionsWithClassMemberSnippets && preferences.includeCompletionsWithInsertText && completionKind === CompletionKind.MemberLike && @@ -4469,6 +4473,10 @@ function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): Ob case SyntaxKind.Identifier: return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent) ? contextToken.parent.parent : undefined; + default: + if (isObjectLiteralExpression(parent.parent)) { + return parent.parent; + } } } From fa517aab59feed2c4c2793e697fcc0ff090c4169 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Mon, 20 Feb 2023 14:56:24 -0800 Subject: [PATCH 02/12] Fix for adding comma --- src/services/completions.ts | 27 +++++++++- src/services/textChanges.ts | 5 ++ .../completionsObjectLiteralExpressions1.ts | 42 ++++++++++++++++ .../completionsObjectLiteralExpressions2.ts | 49 +++++++++++++++++++ 4 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/completionsObjectLiteralExpressions1.ts create mode 100644 tests/cases/fourslash/completionsObjectLiteralExpressions2.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index deb16ec528b87..d483fdabce864 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -55,6 +55,7 @@ import { Expression, ExpressionWithTypeArguments, factory, + FileTextChanges, filter, find, findAncestor, @@ -292,6 +293,7 @@ import { Program, programContainsModules, PropertyAccessExpression, + PropertyAssignment, PropertyDeclaration, PropertyName, PropertySignature, @@ -2447,6 +2449,29 @@ function getCompletionEntryCodeActionsAndSourceDisplay( return { codeActions: [codeAction], sourceDisplay: undefined }; } + if (contextToken && isObjectLiteralExpression(contextToken.parent.parent) && previousToken?.kind !== SyntaxKind.ColonToken) { //previoustoken =: , preferences are0 + let changes: FileTextChanges[]; + if (getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line) { + changes = textChanges.ChangeTracker.with( + { host, formatContext, preferences }, + tracker=>tracker.replacePropertyAssignment(sourceFile, contextToken.parent as PropertyAssignment ,contextToken.parent as PropertyAssignment)); + } + else { + changes = textChanges.ChangeTracker.with( + { host, formatContext, preferences }, + tracker=>tracker.replacePropertyAssignmentOnSameLine(sourceFile, contextToken.parent as PropertyAssignment ,contextToken.parent as PropertyAssignment)); + } + if (changes) { + return { + sourceDisplay: undefined, + codeActions: [{ + changes, + description: diagnosticToString([Diagnostics.Includes_imports_of_types_referenced_by_0, name]), + }], + }; + } + } + if (!origin || !(originIsExport(origin) || originIsResolvedExport(origin))) { return { codeActions: undefined, sourceDisplay: undefined }; } @@ -4474,7 +4499,7 @@ function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): Ob return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent) ? contextToken.parent.parent : undefined; default: - if (isObjectLiteralExpression(parent.parent)) { + if (isObjectLiteralExpression(parent.parent) && contextToken.kind !== SyntaxKind.ColonToken) { return parent.parent; } } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 50621f641601d..daadc5c81d972 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -592,6 +592,11 @@ export class ChangeTracker { this.replaceNode(sourceFile, oldNode, newNode, { suffix }); } + public replacePropertyAssignmentOnSameLine(sourceFile: SourceFile, oldNode: PropertyAssignment, newNode: PropertyAssignment): void { + const suffix = this.nextCommaToken(sourceFile, oldNode) ? "" : ","; + this.replaceNode(sourceFile, oldNode, newNode, { suffix }); + } + public insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}): void { this.replaceRange(sourceFile, createRange(pos), newNode, options); } diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts new file mode 100644 index 0000000000000..07c928577ef6b --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts @@ -0,0 +1,42 @@ +/// +//// interface ColorPalette { +//// primary?: string; +//// secondary?: string; +//// } + +//// let colors: ColorPalette = { +//// primary: "red" +//// /**/ +//// }; + +verify.completions({ + marker: "", + includes: [ + { + name: "secondary", + sortText: completion.SortText.OptionalMember, + hasAction: true, + }], + preferences: { + allowIncompleteCompletions: true, + includeInsertTextCompletions: true, + }, +}); + +verify.applyCodeActionFromCompletion("", { + name: "secondary", + description: `Includes imports of types referenced by 'secondary'`, + newFileContent: + `interface ColorPalette { + primary?: string; + secondary?: string; +} +let colors: ColorPalette = { + primary: "red", + +};`, + preferences: { + allowIncompleteCompletions: true, + includeInsertTextCompletions: true, + }, + }); diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts new file mode 100644 index 0000000000000..e5d222fad4ca5 --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts @@ -0,0 +1,49 @@ +/// +//// 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, + }], + preferences: { + allowIncompleteCompletions: true, + includeInsertTextCompletions: true, + }, +}); + +verify.applyCodeActionFromCompletion("", { + name: "secondary", + description: `Includes imports of types referenced by 'secondary'`, + newFileContent: +` interface ColorPalette { + primary?: string; + secondary?: string; + } + + interface I { + color: ColorPalette; + } + + const a: I = { + color: {primary: "red", /**/} + }`, + preferences: { + allowIncompleteCompletions: true, + includeInsertTextCompletions: true, + }, +}); From 6613dca6159ac5eac2255b5da97a05339d43ce5a Mon Sep 17 00:00:00 2001 From: navya9singh Date: Mon, 20 Feb 2023 23:57:41 -0800 Subject: [PATCH 03/12] fixing test and adding checks for other tests --- src/services/completions.ts | 4 +-- .../completionsObjectLiteralExpressions2.ts | 36 +++++++++---------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index d483fdabce864..438301b53cd8c 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1336,7 +1336,7 @@ function createCompletionEntry( hasAction = true; } - if (contextToken && completionKind === CompletionKind.ObjectPropertyDeclaration && preferences.includeCompletionsWithInsertText) { + if (contextToken && contextToken.kind !== SyntaxKind.OpenBraceToken && completionKind === CompletionKind.ObjectPropertyDeclaration && preferences.includeCompletionsWithInsertText) { hasAction = true; } @@ -4499,7 +4499,7 @@ function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): Ob return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent) ? contextToken.parent.parent : undefined; default: - if (isObjectLiteralExpression(parent.parent) && contextToken.kind !== SyntaxKind.ColonToken) { + if (parent.parent && isObjectLiteralExpression(parent.parent) && contextToken.kind !== SyntaxKind.ColonToken) { return parent.parent; } } diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts index e5d222fad4ca5..1bd46a6338633 100644 --- a/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts @@ -1,15 +1,15 @@ /// //// interface ColorPalette { -//// primary?: string; -//// secondary?: string; +//// primary?: string; +//// secondary?: string; //// } //// interface I { -//// color: ColorPalette; -//// } - +//// color: ColorPalette; +//// } + //// const a: I = { -//// color: {primary: "red", /**/} +//// color: {primary: "red" /**/} //// } verify.completions({ @@ -23,25 +23,23 @@ verify.completions({ preferences: { allowIncompleteCompletions: true, includeInsertTextCompletions: true, - }, + } }); verify.applyCodeActionFromCompletion("", { name: "secondary", description: `Includes imports of types referenced by 'secondary'`, newFileContent: -` interface ColorPalette { - primary?: string; - secondary?: string; - } - - interface I { - color: ColorPalette; - } - - const a: I = { - color: {primary: "red", /**/} - }`, +`interface ColorPalette { + primary?: string; + secondary?: string; +} +interface I { + color: ColorPalette; +} +const a: I = { + color: {primary: "red", } +}`, preferences: { allowIncompleteCompletions: true, includeInsertTextCompletions: true, From 4f88134567207b9836a0026a1d60179b1c8379f3 Mon Sep 17 00:00:00 2001 From: navya9singh <108360753+navya9singh@users.noreply.github.com> Date: Tue, 21 Feb 2023 18:46:52 +0000 Subject: [PATCH 04/12] removing commented code --- src/services/completions.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 438301b53cd8c..9b001d29689d6 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1332,11 +1332,7 @@ function createCompletionEntry( } } - if (origin?.kind === SymbolOriginInfoKind.TypeOnlyAlias) { - hasAction = true; - } - - if (contextToken && contextToken.kind !== SyntaxKind.OpenBraceToken && completionKind === CompletionKind.ObjectPropertyDeclaration && preferences.includeCompletionsWithInsertText) { + if ((origin?.kind === SymbolOriginInfoKind.TypeOnlyAlias) || (contextToken && contextToken.kind !== SyntaxKind.OpenBraceToken && completionKind === CompletionKind.ObjectPropertyDeclaration && preferences.includeCompletionsWithInsertText)) { hasAction = true; } @@ -2449,7 +2445,7 @@ function getCompletionEntryCodeActionsAndSourceDisplay( return { codeActions: [codeAction], sourceDisplay: undefined }; } - if (contextToken && isObjectLiteralExpression(contextToken.parent.parent) && previousToken?.kind !== SyntaxKind.ColonToken) { //previoustoken =: , preferences are0 + if (contextToken && isObjectLiteralExpression(contextToken.parent.parent) && previousToken?.kind !== SyntaxKind.ColonToken) { let changes: FileTextChanges[]; if (getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line) { changes = textChanges.ChangeTracker.with( From 3ab385f6c79476a68adb25cc61705d6ecf5aa66a Mon Sep 17 00:00:00 2001 From: navya9singh <108360753+navya9singh@users.noreply.github.com> Date: Thu, 23 Feb 2023 19:50:13 +0000 Subject: [PATCH 05/12] Adressing pr comments --- src/compiler/diagnosticMessages.json | 4 ++ src/services/completions.ts | 35 +++++++++-------- .../completionsObjectLiteralExpressions1.ts | 39 ++++++++++--------- .../completionsObjectLiteralExpressions2.ts | 19 ++++----- tests/cases/fourslash/fourslash.ts | 1 + 5 files changed, 53 insertions(+), 45 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index b8ffc66a08443..962d3aeea087a 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -7720,5 +7720,9 @@ "Compiler option '{0}' cannot be given an empty string.": { "category": "Error", "code": 18051 + }, + "Add missing comma for an object member completion '{0}'.": { + "category": "Message", + "code": 18052 } } diff --git a/src/services/completions.ts b/src/services/completions.ts index 06ad65e983639..08ece19e7ee27 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -54,7 +54,6 @@ import { Expression, ExpressionWithTypeArguments, factory, - FileTextChanges, filter, find, findAncestor, @@ -294,7 +293,6 @@ import { Program, programContainsModules, PropertyAccessExpression, - PropertyAssignment, PropertyDeclaration, PropertyName, PropertySignature, @@ -360,6 +358,8 @@ import { UserPreferences, VariableDeclaration, walkUpParenthesizedExpressions, + isPropertyAssignment, + findNextToken, } from "./_namespaces/ts"; import { StringCompletions } from "./_namespaces/ts.Completions"; @@ -425,6 +425,8 @@ export enum CompletionSource { ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/", /** Case completions for switch statements */ SwitchCases = "SwitchCases/", + /** Completions for an Object literal expression */ + ObjectLiteralExpression = "ObjectLiteralExpression/", } /** @internal */ @@ -1348,7 +1350,13 @@ function createCompletionEntry( } } - if ((origin?.kind === SymbolOriginInfoKind.TypeOnlyAlias) || (contextToken && contextToken.kind !== SyntaxKind.OpenBraceToken && completionKind === CompletionKind.ObjectPropertyDeclaration && preferences.includeCompletionsWithInsertText)) { + if ((origin?.kind === SymbolOriginInfoKind.TypeOnlyAlias)) { + hasAction = true; + } + + if ((contextToken && isPropertyAssignment(contextToken.parent) && findNextToken(contextToken, contextToken?.parent, sourceFile)?.kind !== SyntaxKind.CommaToken && + completionKind === CompletionKind.ObjectPropertyDeclaration)) { + source = CompletionSource.ObjectLiteralExpression; hasAction = true; } @@ -2277,7 +2285,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.ObjectLiteralExpression) ? { type: "symbol" as const, symbol, location, origin, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation } : undefined; }) || { type: "none" }; @@ -2466,24 +2475,16 @@ function getCompletionEntryCodeActionsAndSourceDisplay( return { codeActions: [codeAction], sourceDisplay: undefined }; } - if (contextToken && isObjectLiteralExpression(contextToken.parent.parent) && previousToken?.kind !== SyntaxKind.ColonToken) { - let changes: FileTextChanges[]; - if (getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line) { - changes = textChanges.ChangeTracker.with( - { host, formatContext, preferences }, - tracker=>tracker.replacePropertyAssignment(sourceFile, contextToken.parent as PropertyAssignment ,contextToken.parent as PropertyAssignment)); - } - else { - changes = textChanges.ChangeTracker.with( - { host, formatContext, preferences }, - tracker=>tracker.replacePropertyAssignmentOnSameLine(sourceFile, contextToken.parent as PropertyAssignment ,contextToken.parent as PropertyAssignment)); - } + if (source === CompletionSource.ObjectLiteralExpression && 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.Includes_imports_of_types_referenced_by_0, name]), + description: diagnosticToString([Diagnostics.Add_missing_comma_for_an_object_member_completion_0, name]), }], }; } diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts index 07c928577ef6b..f9bc0d438b64c 100644 --- a/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts @@ -10,23 +10,24 @@ //// }; verify.completions({ - marker: "", - includes: [ - { - name: "secondary", - sortText: completion.SortText.OptionalMember, - hasAction: true, - }], - preferences: { - allowIncompleteCompletions: true, - includeInsertTextCompletions: true, - }, + marker: "", + includes: [{ + name: "secondary", + sortText: completion.SortText.OptionalMember, + hasAction: true, + source: completion.CompletionSource.ObjectLiteralExpression, + }], + preferences: { + allowIncompleteCompletions: true, + includeInsertTextCompletions: true, + }, }); verify.applyCodeActionFromCompletion("", { - name: "secondary", - description: `Includes imports of types referenced by 'secondary'`, - newFileContent: + name: "secondary", + description: `Add missing comma for an object member completion 'secondary'.`, + source: completion.CompletionSource.ObjectLiteralExpression, + newFileContent: `interface ColorPalette { primary?: string; secondary?: string; @@ -35,8 +36,8 @@ let colors: ColorPalette = { primary: "red", };`, - preferences: { - allowIncompleteCompletions: true, - includeInsertTextCompletions: true, - }, - }); + preferences: { + allowIncompleteCompletions: true, + includeInsertTextCompletions: true, + }, +}); diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts index 1bd46a6338633..c5769ffaa4bf1 100644 --- a/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts @@ -14,21 +14,22 @@ verify.completions({ marker: "", - includes: [ - { - name: "secondary", - sortText: completion.SortText.OptionalMember, - hasAction: true, - }], + includes: [{ + name: "secondary", + sortText: completion.SortText.OptionalMember, + hasAction: true, + source: completion.CompletionSource.ObjectLiteralExpression, + }], preferences: { - allowIncompleteCompletions: true, - includeInsertTextCompletions: true, + allowIncompleteCompletions: true, + includeInsertTextCompletions: true, } }); verify.applyCodeActionFromCompletion("", { name: "secondary", - description: `Includes imports of types referenced by 'secondary'`, + description: `Add missing comma for an object member completion 'secondary'.`, + source: completion.CompletionSource.ObjectLiteralExpression, newFileContent: `interface ColorPalette { primary?: string; diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 53352c8573dc1..cb1b013f181b0 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -899,6 +899,7 @@ declare namespace completion { TypeOnlyAlias = "TypeOnlyAlias/", ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/", SwitchCases = "SwitchCases/", + ObjectLiteralExpression = "ObjectLiteralExpression/", } export const globalThisEntry: Entry; export const undefinedVarEntry: Entry; From abb5c0710c5bce3bfa1ae1d6c75d6226cced5e07 Mon Sep 17 00:00:00 2001 From: navya9singh <108360753+navya9singh@users.noreply.github.com> Date: Thu, 23 Feb 2023 20:16:16 +0000 Subject: [PATCH 06/12] fixing auto-imports --- src/services/completions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 08ece19e7ee27..45c26f3eefb31 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -59,6 +59,7 @@ import { findAncestor, findChildOfKind, findPrecedingToken, + findNextToken, first, firstDefined, flatMap, @@ -205,6 +206,7 @@ import { isPropertyAccessExpression, isPropertyDeclaration, isPropertyNameLiteral, + isPropertyAssignment, isRegularExpressionLiteral, isShorthandPropertyAssignment, isSingleOrDoubleQuote, @@ -358,8 +360,6 @@ import { UserPreferences, VariableDeclaration, walkUpParenthesizedExpressions, - isPropertyAssignment, - findNextToken, } from "./_namespaces/ts"; import { StringCompletions } from "./_namespaces/ts.Completions"; From c8c1362751e6a82dcb4ff82a15ce8ada106ef367 Mon Sep 17 00:00:00 2001 From: navya9singh <108360753+navya9singh@users.noreply.github.com> Date: Thu, 23 Feb 2023 20:33:24 +0000 Subject: [PATCH 07/12] fixing lint error --- src/services/completions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 45c26f3eefb31..a1230cb966a01 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -58,8 +58,8 @@ import { find, findAncestor, findChildOfKind, - findPrecedingToken, findNextToken, + findPrecedingToken, first, firstDefined, flatMap, @@ -204,9 +204,9 @@ import { isPrivateIdentifier, isPrivateIdentifierClassElementDeclaration, isPropertyAccessExpression, + isPropertyAssignment, isPropertyDeclaration, isPropertyNameLiteral, - isPropertyAssignment, isRegularExpressionLiteral, isShorthandPropertyAssignment, isSingleOrDoubleQuote, From 54456cc6380a3676b7c3487dc10d55ddebed63ca Mon Sep 17 00:00:00 2001 From: navya9singh Date: Tue, 20 Jun 2023 15:29:28 -0700 Subject: [PATCH 08/12] Adressing pr comments and adding test cases --- src/compiler/diagnosticMessages.json | 4 ++ src/services/completions.ts | 27 +++++++---- src/services/textChanges.ts | 5 -- .../completionsObjectLiteralExpressions1.ts | 4 +- .../completionsObjectLiteralExpressions2.ts | 4 +- .../completionsObjectLiteralExpressions3.ts | 47 +++++++++++++++++++ .../completionsObjectLiteralExpressions4.ts | 44 +++++++++++++++++ tests/cases/fourslash/fourslash.ts | 2 +- 8 files changed, 118 insertions(+), 19 deletions(-) create mode 100644 tests/cases/fourslash/completionsObjectLiteralExpressions3.ts create mode 100644 tests/cases/fourslash/completionsObjectLiteralExpressions4.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 65ccc4cb18fdc..03b8a346b15a4 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -7644,6 +7644,10 @@ "category": "Message", "code": 95186 }, + "Add missing comma for an 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", diff --git a/src/services/completions.ts b/src/services/completions.ts index bcdbcffe7422b..b221574a0d941 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -446,7 +446,7 @@ export enum CompletionSource { /** Case completions for switch statements */ SwitchCases = "SwitchCases/", /** Completions for an Object literal expression */ - ObjectLiteralExpression = "ObjectLiteralExpression/", + ObjectLiteralMemberWithComma = "ObjectLiteralMemberWithComma/", } /** @internal */ @@ -1687,9 +1687,11 @@ function createCompletionEntry( hasAction = true; } - if ((contextToken && isPropertyAssignment(contextToken.parent) && findNextToken(contextToken, contextToken?.parent, sourceFile)?.kind !== SyntaxKind.CommaToken && - completionKind === CompletionKind.ObjectPropertyDeclaration)) { - source = CompletionSource.ObjectLiteralExpression; + if (completionKind === CompletionKind.ObjectPropertyDeclaration && contextToken && + findPrecedingToken(contextToken.pos, sourceFile, contextToken)?.kind !== SyntaxKind.CommaToken && + findNextToken(contextToken, contextToken.parent, sourceFile)?.kind !== SyntaxKind.CommaToken && + (findAncestor(contextToken.parent, (node: Node) => isPropertyAssignment(node))?.getLastToken() === contextToken || isMethodDeclaration(contextToken.parent.parent))) { + source = CompletionSource.ObjectLiteralMemberWithComma; hasAction = true; } @@ -2675,7 +2677,7 @@ function getSymbolCompletionFromEntryId( entryId.source === CompletionSource.ClassMemberSnippet && symbol.flags & SymbolFlags.ClassMember || entryId.source === CompletionSource.ObjectLiteralMethodSnippet && symbol.flags & (SymbolFlags.Property | SymbolFlags.Method) || getSourceFromOrigin(origin) === entryId.source - || entryId.source === CompletionSource.ObjectLiteralExpression) + || entryId.source === CompletionSource.ObjectLiteralMemberWithComma) ? { type: "symbol" as const, symbol, location, origin, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation } : undefined; }) || { type: "none" }; @@ -2871,10 +2873,10 @@ function getCompletionEntryCodeActionsAndSourceDisplay( return { codeActions: [codeAction], sourceDisplay: undefined }; } - if (source === CompletionSource.ObjectLiteralExpression && contextToken) { + if (source === CompletionSource.ObjectLiteralMemberWithComma && contextToken) { const changes = textChanges.ChangeTracker.with( { host, formatContext, preferences }, - tracker=>tracker.insertText(sourceFile, contextToken.end,",")); + tracker => tracker.insertText(sourceFile, contextToken.end,",")); if (changes) { return { sourceDisplay: undefined, @@ -4920,6 +4922,11 @@ function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): Ob return parent; } break; + case SyntaxKind.CloseBraceToken: // const x = { } | + if (parent.parent && parent.parent.parent && isMethodDeclaration(parent.parent) && isObjectLiteralExpression(parent.parent.parent)) { + return parent.parent.parent; + } + break; case SyntaxKind.AsteriskToken: return isMethodDeclaration(parent) ? tryCast(parent.parent, isObjectLiteralExpression) : undefined; case SyntaxKind.AsyncKeyword: @@ -4928,8 +4935,10 @@ function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): Ob return (contextToken as Identifier).text === "async" && isShorthandPropertyAssignment(contextToken.parent) ? contextToken.parent.parent : undefined; default: - if (parent.parent && isObjectLiteralExpression(parent.parent) && contextToken.kind !== SyntaxKind.ColonToken) { - return parent.parent; + const ancestorNode = findAncestor(parent, (node: Node) => isPropertyAssignment(node)); + if (contextToken.kind !== SyntaxKind.ColonToken && ancestorNode && ancestorNode.getLastToken() === contextToken && + isObjectLiteralExpression(ancestorNode.parent)) { + return ancestorNode.parent; } } } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 5ab3b915ed27a..d52ab642a5e5d 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -601,11 +601,6 @@ export class ChangeTracker { this.replaceNode(sourceFile, oldNode, newNode, { suffix }); } - public replacePropertyAssignmentOnSameLine(sourceFile: SourceFile, oldNode: PropertyAssignment, newNode: PropertyAssignment): void { - const suffix = this.nextCommaToken(sourceFile, oldNode) ? "" : ","; - this.replaceNode(sourceFile, oldNode, newNode, { suffix }); - } - public insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}): void { this.replaceRange(sourceFile, createRange(pos), newNode, options); } diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts index f9bc0d438b64c..960cd5d4f81bc 100644 --- a/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts @@ -15,7 +15,7 @@ verify.completions({ name: "secondary", sortText: completion.SortText.OptionalMember, hasAction: true, - source: completion.CompletionSource.ObjectLiteralExpression, + source: completion.CompletionSource.ObjectLiteralMemberWithComma, }], preferences: { allowIncompleteCompletions: true, @@ -26,7 +26,7 @@ verify.completions({ verify.applyCodeActionFromCompletion("", { name: "secondary", description: `Add missing comma for an object member completion 'secondary'.`, - source: completion.CompletionSource.ObjectLiteralExpression, + source: completion.CompletionSource.ObjectLiteralMemberWithComma, newFileContent: `interface ColorPalette { primary?: string; diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts index c5769ffaa4bf1..4801540f3a373 100644 --- a/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts @@ -18,7 +18,7 @@ verify.completions({ name: "secondary", sortText: completion.SortText.OptionalMember, hasAction: true, - source: completion.CompletionSource.ObjectLiteralExpression, + source: completion.CompletionSource.ObjectLiteralMemberWithComma, }], preferences: { allowIncompleteCompletions: true, @@ -29,7 +29,7 @@ verify.completions({ verify.applyCodeActionFromCompletion("", { name: "secondary", description: `Add missing comma for an object member completion 'secondary'.`, - source: completion.CompletionSource.ObjectLiteralExpression, + source: completion.CompletionSource.ObjectLiteralMemberWithComma, newFileContent: `interface ColorPalette { primary?: string; diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions3.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions3.ts new file mode 100644 index 0000000000000..08c95aa065819 --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions3.ts @@ -0,0 +1,47 @@ +/// +////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 an object member completion 'aaa'.`, + source: completion.CompletionSource.ObjectLiteralMemberWithComma, + newFileContent: + `interface T { + aaa?: string; + foo(): void; + } + const obj: T = { + foo() { + }, + + }`, + preferences: { + allowIncompleteCompletions: true, + includeInsertTextCompletions: true, + }, + }); diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions4.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions4.ts new file mode 100644 index 0000000000000..63f1450084448 --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions4.ts @@ -0,0 +1,44 @@ +/// +////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 an 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, + }, + }); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 7bf6356a07cac..4121114c72be6 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -914,7 +914,7 @@ declare namespace completion { TypeOnlyAlias = "TypeOnlyAlias/", ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/", SwitchCases = "SwitchCases/", - ObjectLiteralExpression = "ObjectLiteralExpression/", + ObjectLiteralMemberWithComma = "ObjectLiteralMemberWithComma/", } export const globalThisEntry: Entry; export const undefinedVarEntry: Entry; From 2fff618e020cefdf4fe7c092611396aeffa26ba3 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Fri, 23 Jun 2023 15:03:31 -0700 Subject: [PATCH 09/12] adding test cases --- src/compiler/diagnosticMessages.json | 2 +- src/services/completions.ts | 39 +++-- .../completionsObjectLiteralExpressions1.ts | 2 +- .../completionsObjectLiteralExpressions2.ts | 2 +- .../completionsObjectLiteralExpressions3.ts | 8 +- .../completionsObjectLiteralExpressions4.ts | 10 +- .../completionsObjectLiteralExpressions5.ts | 39 +++++ .../completionsObjectLiteralExpressions6.ts | 45 ++++++ .../completionsObjectLiteralExpressions7.ts | 57 +++++++ .../completionsObjectLiteralExpressions8.ts | 45 ++++++ .../completionsObjectLiteralExpressions9.ts | 139 ++++++++++++++++++ 11 files changed, 360 insertions(+), 28 deletions(-) create mode 100644 tests/cases/fourslash/completionsObjectLiteralExpressions5.ts create mode 100644 tests/cases/fourslash/completionsObjectLiteralExpressions6.ts create mode 100644 tests/cases/fourslash/completionsObjectLiteralExpressions7.ts create mode 100644 tests/cases/fourslash/completionsObjectLiteralExpressions8.ts create mode 100644 tests/cases/fourslash/completionsObjectLiteralExpressions9.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 03b8a346b15a4..efa2c22652115 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -7644,7 +7644,7 @@ "category": "Message", "code": 95186 }, - "Add missing comma for an object member completion '{0}'.": { + "Add missing comma for object member completion '{0}'.": { "category": "Message", "code": 95187 }, diff --git a/src/services/completions.ts b/src/services/completions.ts index b221574a0d941..fd3993cff0d69 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -65,7 +65,6 @@ import { find, findAncestor, findChildOfKind, - findNextToken, findPrecedingToken, first, firstDefined, @@ -1683,14 +1682,15 @@ function createCompletionEntry( } } - if ((origin?.kind === SymbolOriginInfoKind.TypeOnlyAlias)) { + if (origin?.kind === SymbolOriginInfoKind.TypeOnlyAlias) { hasAction = true; } if (completionKind === CompletionKind.ObjectPropertyDeclaration && contextToken && findPrecedingToken(contextToken.pos, sourceFile, contextToken)?.kind !== SyntaxKind.CommaToken && - findNextToken(contextToken, contextToken.parent, sourceFile)?.kind !== SyntaxKind.CommaToken && - (findAncestor(contextToken.parent, (node: Node) => isPropertyAssignment(node))?.getLastToken() === contextToken || isMethodDeclaration(contextToken.parent.parent))) { + (isMethodDeclaration(contextToken.parent.parent) || isSpreadAssignment(contextToken.parent) || findAncestor(contextToken.parent, (node: Node) => isPropertyAssignment(node))?.getLastToken() === contextToken || + isShorthandPropertyAssignment(contextToken.parent) && getLineAndCharacterOfPosition(contextToken.getSourceFile(), contextToken.getEnd()).line !== getLineAndCharacterOfPosition(contextToken.getSourceFile(), position).line)) { + source = CompletionSource.ObjectLiteralMemberWithComma; hasAction = true; } @@ -2882,7 +2882,7 @@ function getCompletionEntryCodeActionsAndSourceDisplay( sourceDisplay: undefined, codeActions: [{ changes, - description: diagnosticToString([Diagnostics.Add_missing_comma_for_an_object_member_completion_0, name]), + description: diagnosticToString([Diagnostics.Add_missing_comma_for_object_member_completion_0, name]), }], }; } @@ -4184,7 +4184,7 @@ function getCompletionData( */ function tryGetObjectLikeCompletionSymbols(): GlobalsSearch | undefined { const symbolsStartIndex = symbols.length; - const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken); + const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken, position); if (!objectLikeContainer) return GlobalsSearch.Continue; // We're looking up possible property names from contextual/inferred/declared type. @@ -4912,7 +4912,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): ObjectLiteralExpression | ObjectBindingPattern | undefined { if (contextToken) { const { parent } = contextToken; switch (contextToken.kind) { @@ -4922,19 +4922,30 @@ function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): Ob return parent; } break; - case SyntaxKind.CloseBraceToken: // const x = { } | - if (parent.parent && parent.parent.parent && isMethodDeclaration(parent.parent) && isObjectLiteralExpression(parent.parent.parent)) { - return parent.parent.parent; - } - break; case SyntaxKind.AsteriskToken: return isMethodDeclaration(parent) ? tryCast(parent.parent, isObjectLiteralExpression) : undefined; 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(contextToken.getSourceFile(), contextToken.getEnd()).line !== getLineAndCharacterOfPosition(contextToken.getSourceFile(), position).line))) { + return contextToken.parent.parent; + } + const ancestorNode = findAncestor(parent, (node: Node) => isPropertyAssignment(node)); + if (ancestorNode && ancestorNode.getLastToken() === contextToken && isObjectLiteralExpression(ancestorNode.parent)) { + return ancestorNode.parent; + } + } + break; default: + if (parent.parent && parent.parent.parent && isMethodDeclaration(parent.parent) && isObjectLiteralExpression(parent.parent.parent)) { + return parent.parent.parent; + } const ancestorNode = findAncestor(parent, (node: Node) => isPropertyAssignment(node)); if (contextToken.kind !== SyntaxKind.ColonToken && ancestorNode && ancestorNode.getLastToken() === contextToken && isObjectLiteralExpression(ancestorNode.parent)) { diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts index 960cd5d4f81bc..6345fcb6569ee 100644 --- a/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions1.ts @@ -25,7 +25,7 @@ verify.completions({ verify.applyCodeActionFromCompletion("", { name: "secondary", - description: `Add missing comma for an object member completion 'secondary'.`, + description: `Add missing comma for object member completion 'secondary'.`, source: completion.CompletionSource.ObjectLiteralMemberWithComma, newFileContent: `interface ColorPalette { diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts index 4801540f3a373..8a67507a409c0 100644 --- a/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions2.ts @@ -28,7 +28,7 @@ verify.completions({ verify.applyCodeActionFromCompletion("", { name: "secondary", - description: `Add missing comma for an object member completion 'secondary'.`, + description: `Add missing comma for object member completion 'secondary'.`, source: completion.CompletionSource.ObjectLiteralMemberWithComma, newFileContent: `interface ColorPalette { diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions3.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions3.ts index 08c95aa065819..a8003c105888e 100644 --- a/tests/cases/fourslash/completionsObjectLiteralExpressions3.ts +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions3.ts @@ -10,8 +10,6 @@ //// /**/ //// } - - verify.completions({ marker: "", includes: [{ @@ -24,11 +22,11 @@ verify.completions({ allowIncompleteCompletions: true, includeInsertTextCompletions: true, }, - }); +}); - verify.applyCodeActionFromCompletion("", { +verify.applyCodeActionFromCompletion("", { name: "aaa", - description: `Add missing comma for an object member completion 'aaa'.`, + description: `Add missing comma for object member completion 'aaa'.`, source: completion.CompletionSource.ObjectLiteralMemberWithComma, newFileContent: `interface T { diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions4.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions4.ts index 63f1450084448..3ef13a46e4b1d 100644 --- a/tests/cases/fourslash/completionsObjectLiteralExpressions4.ts +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions4.ts @@ -8,8 +8,6 @@ //// /**/ //// } - - verify.completions({ marker: "", includes: [{ @@ -22,11 +20,11 @@ verify.completions({ allowIncompleteCompletions: true, includeInsertTextCompletions: true, }, - }); +}); - verify.applyCodeActionFromCompletion("", { +verify.applyCodeActionFromCompletion("", { name: "bbb", - description: `Add missing comma for an object member completion 'bbb'.`, + description: `Add missing comma for object member completion 'bbb'.`, source: completion.CompletionSource.ObjectLiteralMemberWithComma, newFileContent: `interface T { @@ -41,4 +39,4 @@ verify.completions({ allowIncompleteCompletions: true, includeInsertTextCompletions: true, }, - }); +}); diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions5.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions5.ts new file mode 100644 index 0000000000000..9da760b90d493 --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions5.ts @@ -0,0 +1,39 @@ +/// + +//// 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, + }, +}); diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions6.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions6.ts new file mode 100644 index 0000000000000..0ab643271c48e --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions6.ts @@ -0,0 +1,45 @@ + + +/// + +//// 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, + }, +}); diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions7.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions7.ts new file mode 100644 index 0000000000000..6e5b06cdb5c59 --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions7.ts @@ -0,0 +1,57 @@ + + +/// + +//// interface pp { +//// aaa: string; +//// bbb: number; +//// } +//// +//// const abc: pp = { +//// aaa: "", +//// bbb: 1, +//// } +//// +//// const cab: pp = { +//// ...abc +//// /**/ +//// } + +verify.completions({ + marker: "", + includes: [{ + name: "aaa", + sortText: completion.SortText.MemberDeclaredBySpreadAssignment, + 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 pp { + aaa: string; + bbb: number; +} + +const abc: pp = { + aaa: "", + bbb: 1, +} + +const cab: pp = { + ...abc, + +}`, + preferences: { + allowIncompleteCompletions: true, + includeInsertTextCompletions: true, + }, +}); diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions8.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions8.ts new file mode 100644 index 0000000000000..5f2681dbb83c5 --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions8.ts @@ -0,0 +1,45 @@ +/// + +//// 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, + }, +}); diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions9.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions9.ts new file mode 100644 index 0000000000000..fcf9537c61fd0 --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions9.ts @@ -0,0 +1,139 @@ +/// +// @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, + }, +}); + \ No newline at end of file From b33aef8b33926bcbb566f5859b9852bc500dbcd5 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Fri, 23 Jun 2023 17:56:57 -0700 Subject: [PATCH 10/12] Minor changes and adding a test case --- src/services/completions.ts | 47 +++++++++++++------ .../completionsObjectLiteralExpressions10.ts | 47 +++++++++++++++++++ 2 files changed, 79 insertions(+), 15 deletions(-) create mode 100644 tests/cases/fourslash/completionsObjectLiteralExpressions10.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index fd3993cff0d69..1180aab60ee47 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -164,6 +164,7 @@ import { isFunctionLikeDeclaration, isFunctionLikeKind, isFunctionTypeNode, + isGetAccessorDeclaration, isIdentifier, isIdentifierText, isImportableFile, @@ -221,6 +222,7 @@ import { isPropertyDeclaration, isPropertyNameLiteral, isRegularExpressionLiteral, + isSetAccessorDeclaration, isShorthandPropertyAssignment, isSingleOrDoubleQuote, isSourceFile, @@ -1686,13 +1688,28 @@ function createCompletionEntry( hasAction = true; } - if (completionKind === CompletionKind.ObjectPropertyDeclaration && contextToken && - findPrecedingToken(contextToken.pos, sourceFile, contextToken)?.kind !== SyntaxKind.CommaToken && - (isMethodDeclaration(contextToken.parent.parent) || isSpreadAssignment(contextToken.parent) || findAncestor(contextToken.parent, (node: Node) => isPropertyAssignment(node))?.getLastToken() === contextToken || - isShorthandPropertyAssignment(contextToken.parent) && getLineAndCharacterOfPosition(contextToken.getSourceFile(), contextToken.getEnd()).line !== getLineAndCharacterOfPosition(contextToken.getSourceFile(), position).line)) { + // 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() === contextToken || + isShorthandPropertyAssignment(contextToken.parent) && getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line) { - source = CompletionSource.ObjectLiteralMemberWithComma; - hasAction = true; + source = CompletionSource.ObjectLiteralMemberWithComma; + hasAction = true; + } } if (preferences.includeCompletionsWithClassMemberSnippets && @@ -2876,7 +2893,7 @@ function getCompletionEntryCodeActionsAndSourceDisplay( if (source === CompletionSource.ObjectLiteralMemberWithComma && contextToken) { const changes = textChanges.ChangeTracker.with( { host, formatContext, preferences }, - tracker => tracker.insertText(sourceFile, contextToken.end,",")); + tracker => tracker.insertText(sourceFile, contextToken.end, ",")); if (changes) { return { sourceDisplay: undefined, @@ -4184,7 +4201,7 @@ function getCompletionData( */ function tryGetObjectLikeCompletionSymbols(): GlobalsSearch | undefined { const symbolsStartIndex = symbols.length; - const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken, position); + const objectLikeContainer = tryGetObjectLikeCompletionContainer(contextToken, position, sourceFile); if (!objectLikeContainer) return GlobalsSearch.Continue; // We're looking up possible property names from contextual/inferred/declared type. @@ -4912,7 +4929,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, position: number): ObjectLiteralExpression | ObjectBindingPattern | undefined { +function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined, position: number, sourceFile: SourceFile): ObjectLiteralExpression | ObjectBindingPattern | undefined { if (contextToken) { const { parent } = contextToken; switch (contextToken.kind) { @@ -4933,21 +4950,21 @@ function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined, pos else { if (isObjectLiteralExpression(contextToken.parent.parent) && (isSpreadAssignment(contextToken.parent) || isShorthandPropertyAssignment(contextToken.parent) && - (getLineAndCharacterOfPosition(contextToken.getSourceFile(), contextToken.getEnd()).line !== getLineAndCharacterOfPosition(contextToken.getSourceFile(), position).line))) { + (getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line))) { return contextToken.parent.parent; } - const ancestorNode = findAncestor(parent, (node: Node) => isPropertyAssignment(node)); - if (ancestorNode && ancestorNode.getLastToken() === contextToken && isObjectLiteralExpression(ancestorNode.parent)) { + const ancestorNode = findAncestor(parent, isPropertyAssignment); + if (ancestorNode?.getLastToken() === contextToken && isObjectLiteralExpression(ancestorNode.parent)) { return ancestorNode.parent; } } break; default: - if (parent.parent && parent.parent.parent && isMethodDeclaration(parent.parent) && isObjectLiteralExpression(parent.parent.parent)) { + if (parent.parent?.parent && (isMethodDeclaration(parent.parent) || isGetAccessorDeclaration(parent.parent) || isSetAccessorDeclaration(parent.parent)) && isObjectLiteralExpression(parent.parent.parent)) { return parent.parent.parent; } - const ancestorNode = findAncestor(parent, (node: Node) => isPropertyAssignment(node)); - if (contextToken.kind !== SyntaxKind.ColonToken && ancestorNode && ancestorNode.getLastToken() === contextToken && + const ancestorNode = findAncestor(parent, isPropertyAssignment); + if (contextToken.kind !== SyntaxKind.ColonToken && ancestorNode?.getLastToken() === contextToken && isObjectLiteralExpression(ancestorNode.parent)) { return ancestorNode.parent; } diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions10.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions10.ts new file mode 100644 index 0000000000000..5f98448820949 --- /dev/null +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions10.ts @@ -0,0 +1,47 @@ +/// +//// 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, + }, + }); + \ No newline at end of file From 89d178b6ded5b6ca2a5e1c885d5e2578ebe1d34e Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 26 Jun 2023 16:23:35 -0700 Subject: [PATCH 11/12] Apply suggestions from code review --- src/services/completions.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 1180aab60ee47..ad7a3f2c0b609 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1704,7 +1704,7 @@ function createCompletionEntry( isGetAccessorDeclaration(contextToken.parent.parent) || isSetAccessorDeclaration(contextToken.parent.parent) || isSpreadAssignment(contextToken.parent) || - findAncestor(contextToken.parent, isPropertyAssignment)?.getLastToken() === contextToken || + findAncestor(contextToken.parent, isPropertyAssignment)?.getLastToken(sourceFile) === contextToken || isShorthandPropertyAssignment(contextToken.parent) && getLineAndCharacterOfPosition(sourceFile, contextToken.getEnd()).line !== getLineAndCharacterOfPosition(sourceFile, position).line) { source = CompletionSource.ObjectLiteralMemberWithComma; @@ -2893,7 +2893,9 @@ function getCompletionEntryCodeActionsAndSourceDisplay( if (source === CompletionSource.ObjectLiteralMemberWithComma && contextToken) { const changes = textChanges.ChangeTracker.with( { host, formatContext, preferences }, - tracker => tracker.insertText(sourceFile, contextToken.end, ",")); + tracker => tracker.insertText(sourceFile, contextToken.end, ",") + ); + if (changes) { return { sourceDisplay: undefined, @@ -4954,7 +4956,7 @@ function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined, pos return contextToken.parent.parent; } const ancestorNode = findAncestor(parent, isPropertyAssignment); - if (ancestorNode?.getLastToken() === contextToken && isObjectLiteralExpression(ancestorNode.parent)) { + if (ancestorNode?.getLastToken(sourceFile) === contextToken && isObjectLiteralExpression(ancestorNode.parent)) { return ancestorNode.parent; } } @@ -4964,7 +4966,7 @@ function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined, pos return parent.parent.parent; } const ancestorNode = findAncestor(parent, isPropertyAssignment); - if (contextToken.kind !== SyntaxKind.ColonToken && ancestorNode?.getLastToken() === contextToken && + if (contextToken.kind !== SyntaxKind.ColonToken && ancestorNode?.getLastToken(sourceFile) === contextToken && isObjectLiteralExpression(ancestorNode.parent)) { return ancestorNode.parent; } From 7e861b6cd95ebaaa4b544cb48ce4d03b9e5c56c5 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Mon, 26 Jun 2023 17:46:06 -0700 Subject: [PATCH 12/12] Adding test for spread assignment --- src/services/completions.ts | 3 + .../completionsObjectLiteralExpressions7.ts | 98 ++++++++++++++++--- 2 files changed, 88 insertions(+), 13 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index c0f3148e688c8..2b47de8b7db94 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -4965,6 +4965,9 @@ function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined, pos 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)) { diff --git a/tests/cases/fourslash/completionsObjectLiteralExpressions7.ts b/tests/cases/fourslash/completionsObjectLiteralExpressions7.ts index 6e5b06cdb5c59..3cd6c3c011f5a 100644 --- a/tests/cases/fourslash/completionsObjectLiteralExpressions7.ts +++ b/tests/cases/fourslash/completionsObjectLiteralExpressions7.ts @@ -2,6 +2,31 @@ /// +// @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; @@ -14,14 +39,14 @@ //// //// const cab: pp = { //// ...abc -//// /**/ +//// /*b*/ //// } verify.completions({ - marker: "", + marker: "a", includes: [{ - name: "aaa", - sortText: completion.SortText.MemberDeclaredBySpreadAssignment, + name: "bbb", + sortText: completion.SortText.LocationPriority, hasAction: true, source: completion.CompletionSource.ObjectLiteralMemberWithComma, }], @@ -31,12 +56,59 @@ verify.completions({ }, }); -verify.applyCodeActionFromCompletion("", { - name: "aaa", - description: `Add missing comma for object member completion 'aaa'.`, +verify.applyCodeActionFromCompletion("a", { + name: "bbb", + description: `Add missing comma for object member completion 'bbb'.`, source: completion.CompletionSource.ObjectLiteralMemberWithComma, newFileContent: - `interface pp { + `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; } @@ -50,8 +122,8 @@ const cab: pp = { ...abc, }`, - preferences: { - allowIncompleteCompletions: true, - includeInsertTextCompletions: true, - }, -}); + preferences: { + allowIncompleteCompletions: true, + includeInsertTextCompletions: true, + }, +}); \ No newline at end of file