Skip to content

Commit c27dace

Browse files
authored
Merge pull request microsoft#25182 from Kingwl/fix-missing-enum-member
add quick fix for add missing enum member
2 parents 8530fe0 + d8cdd75 commit c27dace

15 files changed

+298
-22
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4446,5 +4446,13 @@
44464446
"Convert named export to default export": {
44474447
"category": "Message",
44484448
"code": 95062
4449+
},
4450+
"Add missing enum member '{0}'": {
4451+
"category": "Message",
4452+
"code": 95063
4453+
},
4454+
"Add all missing enum members": {
4455+
"category": "Message",
4456+
"code": 95064
44494457
}
44504458
}

src/services/codefixes/fixAddMissingMember.ts

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ namespace ts.codefix {
1111
getCodeActions(context) {
1212
const info = getInfo(context.sourceFile, context.span.start, context.program.getTypeChecker());
1313
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+
}
1420
const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
1521
const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs, context.preferences);
1622
const addMember = inJs ?
@@ -23,31 +29,41 @@ namespace ts.codefix {
2329
const seenNames = createMap<true>();
2430
return codeFixAll(context, errorCodes, (changes, diag) => {
2531
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)) {
3035
return;
3136
}
3237

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);
3641
}
3742
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);
4047
}
4148
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+
}
4456
}
4557
}
4658
});
4759
},
4860
});
4961

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+
5167
function getInfo(tokenSourceFile: SourceFile, tokenPos: number, checker: TypeChecker): Info | undefined {
5268
// The identifier of the missing property. eg:
5369
// this.missing = 1;
@@ -62,15 +78,21 @@ namespace ts.codefix {
6278

6379
const leftExpressionType = skipConstraint(checker.getTypeAtLocation(parent.expression)!);
6480
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;
7496
}
7597

7698
function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined {
@@ -209,4 +231,25 @@ namespace ts.codefix {
209231
changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, methodDeclaration);
210232
}
211233
}
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+
}
212255
}

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5946,6 +5946,8 @@ declare namespace ts {
59465946
Remove_braces_from_arrow_function: DiagnosticMessage;
59475947
Convert_default_export_to_named_export: DiagnosticMessage;
59485948
Convert_named_export_to_default_export: DiagnosticMessage;
5949+
Add_missing_enum_member_0: DiagnosticMessage;
5950+
Add_all_missing_enum_members: DiagnosticMessage;
59495951
};
59505952
}
59515953
declare namespace ts {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////enum E {
4+
//// a
5+
////}
6+
////E.b
7+
8+
verify.codeFix({
9+
description: "Add missing enum member 'b'",
10+
newFileContent: `enum E {
11+
a,
12+
b
13+
}
14+
E.b`
15+
});
16+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////enum E {
4+
//// a,
5+
//// b = 1,
6+
//// c = "123"
7+
////}
8+
////enum A {
9+
//// a = E.c
10+
////}
11+
////A.b
12+
13+
verify.codeFix({
14+
description: "Add missing enum member 'b'",
15+
newFileContent: `enum E {
16+
a,
17+
b = 1,
18+
c = "123"
19+
}
20+
enum A {
21+
a = E.c,
22+
b
23+
}
24+
A.b`
25+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////enum E {
4+
//// a,
5+
//// b = 1,
6+
//// c = "123"
7+
////}
8+
////enum A {
9+
//// a = E.c
10+
////}
11+
////enum B {
12+
//// b = A.a
13+
////}
14+
////B.c
15+
16+
verify.codeFix({
17+
description: "Add missing enum member 'c'",
18+
newFileContent: `enum E {
19+
a,
20+
b = 1,
21+
c = "123"
22+
}
23+
enum A {
24+
a = E.c
25+
}
26+
enum B {
27+
b = A.a,
28+
c
29+
}
30+
B.c`
31+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////enum E {
4+
//// a = 1
5+
////}
6+
////E.b
7+
8+
verify.codeFix({
9+
description: "Add missing enum member 'b'",
10+
newFileContent: `enum E {
11+
a = 1,
12+
b
13+
}
14+
E.b`
15+
});
16+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////enum E {
4+
//// a,
5+
//// b = 1,
6+
//// c
7+
////}
8+
////E.d
9+
10+
verify.codeFix({
11+
description: "Add missing enum member 'd'",
12+
newFileContent: `enum E {
13+
a,
14+
b = 1,
15+
c,
16+
d
17+
}
18+
E.d`
19+
});
20+
21+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////enum E {
4+
//// a = "a",
5+
////}
6+
////E.b
7+
8+
verify.codeFix({
9+
description: "Add missing enum member 'b'",
10+
newFileContent: `enum E {
11+
a = "a",
12+
b = "b"
13+
}
14+
E.b`
15+
});
16+
17+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////enum E {
4+
//// a = "a" + "-",
5+
////}
6+
////E.b
7+
8+
verify.codeFix({
9+
description: "Add missing enum member 'b'",
10+
newFileContent: `enum E {
11+
a = "a" + "-",
12+
b = "b"
13+
}
14+
E.b`
15+
});
16+
17+

0 commit comments

Comments
 (0)