1
1
/* @internal */
2
2
namespace ts . codefix {
3
3
const fixMissingMember = "fixMissingMember" ;
4
+ const fixMissingProperties = "fixMissingProperties" ;
4
5
const fixMissingFunctionDeclaration = "fixMissingFunctionDeclaration" ;
6
+
5
7
const errorCodes = [
6
8
Diagnostics . Property_0_does_not_exist_on_type_1 . code ,
7
9
Diagnostics . Property_0_does_not_exist_on_type_1_Did_you_mean_2 . code ,
@@ -19,6 +21,10 @@ namespace ts.codefix {
19
21
if ( ! info ) {
20
22
return undefined ;
21
23
}
24
+ if ( info . kind === InfoKind . ObjectLiteral ) {
25
+ const changes = textChanges . ChangeTracker . with ( context , t => addObjectLiteralProperties ( t , context , info ) ) ;
26
+ return [ createCodeFixAction ( fixMissingProperties , changes , Diagnostics . Add_missing_properties , fixMissingProperties , Diagnostics . Add_all_missing_properties ) ] ;
27
+ }
22
28
if ( info . kind === InfoKind . Function ) {
23
29
const changes = textChanges . ChangeTracker . with ( context , t => addFunctionDeclaration ( t , context , info ) ) ;
24
30
return [ createCodeFixAction ( fixMissingFunctionDeclaration , changes , [ Diagnostics . Add_missing_function_declaration_0 , info . token . text ] , fixMissingFunctionDeclaration , Diagnostics . Add_all_missing_function_declarations ) ] ;
@@ -29,7 +35,7 @@ namespace ts.codefix {
29
35
}
30
36
return concatenate ( getActionsForMissingMethodDeclaration ( context , info ) , getActionsForMissingMemberDeclaration ( context , info ) ) ;
31
37
} ,
32
- fixIds : [ fixMissingMember , fixMissingFunctionDeclaration ] ,
38
+ fixIds : [ fixMissingMember , fixMissingFunctionDeclaration , fixMissingProperties ] ,
33
39
getAllCodeActions : context => {
34
40
const { program, fixId } = context ;
35
41
const checker = program . getTypeChecker ( ) ;
@@ -48,11 +54,15 @@ namespace ts.codefix {
48
54
addFunctionDeclaration ( changes , context , info ) ;
49
55
}
50
56
}
57
+ else if ( fixId === fixMissingProperties ) {
58
+ if ( info . kind === InfoKind . ObjectLiteral ) {
59
+ addObjectLiteralProperties ( changes , context , info ) ;
60
+ }
61
+ }
51
62
else {
52
63
if ( info . kind === InfoKind . Enum ) {
53
64
addEnumMemberDeclaration ( changes , checker , info ) ;
54
65
}
55
-
56
66
if ( info . kind === InfoKind . ClassOrInterface ) {
57
67
const { parentDeclaration, token } = info ;
58
68
const infos = getOrUpdate ( typeDeclToMembers , parentDeclaration , ( ) => [ ] ) ;
@@ -92,8 +102,8 @@ namespace ts.codefix {
92
102
} ,
93
103
} ) ;
94
104
95
- const enum InfoKind { Enum , ClassOrInterface , Function }
96
- type Info = EnumInfo | ClassOrInterfaceInfo | FunctionInfo ;
105
+ const enum InfoKind { Enum , ClassOrInterface , Function , ObjectLiteral }
106
+ type Info = EnumInfo | ClassOrInterfaceInfo | FunctionInfo | ObjectLiteralInfo ;
97
107
98
108
interface EnumInfo {
99
109
readonly kind : InfoKind . Enum ;
@@ -120,6 +130,13 @@ namespace ts.codefix {
120
130
readonly parentDeclaration : SourceFile | ModuleDeclaration ;
121
131
}
122
132
133
+ interface ObjectLiteralInfo {
134
+ readonly kind : InfoKind . ObjectLiteral ;
135
+ readonly token : Identifier ;
136
+ readonly properties : Symbol [ ] ;
137
+ readonly parentDeclaration : ObjectLiteralExpression ;
138
+ }
139
+
123
140
function getInfo ( sourceFile : SourceFile , tokenPos : number , checker : TypeChecker , program : Program ) : Info | undefined {
124
141
// The identifier of the missing property. eg:
125
142
// this.missing = 1;
@@ -130,6 +147,13 @@ namespace ts.codefix {
130
147
}
131
148
132
149
const { parent } = token ;
150
+ if ( isIdentifier ( token ) && hasInitializer ( parent ) && parent . initializer && isObjectLiteralExpression ( parent . initializer ) ) {
151
+ const properties = arrayFrom ( checker . getUnmatchedProperties ( checker . getTypeAtLocation ( parent . initializer ) , checker . getTypeAtLocation ( token ) , /* requireOptionalProperties */ false , /* matchDiscriminantProperties */ false ) ) ;
152
+ if ( length ( properties ) ) {
153
+ return { kind : InfoKind . ObjectLiteral , token, properties, parentDeclaration : parent . initializer } ;
154
+ }
155
+ }
156
+
133
157
if ( isIdentifier ( token ) && isCallExpression ( parent ) ) {
134
158
return { kind : InfoKind . Function , token, call : parent , sourceFile, modifierFlags : ModifierFlags . None , parentDeclaration : sourceFile } ;
135
159
}
@@ -248,7 +272,7 @@ namespace ts.codefix {
248
272
}
249
273
250
274
function initializePropertyToUndefined ( obj : Expression , propertyName : string ) {
251
- return factory . createExpressionStatement ( factory . createAssignment ( factory . createPropertyAccessExpression ( obj , propertyName ) , factory . createIdentifier ( "undefined" ) ) ) ;
275
+ return factory . createExpressionStatement ( factory . createAssignment ( factory . createPropertyAccessExpression ( obj , propertyName ) , createUndefined ( ) ) ) ;
252
276
}
253
277
254
278
function createActionsForAddMissingMemberInTypeScriptFile ( context : CodeFixContext , { parentDeclaration, declSourceFile, modifierFlags, token } : ClassOrInterfaceInfo ) : CodeFixAction [ ] | undefined {
@@ -405,4 +429,97 @@ namespace ts.codefix {
405
429
const functionDeclaration = createSignatureDeclarationFromCallExpression ( SyntaxKind . FunctionDeclaration , context , importAdder , info . call , idText ( info . token ) , info . modifierFlags , info . parentDeclaration ) as FunctionDeclaration ;
406
430
changes . insertNodeAtEndOfScope ( info . sourceFile , info . parentDeclaration , functionDeclaration ) ;
407
431
}
432
+
433
+ function addObjectLiteralProperties ( changes : textChanges . ChangeTracker , context : CodeFixContextBase , info : ObjectLiteralInfo ) {
434
+ const importAdder = createImportAdder ( context . sourceFile , context . program , context . preferences , context . host ) ;
435
+ const quotePreference = getQuotePreference ( context . sourceFile , context . preferences ) ;
436
+ const checker = context . program . getTypeChecker ( ) ;
437
+ const props = map ( info . properties , prop => {
438
+ const initializer = prop . valueDeclaration ? tryGetInitializerValueFromType ( context , checker , importAdder , quotePreference , checker . getTypeAtLocation ( prop . valueDeclaration ) ) : createUndefined ( ) ;
439
+ return factory . createPropertyAssignment ( prop . name , initializer ) ;
440
+ } ) ;
441
+ changes . replaceNode ( context . sourceFile , info . parentDeclaration , factory . createObjectLiteralExpression ( [ ...info . parentDeclaration . properties , ...props ] , /*multiLine*/ true ) ) ;
442
+ }
443
+
444
+ function tryGetInitializerValueFromType ( context : CodeFixContextBase , checker : TypeChecker , importAdder : ImportAdder , quotePreference : QuotePreference , type : Type ) : Expression {
445
+ if ( type . flags & TypeFlags . AnyOrUnknown ) {
446
+ return createUndefined ( ) ;
447
+ }
448
+ if ( type . flags & ( TypeFlags . String | TypeFlags . TemplateLiteral ) ) {
449
+ return factory . createStringLiteral ( "" , /* isSingleQuote */ quotePreference === QuotePreference . Single ) ;
450
+ }
451
+ if ( type . flags & TypeFlags . Number ) {
452
+ return factory . createNumericLiteral ( 0 ) ;
453
+ }
454
+ if ( type . flags & TypeFlags . BigInt ) {
455
+ return factory . createBigIntLiteral ( "0n" ) ;
456
+ }
457
+ if ( type . flags & TypeFlags . Boolean ) {
458
+ return factory . createFalse ( ) ;
459
+ }
460
+ if ( type . flags & TypeFlags . EnumLike ) {
461
+ const enumMember = type . symbol . exports ? firstOrUndefined ( arrayFrom ( type . symbol . exports . values ( ) ) ) : type . symbol ;
462
+ const name = checker . symbolToExpression ( type . symbol . parent ? type . symbol . parent : type . symbol , SymbolFlags . Value , /*enclosingDeclaration*/ undefined , /*flags*/ undefined ) ;
463
+ return enumMember === undefined || name === undefined ? factory . createNumericLiteral ( 0 ) : factory . createPropertyAccessExpression ( name , checker . symbolToString ( enumMember ) ) ;
464
+ }
465
+ if ( type . flags & TypeFlags . NumberLiteral ) {
466
+ return factory . createNumericLiteral ( ( type as NumberLiteralType ) . value ) ;
467
+ }
468
+ if ( type . flags & TypeFlags . BigIntLiteral ) {
469
+ return factory . createBigIntLiteral ( ( type as BigIntLiteralType ) . value ) ;
470
+ }
471
+ if ( type . flags & TypeFlags . StringLiteral ) {
472
+ return factory . createStringLiteral ( ( type as StringLiteralType ) . value , /* isSingleQuote */ quotePreference === QuotePreference . Single ) ;
473
+ }
474
+ if ( type . flags & TypeFlags . BooleanLiteral ) {
475
+ return ( type === checker . getFalseType ( ) || type === checker . getFalseType ( /*fresh*/ true ) ) ? factory . createFalse ( ) : factory . createTrue ( ) ;
476
+ }
477
+ if ( type . flags & TypeFlags . Null ) {
478
+ return factory . createNull ( ) ;
479
+ }
480
+ if ( type . flags & TypeFlags . Union ) {
481
+ const expression = firstDefined ( ( type as UnionType ) . types , t => tryGetInitializerValueFromType ( context , checker , importAdder , quotePreference , t ) ) ;
482
+ return expression ?? createUndefined ( ) ;
483
+ }
484
+ if ( checker . isArrayLikeType ( type ) ) {
485
+ return factory . createArrayLiteralExpression ( ) ;
486
+ }
487
+ if ( isObjectLiteralType ( type ) ) {
488
+ const props = map ( checker . getPropertiesOfType ( type ) , prop => {
489
+ const initializer = prop . valueDeclaration ? tryGetInitializerValueFromType ( context , checker , importAdder , quotePreference , checker . getTypeAtLocation ( prop . valueDeclaration ) ) : createUndefined ( ) ;
490
+ return factory . createPropertyAssignment ( prop . name , initializer ) ;
491
+ } ) ;
492
+ return factory . createObjectLiteralExpression ( props , /*multiLine*/ true ) ;
493
+ }
494
+ if ( getObjectFlags ( type ) & ObjectFlags . Anonymous ) {
495
+ const decl = find ( type . symbol . declarations || emptyArray , or ( isFunctionTypeNode , isMethodSignature , isMethodDeclaration ) ) ;
496
+ if ( decl === undefined ) return createUndefined ( ) ;
497
+
498
+ const signature = checker . getSignaturesOfType ( type , SignatureKind . Call ) ;
499
+ if ( signature === undefined ) return createUndefined ( ) ;
500
+
501
+ const func = createSignatureDeclarationFromSignature ( SyntaxKind . FunctionExpression , context , quotePreference , signature [ 0 ] ,
502
+ createStubbedBody ( Diagnostics . Function_not_implemented . message , quotePreference ) , /*name*/ undefined , /*modifiers*/ undefined , /*optional*/ undefined , /*enclosingDeclaration*/ undefined , importAdder ) as FunctionExpression | undefined ;
503
+ return func ?? createUndefined ( ) ;
504
+ }
505
+ if ( getObjectFlags ( type ) & ObjectFlags . Class ) {
506
+ const classDeclaration = getClassLikeDeclarationOfSymbol ( type . symbol ) ;
507
+ if ( classDeclaration === undefined || hasAbstractModifier ( classDeclaration ) ) return createUndefined ( ) ;
508
+
509
+ const constructorDeclaration = getFirstConstructorWithBody ( classDeclaration ) ;
510
+ if ( constructorDeclaration && length ( constructorDeclaration . parameters ) ) return createUndefined ( ) ;
511
+
512
+ return factory . createNewExpression ( factory . createIdentifier ( type . symbol . name ) , /*typeArguments*/ undefined , /*argumentsArray*/ undefined ) ;
513
+ }
514
+ return createUndefined ( ) ;
515
+ }
516
+
517
+ function createUndefined ( ) {
518
+ return factory . createIdentifier ( "undefined" ) ;
519
+ }
520
+
521
+ function isObjectLiteralType ( type : Type ) {
522
+ return ( type . flags & TypeFlags . Object ) &&
523
+ ( ( getObjectFlags ( type ) & ObjectFlags . ObjectLiteral ) || ( type . symbol && tryCast ( singleOrUndefined ( type . symbol . declarations ) , isTypeLiteralNode ) ) ) ;
524
+ }
408
525
}
0 commit comments