@@ -11,6 +11,12 @@ namespace ts.codefix {
11
11
getCodeActions ( context ) {
12
12
const info = getInfo ( context . sourceFile , context . span . start , context . program . getTypeChecker ( ) ) ;
13
13
if ( ! info ) return undefined ;
14
+
15
+ if ( info . kind === InfoKind . enum ) {
16
+ const { token, enumDeclaration } = info ;
17
+ const changes = textChanges . ChangeTracker . with ( context , t => addEnumMemberDeclaration ( t , context . program . getTypeChecker ( ) , token , enumDeclaration ) ) ;
18
+ return singleElementArray ( createCodeFixAction ( fixName , changes , [ Diagnostics . Add_missing_enum_member_0 , token . text ] , fixId , Diagnostics . Add_all_missing_enum_members ) ) ;
19
+ }
14
20
const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info ;
15
21
const methodCodeAction = call && getActionForMethodDeclaration ( context , classDeclarationSourceFile , classDeclaration , token , call , makeStatic , inJs , context . preferences ) ;
16
22
const addMember = inJs ?
@@ -23,31 +29,41 @@ namespace ts.codefix {
23
29
const seenNames = createMap < true > ( ) ;
24
30
return codeFixAll ( context , errorCodes , ( changes , diag ) => {
25
31
const { program, preferences } = context ;
26
- const info = getInfo ( diag . file , diag . start , program . getTypeChecker ( ) ) ;
27
- if ( ! info ) return ;
28
- const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info ;
29
- if ( ! addToSeen ( seenNames , token . text ) ) {
32
+ const checker = program . getTypeChecker ( ) ;
33
+ const info = getInfo ( diag . file , diag . start , checker ) ;
34
+ if ( ! info || ! addToSeen ( seenNames , info . token . text ) ) {
30
35
return ;
31
36
}
32
37
33
- // Always prefer to add a method declaration if possible.
34
- if ( call ) {
35
- addMethodDeclaration ( context , changes , classDeclarationSourceFile , classDeclaration , token , call , makeStatic , inJs , preferences ) ;
38
+ if ( info . kind === InfoKind . enum ) {
39
+ const { token , enumDeclaration } = info ;
40
+ addEnumMemberDeclaration ( changes , checker , token , enumDeclaration ) ;
36
41
}
37
42
else {
38
- if ( inJs ) {
39
- addMissingMemberInJs ( changes , classDeclarationSourceFile , classDeclaration , token . text , makeStatic ) ;
43
+ const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info ;
44
+ // Always prefer to add a method declaration if possible.
45
+ if ( call ) {
46
+ addMethodDeclaration ( context , changes , classDeclarationSourceFile , classDeclaration , token , call , makeStatic , inJs , preferences ) ;
40
47
}
41
48
else {
42
- const typeNode = getTypeNode ( program . getTypeChecker ( ) , classDeclaration , token ) ;
43
- addPropertyDeclaration ( changes , classDeclarationSourceFile , classDeclaration , token . text , typeNode , makeStatic ) ;
49
+ if ( inJs ) {
50
+ addMissingMemberInJs ( changes , classDeclarationSourceFile , classDeclaration , token . text , makeStatic ) ;
51
+ }
52
+ else {
53
+ const typeNode = getTypeNode ( program . getTypeChecker ( ) , classDeclaration , token ) ;
54
+ addPropertyDeclaration ( changes , classDeclarationSourceFile , classDeclaration , token . text , typeNode , makeStatic ) ;
55
+ }
44
56
}
45
57
}
46
58
} ) ;
47
59
} ,
48
60
} ) ;
49
61
50
- interface Info { token : Identifier ; classDeclaration : ClassLikeDeclaration ; makeStatic : boolean ; classDeclarationSourceFile : SourceFile ; inJs : boolean ; call : CallExpression | undefined ; }
62
+ enum InfoKind { enum , class }
63
+ interface EnumInfo { kind : InfoKind . enum ; token : Identifier ; enumDeclaration : EnumDeclaration ; }
64
+ interface ClassInfo { kind : InfoKind . class ; token : Identifier ; classDeclaration : ClassLikeDeclaration ; makeStatic : boolean ; classDeclarationSourceFile : SourceFile ; inJs : boolean ; call : CallExpression | undefined ; }
65
+ type Info = EnumInfo | ClassInfo ;
66
+
51
67
function getInfo ( tokenSourceFile : SourceFile , tokenPos : number , checker : TypeChecker ) : Info | undefined {
52
68
// The identifier of the missing property. eg:
53
69
// this.missing = 1;
@@ -62,15 +78,21 @@ namespace ts.codefix {
62
78
63
79
const leftExpressionType = skipConstraint ( checker . getTypeAtLocation ( parent . expression ) ! ) ;
64
80
const { symbol } = leftExpressionType ;
65
- const classDeclaration = symbol && symbol . declarations && find ( symbol . declarations , isClassLike ) ;
66
- if ( ! classDeclaration ) return undefined ;
67
-
68
- const makeStatic = ( leftExpressionType as TypeReference ) . target !== checker . getDeclaredTypeOfSymbol ( symbol ) ;
69
- const classDeclarationSourceFile = classDeclaration . getSourceFile ( ) ;
70
- const inJs = isSourceFileJavaScript ( classDeclarationSourceFile ) ;
71
- const call = tryCast ( parent . parent , isCallExpression ) ;
72
-
73
- return { token, classDeclaration, makeStatic, classDeclarationSourceFile, inJs, call } ;
81
+ if ( ! symbol || ! symbol . declarations ) return undefined ;
82
+
83
+ const classDeclaration = find ( symbol . declarations , isClassLike ) ;
84
+ if ( classDeclaration ) {
85
+ const makeStatic = ( leftExpressionType as TypeReference ) . target !== checker . getDeclaredTypeOfSymbol ( symbol ) ;
86
+ const classDeclarationSourceFile = classDeclaration . getSourceFile ( ) ;
87
+ const inJs = isSourceFileJavaScript ( classDeclarationSourceFile ) ;
88
+ const call = tryCast ( parent . parent , isCallExpression ) ;
89
+ return { kind : InfoKind . class , token, classDeclaration, makeStatic, classDeclarationSourceFile, inJs, call } ;
90
+ }
91
+ const enumDeclaration = find ( symbol . declarations , isEnumDeclaration ) ;
92
+ if ( enumDeclaration ) {
93
+ return { kind : InfoKind . enum , token, enumDeclaration } ;
94
+ }
95
+ return undefined ;
74
96
}
75
97
76
98
function getActionsForAddMissingMemberInJavaScriptFile ( context : CodeFixContext , classDeclarationSourceFile : SourceFile , classDeclaration : ClassLikeDeclaration , tokenName : string , makeStatic : boolean ) : CodeFixAction | undefined {
@@ -209,4 +231,25 @@ namespace ts.codefix {
209
231
changeTracker . insertNodeAtClassStart ( classDeclarationSourceFile , classDeclaration , methodDeclaration ) ;
210
232
}
211
233
}
234
+
235
+ function addEnumMemberDeclaration ( changes : textChanges . ChangeTracker , checker : TypeChecker , token : Identifier , enumDeclaration : EnumDeclaration ) {
236
+ /**
237
+ * create initializer only literal enum that has string initializer.
238
+ * value of initializer is a string literal that equal to name of enum member.
239
+ * numeric enum or empty enum will not create initializer.
240
+ */
241
+ const hasStringInitializer = some ( enumDeclaration . members , member => {
242
+ const type = checker . getTypeAtLocation ( member ) ;
243
+ return ! ! ( type && type . flags & TypeFlags . StringLike ) ;
244
+ } ) ;
245
+
246
+ const enumMember = createEnumMember ( token , hasStringInitializer ? createStringLiteral ( token . text ) : undefined ) ;
247
+ changes . replaceNode ( enumDeclaration . getSourceFile ( ) , enumDeclaration , updateEnumDeclaration (
248
+ enumDeclaration ,
249
+ enumDeclaration . decorators ,
250
+ enumDeclaration . modifiers ,
251
+ enumDeclaration . name ,
252
+ concatenate ( enumDeclaration . members , singleElementArray ( enumMember ) )
253
+ ) ) ;
254
+ }
212
255
}
0 commit comments