@@ -164,6 +164,7 @@ import {
164164 isFunctionLikeDeclaration ,
165165 isFunctionLikeKind ,
166166 isFunctionTypeNode ,
167+ isGetAccessorDeclaration ,
167168 isIdentifier ,
168169 isIdentifierText ,
169170 isImportableFile ,
@@ -217,9 +218,11 @@ import {
217218 isPrivateIdentifier ,
218219 isPrivateIdentifierClassElementDeclaration ,
219220 isPropertyAccessExpression ,
221+ isPropertyAssignment ,
220222 isPropertyDeclaration ,
221223 isPropertyNameLiteral ,
222224 isRegularExpressionLiteral ,
225+ isSetAccessorDeclaration ,
223226 isShorthandPropertyAssignment ,
224227 isSingleOrDoubleQuote ,
225228 isSourceFile ,
@@ -443,6 +446,8 @@ export enum CompletionSource {
443446 ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/" ,
444447 /** Case completions for switch statements */
445448 SwitchCases = "SwitchCases/" ,
449+ /** Completions for an Object literal expression */
450+ ObjectLiteralMemberWithComma = "ObjectLiteralMemberWithComma/" ,
446451}
447452
448453/** @internal */
@@ -1683,6 +1688,30 @@ function createCompletionEntry(
16831688 hasAction = true ;
16841689 }
16851690
1691+ // Provide object member completions when missing commas, and insert missing commas.
1692+ // For example:
1693+ //
1694+ // interface I {
1695+ // a: string;
1696+ // b: number
1697+ // }
1698+ //
1699+ // const cc: I = { a: "red" | }
1700+ //
1701+ // Completion should add a comma after "red" and provide completions for b
1702+ if ( completionKind === CompletionKind . ObjectPropertyDeclaration && contextToken && findPrecedingToken ( contextToken . pos , sourceFile , contextToken ) ?. kind !== SyntaxKind . CommaToken ) {
1703+ if ( isMethodDeclaration ( contextToken . parent . parent ) ||
1704+ isGetAccessorDeclaration ( contextToken . parent . parent ) ||
1705+ isSetAccessorDeclaration ( contextToken . parent . parent ) ||
1706+ isSpreadAssignment ( contextToken . parent ) ||
1707+ findAncestor ( contextToken . parent , isPropertyAssignment ) ?. getLastToken ( sourceFile ) === contextToken ||
1708+ isShorthandPropertyAssignment ( contextToken . parent ) && getLineAndCharacterOfPosition ( sourceFile , contextToken . getEnd ( ) ) . line !== getLineAndCharacterOfPosition ( sourceFile , position ) . line ) {
1709+
1710+ source = CompletionSource . ObjectLiteralMemberWithComma ;
1711+ hasAction = true ;
1712+ }
1713+ }
1714+
16861715 if ( preferences . includeCompletionsWithClassMemberSnippets &&
16871716 preferences . includeCompletionsWithInsertText &&
16881717 completionKind === CompletionKind . MemberLike &&
@@ -2664,7 +2693,8 @@ function getSymbolCompletionFromEntryId(
26642693 return info && info . name === entryId . name && (
26652694 entryId . source === CompletionSource . ClassMemberSnippet && symbol . flags & SymbolFlags . ClassMember
26662695 || entryId . source === CompletionSource . ObjectLiteralMethodSnippet && symbol . flags & ( SymbolFlags . Property | SymbolFlags . Method )
2667- || getSourceFromOrigin ( origin ) === entryId . source )
2696+ || getSourceFromOrigin ( origin ) === entryId . source
2697+ || entryId . source === CompletionSource . ObjectLiteralMemberWithComma )
26682698 ? { type : "symbol" as const , symbol, location, origin, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation }
26692699 : undefined ;
26702700 } ) || { type : "none" } ;
@@ -2860,6 +2890,23 @@ function getCompletionEntryCodeActionsAndSourceDisplay(
28602890 return { codeActions : [ codeAction ] , sourceDisplay : undefined } ;
28612891 }
28622892
2893+ if ( source === CompletionSource . ObjectLiteralMemberWithComma && contextToken ) {
2894+ const changes = textChanges . ChangeTracker . with (
2895+ { host, formatContext, preferences } ,
2896+ tracker => tracker . insertText ( sourceFile , contextToken . end , "," )
2897+ ) ;
2898+
2899+ if ( changes ) {
2900+ return {
2901+ sourceDisplay : undefined ,
2902+ codeActions : [ {
2903+ changes,
2904+ description : diagnosticToString ( [ Diagnostics . Add_missing_comma_for_object_member_completion_0 , name ] ) ,
2905+ } ] ,
2906+ } ;
2907+ }
2908+ }
2909+
28632910 if ( ! origin || ! ( originIsExport ( origin ) || originIsResolvedExport ( origin ) ) ) {
28642911 return { codeActions : undefined , sourceDisplay : undefined } ;
28652912 }
@@ -4156,7 +4203,7 @@ function getCompletionData(
41564203 */
41574204 function tryGetObjectLikeCompletionSymbols ( ) : GlobalsSearch | undefined {
41584205 const symbolsStartIndex = symbols . length ;
4159- const objectLikeContainer = tryGetObjectLikeCompletionContainer ( contextToken ) ;
4206+ const objectLikeContainer = tryGetObjectLikeCompletionContainer ( contextToken , position , sourceFile ) ;
41604207 if ( ! objectLikeContainer ) return GlobalsSearch . Continue ;
41614208
41624209 // We're looking up possible property names from contextual/inferred/declared type.
@@ -4884,7 +4931,7 @@ function getCompletionData(
48844931 * Returns the immediate owning object literal or binding pattern of a context token,
48854932 * on the condition that one exists and that the context implies completion should be given.
48864933 */
4887- function tryGetObjectLikeCompletionContainer ( contextToken : Node | undefined ) : ObjectLiteralExpression | ObjectBindingPattern | undefined {
4934+ function tryGetObjectLikeCompletionContainer ( contextToken : Node | undefined , position : number , sourceFile : SourceFile ) : ObjectLiteralExpression | ObjectBindingPattern | undefined {
48884935 if ( contextToken ) {
48894936 const { parent } = contextToken ;
48904937 switch ( contextToken . kind ) {
@@ -4899,8 +4946,33 @@ function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): Ob
48994946 case SyntaxKind . AsyncKeyword :
49004947 return tryCast ( parent . parent , isObjectLiteralExpression ) ;
49014948 case SyntaxKind . Identifier :
4902- return ( contextToken as Identifier ) . text === "async" && isShorthandPropertyAssignment ( contextToken . parent )
4903- ? contextToken . parent . parent : undefined ;
4949+ if ( ( contextToken as Identifier ) . text === "async" && isShorthandPropertyAssignment ( contextToken . parent ) ) {
4950+ return contextToken . parent . parent ;
4951+ }
4952+ else {
4953+ if ( isObjectLiteralExpression ( contextToken . parent . parent ) &&
4954+ ( isSpreadAssignment ( contextToken . parent ) || isShorthandPropertyAssignment ( contextToken . parent ) &&
4955+ ( getLineAndCharacterOfPosition ( sourceFile , contextToken . getEnd ( ) ) . line !== getLineAndCharacterOfPosition ( sourceFile , position ) . line ) ) ) {
4956+ return contextToken . parent . parent ;
4957+ }
4958+ const ancestorNode = findAncestor ( parent , isPropertyAssignment ) ;
4959+ if ( ancestorNode ?. getLastToken ( sourceFile ) === contextToken && isObjectLiteralExpression ( ancestorNode . parent ) ) {
4960+ return ancestorNode . parent ;
4961+ }
4962+ }
4963+ break ;
4964+ default :
4965+ if ( parent . parent ?. parent && ( isMethodDeclaration ( parent . parent ) || isGetAccessorDeclaration ( parent . parent ) || isSetAccessorDeclaration ( parent . parent ) ) && isObjectLiteralExpression ( parent . parent . parent ) ) {
4966+ return parent . parent . parent ;
4967+ }
4968+ if ( isSpreadAssignment ( parent ) && isObjectLiteralExpression ( parent . parent ) ) {
4969+ return parent . parent ;
4970+ }
4971+ const ancestorNode = findAncestor ( parent , isPropertyAssignment ) ;
4972+ if ( contextToken . kind !== SyntaxKind . ColonToken && ancestorNode ?. getLastToken ( sourceFile ) === contextToken &&
4973+ isObjectLiteralExpression ( ancestorNode . parent ) ) {
4974+ return ancestorNode . parent ;
4975+ }
49044976 }
49054977 }
49064978
0 commit comments