Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47833,6 +47833,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
}
}
if (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);
}
Expand Down
15 changes: 13 additions & 2 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/services/_namespaces/ts.codefix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
83 changes: 83 additions & 0 deletions src/services/codefixes/convertComputedEnumMemberName.ts
Original file line number Diff line number Diff line change
@@ -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));
}
17 changes: 17 additions & 0 deletions tests/cases/fourslash/codeFixEnumComputedPropertyName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// <reference path='fourslash.ts' />

// @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,
});
19 changes: 19 additions & 0 deletions tests/cases/fourslash/codeFixEnumComputedPropertyNameAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// <reference path='fourslash.ts' />

// @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,
}`,
});
23 changes: 23 additions & 0 deletions tests/cases/fourslash/enumComputedPropertyNameDeprecated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
///<reference path="fourslash.ts" />
// @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)
15 changes: 15 additions & 0 deletions tests/cases/fourslash/enumComputedPropertyNameError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
///<reference path="fourslash.ts" />
// @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)
Loading