diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 8e5c03560db3e..2b0632f6cfb88 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -47821,18 +47821,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function computeEnumMemberValue(member: EnumMember, autoValue: number | undefined, previous: EnumMember | undefined): EvaluatorResult {
+ let errorReported = false;
if (isComputedNonLiteralName(member.name)) {
+ errorReported = true;
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
}
else if (isBigIntLiteral(member.name)) {
+ errorReported = true;
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
}
else {
const text = getTextOfPropertyName(member.name);
if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) {
+ errorReported = true;
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
}
}
+ if (!errorReported && isComputedPropertyName(member.name)) {
+ // Computed property name with a literal expression (e.g., ['key'] or [`key`])
+ // This is deprecated and will be disallowed in a future version
+ suggestionDiagnostics.add(
+ 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),
+ );
+ }
if (member.initializer) {
return computeConstantEnumMemberValue(member);
}
diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json
index c21fd11e5c7bf..fa37e5afb3b10 100644
--- a/src/compiler/diagnosticMessages.json
+++ b/src/compiler/diagnosticMessages.json
@@ -1861,7 +1861,11 @@
"category": "Message",
"code": 1549
},
-
+ "Using a string literal as an enum member name via a computed property is deprecated. Use a simple string literal instead.": {
+ "category": "Suggestion",
+ "code": 1550,
+ "reportsDeprecated": true
+ },
"The types of '{0}' are incompatible between these types.": {
"category": "Error",
"code": 2200
@@ -8348,7 +8352,14 @@
"category": "Message",
"code": 95197
},
-
+ "Remove unnecessary computed property name syntax": {
+ "category": "Message",
+ "code": 95198
+ },
+ "Remove all unnecessary computed property name syntax": {
+ "category": "Message",
+ "code": 95199
+ },
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
"code": 18004
diff --git a/src/services/_namespaces/ts.codefix.ts b/src/services/_namespaces/ts.codefix.ts
index 3cf05630388de..8b07510aa9ca3 100644
--- a/src/services/_namespaces/ts.codefix.ts
+++ b/src/services/_namespaces/ts.codefix.ts
@@ -74,3 +74,4 @@ export * from "../codefixes/splitTypeOnlyImport.js";
export * from "../codefixes/convertConstToLet.js";
export * from "../codefixes/fixExpectedComma.js";
export * from "../codefixes/fixAddVoidToPromise.js";
+export * from "../codefixes/convertComputedEnumMemberName.js";
diff --git a/src/services/codefixes/convertComputedEnumMemberName.ts b/src/services/codefixes/convertComputedEnumMemberName.ts
new file mode 100644
index 0000000000000..3d911f8c10426
--- /dev/null
+++ b/src/services/codefixes/convertComputedEnumMemberName.ts
@@ -0,0 +1,83 @@
+import {
+ createCodeFixActionMaybeFixAll,
+ createCombinedCodeActions,
+ eachDiagnostic,
+ registerCodeFix,
+} from "../_namespaces/ts.codefix.js";
+import {
+ ComputedPropertyName,
+ Diagnostics,
+ factory,
+ getTokenAtPosition,
+ isComputedPropertyName,
+ isEnumMember,
+ isNoSubstitutionTemplateLiteral,
+ isStringLiteral,
+ SourceFile,
+ textChanges,
+} from "../_namespaces/ts.js";
+
+const fixId = "convertComputedEnumMemberName";
+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];
+
+registerCodeFix({
+ errorCodes,
+ getCodeActions(context) {
+ const { sourceFile, span } = context;
+ const info = getInfo(sourceFile, span.start);
+ if (info === undefined) return;
+
+ const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, info));
+ return [createCodeFixActionMaybeFixAll(fixId, changes, Diagnostics.Remove_unnecessary_computed_property_name_syntax, fixId, Diagnostics.Remove_all_unnecessary_computed_property_name_syntax)];
+ },
+ getAllCodeActions(context) {
+ return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => {
+ eachDiagnostic(context, errorCodes, diag => {
+ const info = getInfo(diag.file, diag.start);
+ if (info) {
+ return doChange(changes, diag.file, info);
+ }
+ return undefined;
+ });
+ }));
+ },
+ fixIds: [fixId],
+});
+
+interface Info {
+ computedName: ComputedPropertyName;
+ literalText: string;
+}
+
+function getInfo(sourceFile: SourceFile, pos: number): Info | undefined {
+ const token = getTokenAtPosition(sourceFile, pos);
+
+ // Navigate to find the computed property name
+ let node = token;
+ while (node && !isComputedPropertyName(node)) {
+ node = node.parent;
+ }
+
+ if (!node || !isComputedPropertyName(node)) return undefined;
+ if (!isEnumMember(node.parent)) return undefined;
+
+ const expression = node.expression;
+ let literalText: string;
+
+ if (isStringLiteral(expression)) {
+ literalText = expression.text;
+ }
+ else if (isNoSubstitutionTemplateLiteral(expression)) {
+ literalText = expression.text;
+ }
+ else {
+ return undefined;
+ }
+
+ return { computedName: node, literalText };
+}
+
+function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, info: Info) {
+ // Replace ['\t'] with '\t' (or ["key"] with "key")
+ changes.replaceNode(sourceFile, info.computedName, factory.createStringLiteral(info.literalText));
+}
diff --git a/tests/cases/fourslash/codeFixEnumComputedPropertyName.ts b/tests/cases/fourslash/codeFixEnumComputedPropertyName.ts
new file mode 100644
index 0000000000000..879efab64be39
--- /dev/null
+++ b/tests/cases/fourslash/codeFixEnumComputedPropertyName.ts
@@ -0,0 +1,17 @@
+///
+
+// @Filename: test.ts
+////enum CHAR {
+//// [|['\t']|] = 0x09,
+//// ['\n'] = 0x0A,
+////}
+
+goTo.file("test.ts");
+verify.codeFix({
+ description: "Remove unnecessary computed property name syntax",
+ newFileContent: `enum CHAR {
+ "\\t" = 0x09,
+ ['\\n'] = 0x0A,
+}`,
+ index: 0,
+});
diff --git a/tests/cases/fourslash/codeFixEnumComputedPropertyNameAll.ts b/tests/cases/fourslash/codeFixEnumComputedPropertyNameAll.ts
new file mode 100644
index 0000000000000..622cf931a68d1
--- /dev/null
+++ b/tests/cases/fourslash/codeFixEnumComputedPropertyNameAll.ts
@@ -0,0 +1,19 @@
+///
+
+// @Filename: test.ts
+////enum CHAR {
+//// ['\t'] = 0x09,
+//// ['\n'] = 0x0A,
+//// [`\r`] = 0x0D,
+////}
+
+goTo.file("test.ts");
+verify.codeFixAll({
+ fixId: "convertComputedEnumMemberName",
+ fixAllDescription: "Remove all unnecessary computed property name syntax",
+ newFileContent: `enum CHAR {
+ "\\t" = 0x09,
+ "\\n" = 0x0A,
+ "\\r" = 0x0D,
+}`,
+});
diff --git a/tests/cases/fourslash/enumComputedPropertyNameDeprecated.ts b/tests/cases/fourslash/enumComputedPropertyNameDeprecated.ts
new file mode 100644
index 0000000000000..3a06bcd722abe
--- /dev/null
+++ b/tests/cases/fourslash/enumComputedPropertyNameDeprecated.ts
@@ -0,0 +1,23 @@
+///
+// @Filename: a.ts
+////enum CHAR {
+//// [|['\t']|] = 0x09,
+//// [|['\n']|] = 0x0A,
+//// [|[`\r`]|] = 0x0D,
+//// 'space' = 0x20, // no warning for simple string literal
+////}
+////
+////enum NoWarning {
+//// A = 1,
+//// B = 2,
+//// "quoted" = 3,
+////}
+
+goTo.file("a.ts")
+const diagnostics = test.ranges().map(range => ({
+ code: 1550,
+ message: "Using a string literal as an enum member name via a computed property is deprecated. Use a simple string literal instead.",
+ reportsDeprecated: true,
+ range,
+}));
+verify.getSuggestionDiagnostics(diagnostics)
diff --git a/tests/cases/fourslash/enumComputedPropertyNameError.ts b/tests/cases/fourslash/enumComputedPropertyNameError.ts
new file mode 100644
index 0000000000000..e3b1e962e2efe
--- /dev/null
+++ b/tests/cases/fourslash/enumComputedPropertyNameError.ts
@@ -0,0 +1,15 @@
+///
+// @Filename: a.ts
+////const key = "dynamic";
+////enum Test {
+//// [|[key]|] = 1, // error: non-literal computed property name
+//// [|["a" + "b"]|] = 2, // error: binary expression
+////}
+
+goTo.file("a.ts")
+const diagnostics = test.ranges().map(range => ({
+ code: 1164,
+ message: "Computed property names are not allowed in enums.",
+ range,
+}));
+verify.getSemanticDiagnostics(diagnostics)