Skip to content

Commit d5268c8

Browse files
committed
add quick fix for add missing enum member
1 parent 22d33d2 commit d5268c8

11 files changed

Lines changed: 217 additions & 25 deletions

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4418,5 +4418,13 @@
44184418
"Remove braces from arrow function": {
44194419
"category": "Message",
44204420
"code": 95060
4421+
},
4422+
"Add missing enum member '{0}'": {
4423+
"category": "Message",
4424+
"code": 95061
4425+
},
4426+
"Add all missing enum members": {
4427+
"category": "Message",
4428+
"code": 95062
44214429
}
44224430
}

src/services/codefixes/fixAddMissingMember.ts

Lines changed: 85 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,43 +11,60 @@ namespace ts.codefix {
1111
getCodeActions(context) {
1212
const info = getInfo(context.sourceFile, context.span.start, context.program.getTypeChecker());
1313
if (!info) return undefined;
14-
const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
15-
const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs, context.preferences);
14+
15+
if (isEnumInfo(info)) {
16+
return singleElementArray(getActionForEnumMemberDeclaration(context, info.enumDeclarationSourceFile, info.declaration, info.token));
17+
}
18+
const { declaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
19+
const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, declaration, token, call, makeStatic, inJs, context.preferences);
1620
const addMember = inJs ?
17-
singleElementArray(getActionsForAddMissingMemberInJavaScriptFile(context, classDeclarationSourceFile, classDeclaration, token.text, makeStatic)) :
18-
getActionsForAddMissingMemberInTypeScriptFile(context, classDeclarationSourceFile, classDeclaration, token, makeStatic);
21+
singleElementArray(getActionsForAddMissingMemberInJavaScriptFile(context, classDeclarationSourceFile, declaration, token.text, makeStatic)) :
22+
getActionsForAddMissingMemberInTypeScriptFile(context, classDeclarationSourceFile, declaration, token, makeStatic);
1923
return concatenate(singleElementArray(methodCodeAction), addMember);
2024
},
2125
fixIds: [fixId],
2226
getAllCodeActions: context => {
2327
const seenNames = createMap<true>();
2428
return codeFixAll(context, errorCodes, (changes, diag) => {
2529
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)) {
30+
const checker = program.getTypeChecker();
31+
const info = getInfo(diag.file, diag.start, checker);
32+
if (!info || !addToSeen(seenNames, info.token.text)) {
3033
return;
3134
}
3235

33-
// Always prefer to add a method declaration if possible.
34-
if (call) {
35-
addMethodDeclaration(context, changes, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs, preferences);
36+
if (isEnumInfo(info)) {
37+
const { token, declaration, enumDeclarationSourceFile } = info;
38+
addEnumMemberDeclaration(changes, checker, token, declaration, enumDeclarationSourceFile);
3639
}
3740
else {
38-
if (inJs) {
39-
addMissingMemberInJs(changes, classDeclarationSourceFile, classDeclaration, token.text, makeStatic);
41+
const { declaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
42+
// Always prefer to add a method declaration if possible.
43+
if (call) {
44+
addMethodDeclaration(context, changes, classDeclarationSourceFile, declaration, token, call, makeStatic, inJs, preferences);
4045
}
4146
else {
42-
const typeNode = getTypeNode(program.getTypeChecker(), classDeclaration, token);
43-
addPropertyDeclaration(changes, classDeclarationSourceFile, classDeclaration, token.text, typeNode, makeStatic);
47+
if (inJs) {
48+
addMissingMemberInJs(changes, classDeclarationSourceFile, declaration, token.text, makeStatic);
49+
}
50+
else {
51+
const typeNode = getTypeNode(program.getTypeChecker(), declaration, token);
52+
addPropertyDeclaration(changes, classDeclarationSourceFile, declaration, token.text, typeNode, makeStatic);
53+
}
4454
}
4555
}
4656
});
4757
},
4858
});
4959

50-
interface Info { token: Identifier; classDeclaration: ClassLikeDeclaration; makeStatic: boolean; classDeclarationSourceFile: SourceFile; inJs: boolean; call: CallExpression | undefined; }
60+
interface EnumInfo { token: Identifier; declaration: EnumDeclaration; enumDeclarationSourceFile: SourceFile; }
61+
interface ClassInfo { token: Identifier; declaration: ClassLikeDeclaration; makeStatic: boolean; classDeclarationSourceFile: SourceFile; inJs: boolean; call: CallExpression | undefined; }
62+
type Info = EnumInfo | ClassInfo;
63+
64+
function isEnumInfo (info: Info): info is EnumInfo {
65+
return isEnumDeclaration(info.declaration);
66+
}
67+
5168
function getInfo(tokenSourceFile: SourceFile, tokenPos: number, checker: TypeChecker): Info | undefined {
5269
// The identifier of the missing property. eg:
5370
// this.missing = 1;
@@ -62,15 +79,21 @@ namespace ts.codefix {
6279

6380
const leftExpressionType = skipConstraint(checker.getTypeAtLocation(parent.expression)!);
6481
const { symbol } = leftExpressionType;
65-
const classDeclaration = symbol && symbol.declarations && find(symbol.declarations, isClassLike);
66-
if (!classDeclaration) return undefined;
82+
if (!symbol || !symbol.declarations) return undefined;
6783

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 };
84+
const classDeclaration = find(symbol.declarations, isClassLike);
85+
if (classDeclaration) {
86+
const makeStatic = (leftExpressionType as TypeReference).target !== checker.getDeclaredTypeOfSymbol(symbol);
87+
const classDeclarationSourceFile = classDeclaration.getSourceFile();
88+
const inJs = isSourceFileJavaScript(classDeclarationSourceFile);
89+
const call = tryCast(parent.parent, isCallExpression);
90+
return { token, declaration: classDeclaration, makeStatic, classDeclarationSourceFile, inJs, call };
91+
}
92+
const enumDeclaration = find(symbol.declarations, isEnumDeclaration);
93+
if (enumDeclaration) {
94+
return { token, declaration: enumDeclaration, enumDeclarationSourceFile: enumDeclaration.getSourceFile() };
95+
}
96+
return undefined;
7497
}
7598

7699
function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined {
@@ -188,6 +211,16 @@ namespace ts.codefix {
188211
return createCodeFixAction(fixName, changes, [makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0, token.text], fixId, Diagnostics.Add_all_missing_members);
189212
}
190213

214+
function getActionForEnumMemberDeclaration(
215+
context: CodeFixContext,
216+
enumDeclarationSourceFile: SourceFile,
217+
enumDeclaration: EnumDeclaration,
218+
token: Identifier
219+
): CodeFixAction | undefined {
220+
const changes = textChanges.ChangeTracker.with(context, t => addEnumMemberDeclaration(t, context.program.getTypeChecker(), token, enumDeclaration, enumDeclarationSourceFile));
221+
return createCodeFixAction(fixName, changes, [Diagnostics.Add_missing_enum_member_0, token.text], fixId, Diagnostics.Add_all_missing_enum_members);
222+
}
223+
191224
function addMethodDeclaration(
192225
context: CodeFixContextBase,
193226
changeTracker: textChanges.ChangeTracker,
@@ -209,4 +242,32 @@ namespace ts.codefix {
209242
changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, methodDeclaration);
210243
}
211244
}
245+
246+
function createEnumMemberFromEnumDeclaration(checker: TypeChecker, token: Identifier, enumDeclaration: EnumDeclaration) {
247+
/**
248+
* create initializer only string enum.
249+
* value of initializer is a string literal that equal to name of enum member.
250+
* literal enum or empty enum will not create initializer.
251+
*/
252+
const firstMember = firstOrUndefined(enumDeclaration.members);
253+
let enumMemberInitializer: Expression | undefined;
254+
if (firstMember && firstMember.initializer) {
255+
const memberType = checker.getTypeAtLocation(firstMember.initializer);
256+
if (memberType && memberType.flags & TypeFlags.StringLike) {
257+
enumMemberInitializer = createStringLiteral(token.text);
258+
}
259+
}
260+
return createEnumMember(token, enumMemberInitializer);
261+
}
262+
263+
function addEnumMemberDeclaration(changes: textChanges.ChangeTracker, checker: TypeChecker, token: Identifier, enumDeclaration: EnumDeclaration, file: SourceFile) {
264+
const enumMember = createEnumMemberFromEnumDeclaration(checker, token, enumDeclaration);
265+
changes.replaceNode(file, enumDeclaration, updateEnumDeclaration(
266+
enumDeclaration,
267+
enumDeclaration.decorators,
268+
enumDeclaration.modifiers,
269+
enumDeclaration.name,
270+
concatenate(enumDeclaration.members, singleElementArray(enumMember))
271+
));
272+
}
212273
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5914,6 +5914,8 @@ declare namespace ts {
59145914
Add_or_remove_braces_in_an_arrow_function: DiagnosticMessage;
59155915
Add_braces_to_arrow_function: DiagnosticMessage;
59165916
Remove_braces_from_arrow_function: DiagnosticMessage;
5917+
Add_missing_enum_member_0: DiagnosticMessage;
5918+
Add_all_missing_enum_members: DiagnosticMessage;
59175919
};
59185920
}
59195921
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: 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+
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 = "b"
5+
////}
6+
////E.b
7+
8+
verify.codeFix({
9+
description: "Add missing enum member 'b'",
10+
newFileContent: `enum E {
11+
a = "b",
12+
b = "b"
13+
}
14+
E.b`
15+
});
16+
17+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////enum E {
4+
////}
5+
////E.a
6+
7+
verify.codeFix({
8+
description: "Add missing enum member 'a'",
9+
newFileContent: `enum E {
10+
a
11+
}
12+
E.a`
13+
});
14+
15+

0 commit comments

Comments
 (0)