@@ -164,6 +164,7 @@ import {
164
164
isFunctionLikeDeclaration ,
165
165
isFunctionLikeKind ,
166
166
isFunctionTypeNode ,
167
+ isGetAccessorDeclaration ,
167
168
isIdentifier ,
168
169
isIdentifierText ,
169
170
isImportableFile ,
@@ -217,9 +218,11 @@ import {
217
218
isPrivateIdentifier ,
218
219
isPrivateIdentifierClassElementDeclaration ,
219
220
isPropertyAccessExpression ,
221
+ isPropertyAssignment ,
220
222
isPropertyDeclaration ,
221
223
isPropertyNameLiteral ,
222
224
isRegularExpressionLiteral ,
225
+ isSetAccessorDeclaration ,
223
226
isShorthandPropertyAssignment ,
224
227
isSingleOrDoubleQuote ,
225
228
isSourceFile ,
@@ -443,6 +446,8 @@ export enum CompletionSource {
443
446
ObjectLiteralMethodSnippet = "ObjectLiteralMethodSnippet/" ,
444
447
/** Case completions for switch statements */
445
448
SwitchCases = "SwitchCases/" ,
449
+ /** Completions for an Object literal expression */
450
+ ObjectLiteralMemberWithComma = "ObjectLiteralMemberWithComma/" ,
446
451
}
447
452
448
453
/** @internal */
@@ -1683,6 +1688,30 @@ function createCompletionEntry(
1683
1688
hasAction = true ;
1684
1689
}
1685
1690
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
+
1686
1715
if ( preferences . includeCompletionsWithClassMemberSnippets &&
1687
1716
preferences . includeCompletionsWithInsertText &&
1688
1717
completionKind === CompletionKind . MemberLike &&
@@ -2664,7 +2693,8 @@ function getSymbolCompletionFromEntryId(
2664
2693
return info && info . name === entryId . name && (
2665
2694
entryId . source === CompletionSource . ClassMemberSnippet && symbol . flags & SymbolFlags . ClassMember
2666
2695
|| 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 )
2668
2698
? { type : "symbol" as const , symbol, location, origin, contextToken, previousToken, isJsxInitializer, isTypeOnlyLocation }
2669
2699
: undefined ;
2670
2700
} ) || { type : "none" } ;
@@ -2860,6 +2890,23 @@ function getCompletionEntryCodeActionsAndSourceDisplay(
2860
2890
return { codeActions : [ codeAction ] , sourceDisplay : undefined } ;
2861
2891
}
2862
2892
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
+
2863
2910
if ( ! origin || ! ( originIsExport ( origin ) || originIsResolvedExport ( origin ) ) ) {
2864
2911
return { codeActions : undefined , sourceDisplay : undefined } ;
2865
2912
}
@@ -4156,7 +4203,7 @@ function getCompletionData(
4156
4203
*/
4157
4204
function tryGetObjectLikeCompletionSymbols ( ) : GlobalsSearch | undefined {
4158
4205
const symbolsStartIndex = symbols . length ;
4159
- const objectLikeContainer = tryGetObjectLikeCompletionContainer ( contextToken ) ;
4206
+ const objectLikeContainer = tryGetObjectLikeCompletionContainer ( contextToken , position , sourceFile ) ;
4160
4207
if ( ! objectLikeContainer ) return GlobalsSearch . Continue ;
4161
4208
4162
4209
// We're looking up possible property names from contextual/inferred/declared type.
@@ -4884,7 +4931,7 @@ function getCompletionData(
4884
4931
* Returns the immediate owning object literal or binding pattern of a context token,
4885
4932
* on the condition that one exists and that the context implies completion should be given.
4886
4933
*/
4887
- function tryGetObjectLikeCompletionContainer ( contextToken : Node | undefined ) : ObjectLiteralExpression | ObjectBindingPattern | undefined {
4934
+ function tryGetObjectLikeCompletionContainer ( contextToken : Node | undefined , position : number , sourceFile : SourceFile ) : ObjectLiteralExpression | ObjectBindingPattern | undefined {
4888
4935
if ( contextToken ) {
4889
4936
const { parent } = contextToken ;
4890
4937
switch ( contextToken . kind ) {
@@ -4899,8 +4946,33 @@ function tryGetObjectLikeCompletionContainer(contextToken: Node | undefined): Ob
4899
4946
case SyntaxKind . AsyncKeyword :
4900
4947
return tryCast ( parent . parent , isObjectLiteralExpression ) ;
4901
4948
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
+ }
4904
4976
}
4905
4977
}
4906
4978
0 commit comments