Skip to content

Commit 708b4a7

Browse files
committed
Add deprecation warning for computed property names in enums
This adds a suggestion diagnostic for using computed property names with literal expressions in enum members (e.g., `["key"]`). This syntax is rarely used, not supported by Babel/typescript-eslint/SWC, and will be disallowed in a future version (typescript-go). Changes: - Add suggestion diagnostic (code 1550) for computed property names - Add quick fix to convert `["key"]` to `"key"` - Add "Fix All" support for batch conversion
1 parent 669c25c commit 708b4a7

File tree

8 files changed

+178
-2
lines changed

8 files changed

+178
-2
lines changed

src/compiler/checker.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47833,6 +47833,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4783347833
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
4783447834
}
4783547835
}
47836+
if (isComputedPropertyName(member.name)) {
47837+
// Computed property name with a literal expression (e.g., ['key'] or [`key`])
47838+
// This is deprecated and will be disallowed in a future version
47839+
suggestionDiagnostics.add(
47840+
createDiagnosticForNode(member.name, Diagnostics.Using_a_string_literal_as_an_enum_member_name_via_a_computed_property_is_deprecated_Use_a_simple_string_literal_instead),
47841+
);
47842+
}
4783647843
if (member.initializer) {
4783747844
return computeConstantEnumMemberValue(member);
4783847845
}

src/compiler/diagnosticMessages.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,7 +1861,11 @@
18611861
"category": "Message",
18621862
"code": 1549
18631863
},
1864-
1864+
"Using a string literal as an enum member name via a computed property is deprecated. Use a simple string literal instead.": {
1865+
"category": "Suggestion",
1866+
"code": 1550,
1867+
"reportsDeprecated": true
1868+
},
18651869
"The types of '{0}' are incompatible between these types.": {
18661870
"category": "Error",
18671871
"code": 2200
@@ -8348,7 +8352,14 @@
83488352
"category": "Message",
83498353
"code": 95197
83508354
},
8351-
8355+
"Remove unnecessary computed property name syntax": {
8356+
"category": "Message",
8357+
"code": 95198
8358+
},
8359+
"Remove all unnecessary computed property name syntax": {
8360+
"category": "Message",
8361+
"code": 95199
8362+
},
83528363
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
83538364
"category": "Error",
83548365
"code": 18004

src/services/_namespaces/ts.codefix.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,4 @@ export * from "../codefixes/splitTypeOnlyImport.js";
7474
export * from "../codefixes/convertConstToLet.js";
7575
export * from "../codefixes/fixExpectedComma.js";
7676
export * from "../codefixes/fixAddVoidToPromise.js";
77+
export * from "../codefixes/convertComputedEnumMemberName.js";
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {
2+
createCodeFixActionMaybeFixAll,
3+
createCombinedCodeActions,
4+
eachDiagnostic,
5+
registerCodeFix,
6+
} from "../_namespaces/ts.codefix.js";
7+
import {
8+
ComputedPropertyName,
9+
Diagnostics,
10+
factory,
11+
getTokenAtPosition,
12+
isComputedPropertyName,
13+
isEnumMember,
14+
isNoSubstitutionTemplateLiteral,
15+
isStringLiteral,
16+
SourceFile,
17+
textChanges,
18+
} from "../_namespaces/ts.js";
19+
20+
const fixId = "convertComputedEnumMemberName";
21+
const errorCodes = [Diagnostics.Using_a_string_literal_as_an_enum_member_name_via_a_computed_property_is_deprecated_Use_a_simple_string_literal_instead.code];
22+
23+
registerCodeFix({
24+
errorCodes,
25+
getCodeActions(context) {
26+
const { sourceFile, span } = context;
27+
const info = getInfo(sourceFile, span.start);
28+
if (info === undefined) return;
29+
30+
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, info));
31+
return [createCodeFixActionMaybeFixAll(fixId, changes, Diagnostics.Remove_unnecessary_computed_property_name_syntax, fixId, Diagnostics.Remove_all_unnecessary_computed_property_name_syntax)];
32+
},
33+
getAllCodeActions(context) {
34+
return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => {
35+
eachDiagnostic(context, errorCodes, diag => {
36+
const info = getInfo(diag.file, diag.start);
37+
if (info) {
38+
return doChange(changes, diag.file, info);
39+
}
40+
return undefined;
41+
});
42+
}));
43+
},
44+
fixIds: [fixId],
45+
});
46+
47+
interface Info {
48+
computedName: ComputedPropertyName;
49+
literalText: string;
50+
}
51+
52+
function getInfo(sourceFile: SourceFile, pos: number): Info | undefined {
53+
const token = getTokenAtPosition(sourceFile, pos);
54+
55+
// Navigate to find the computed property name
56+
let node = token;
57+
while (node && !isComputedPropertyName(node)) {
58+
node = node.parent;
59+
}
60+
61+
if (!node || !isComputedPropertyName(node)) return undefined;
62+
if (!isEnumMember(node.parent)) return undefined;
63+
64+
const expression = node.expression;
65+
let literalText: string;
66+
67+
if (isStringLiteral(expression)) {
68+
literalText = expression.text;
69+
}
70+
else if (isNoSubstitutionTemplateLiteral(expression)) {
71+
literalText = expression.text;
72+
}
73+
else {
74+
return undefined;
75+
}
76+
77+
return { computedName: node, literalText };
78+
}
79+
80+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, info: Info) {
81+
// Replace ['\t'] with '\t' (or ["key"] with "key")
82+
changes.replaceNode(sourceFile, info.computedName, factory.createStringLiteral(info.literalText));
83+
}
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+
// @Filename: test.ts
4+
////enum CHAR {
5+
//// [|['\t']|] = 0x09,
6+
//// ['\n'] = 0x0A,
7+
////}
8+
9+
goTo.file("test.ts");
10+
verify.codeFix({
11+
description: "Remove unnecessary computed property name syntax",
12+
newFileContent: `enum CHAR {
13+
"\\t" = 0x09,
14+
['\\n'] = 0x0A,
15+
}`,
16+
index: 0,
17+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @Filename: test.ts
4+
////enum CHAR {
5+
//// ['\t'] = 0x09,
6+
//// ['\n'] = 0x0A,
7+
//// [`\r`] = 0x0D,
8+
////}
9+
10+
goTo.file("test.ts");
11+
verify.codeFixAll({
12+
fixId: "convertComputedEnumMemberName",
13+
fixAllDescription: "Remove all unnecessary computed property name syntax",
14+
newFileContent: `enum CHAR {
15+
"\\t" = 0x09,
16+
"\\n" = 0x0A,
17+
"\\r" = 0x0D,
18+
}`,
19+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
///<reference path="fourslash.ts" />
2+
// @Filename: a.ts
3+
////enum CHAR {
4+
//// [|['\t']|] = 0x09,
5+
//// [|['\n']|] = 0x0A,
6+
//// [|[`\r`]|] = 0x0D,
7+
//// 'space' = 0x20, // no warning for simple string literal
8+
////}
9+
////
10+
////enum NoWarning {
11+
//// A = 1,
12+
//// B = 2,
13+
//// "quoted" = 3,
14+
////}
15+
16+
goTo.file("a.ts")
17+
const diagnostics = test.ranges().map(range => ({
18+
code: 1550,
19+
message: "Using a string literal as an enum member name via a computed property is deprecated. Use a simple string literal instead.",
20+
reportsDeprecated: true,
21+
range,
22+
}));
23+
verify.getSuggestionDiagnostics(diagnostics)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
///<reference path="fourslash.ts" />
2+
// @Filename: a.ts
3+
////const key = "dynamic";
4+
////enum Test {
5+
//// [|[key]|] = 1, // error: non-literal computed property name
6+
//// [|["a" + "b"]|] = 2, // error: binary expression
7+
////}
8+
9+
goTo.file("a.ts")
10+
const diagnostics = test.ranges().map(range => ({
11+
code: 1164,
12+
message: "Computed property names are not allowed in enums.",
13+
range,
14+
}));
15+
verify.getSemanticDiagnostics(diagnostics)

0 commit comments

Comments
 (0)