Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13716,7 +13716,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return false;
}
const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression;
return isEntityNameExpression(expr);
// Allow entity name expressions (e.g., Type.Foo) or element access expressions with string literal keys (e.g., Type['Foo'])
// where the object is a direct identifier (not a variable reference or property access)
return isEntityNameExpression(expr) || (isElementAccessExpression(expr) && isIdentifier(expr.expression) && isStringLiteral(expr.argumentExpression));
}

function isTypeUsableAsIndexSignature(type: Type): boolean {
Expand Down
86 changes: 86 additions & 0 deletions tests/baselines/reference/enumComputedPropertyNonIdentifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//// [tests/cases/compiler/enumComputedPropertyNonIdentifier.ts] ////

//// [enumComputedPropertyNonIdentifier.ts]
// Issue #25083: Enum keys not accepted as computed properties if their name is not a valid identifier

enum Type {
Foo = 'foo',
'3x14' = '3x14',
'hello-world' = 'hello-world'
}

// These should work - dot notation
type TypeMapDot = {
[Type.Foo]: string;
}

// These should also work - bracket notation with valid identifier
type TypeMapBracketValid = {
[Type['Foo']]: string; // Now works!
}

// These should work - bracket notation with non-identifier names
type TypeMapBracketNonIdentifier = {
[Type['3x14']]: number; // Now works!
[Type['hello-world']]: string; // Now works!
}

// Test in object types as well
interface TestInterface {
[Type.Foo]: string; // OK
[Type['3x14']]: number; // Now works!
}

// Verify the enum values work in actual objects
const obj1: Record<Type, any> = {
[Type.Foo]: 'test',
[Type['3x14']]: 123,
[Type['hello-world']]: 'hello'
};

// Verify direct access works
const val1 = Type.Foo; // OK
const val2 = Type['Foo']; // OK
const val3 = Type['3x14']; // OK


//// [enumComputedPropertyNonIdentifier.js]
"use strict";
// Issue #25083: Enum keys not accepted as computed properties if their name is not a valid identifier
var _a;
var Type;
(function (Type) {
Type["Foo"] = "foo";
Type["3x14"] = "3x14";
Type["hello-world"] = "hello-world";
})(Type || (Type = {}));
// Verify the enum values work in actual objects
var obj1 = (_a = {},
_a[Type.Foo] = 'test',
_a[Type['3x14']] = 123,
_a[Type['hello-world']] = 'hello',
_a);
// Verify direct access works
var val1 = Type.Foo; // OK
var val2 = Type['Foo']; // OK
var val3 = Type['3x14']; // OK


//// [enumComputedPropertyNonIdentifier.d.ts]
declare enum Type {
Foo = "foo",
'3x14' = "3x14",
'hello-world' = "hello-world"
}
type TypeMapDot = {
[Type.Foo]: string;
};
type TypeMapBracketValid = {};
type TypeMapBracketNonIdentifier = {};
interface TestInterface {
[Type.Foo]: string;
}
declare const obj1: Record<Type, any>;
declare const val1 = Type.Foo;
declare const val2 = Type.Foo;
declare const val3 = Type['3x14'];
111 changes: 111 additions & 0 deletions tests/baselines/reference/enumComputedPropertyNonIdentifier.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//// [tests/cases/compiler/enumComputedPropertyNonIdentifier.ts] ////

=== enumComputedPropertyNonIdentifier.ts ===
// Issue #25083: Enum keys not accepted as computed properties if their name is not a valid identifier

enum Type {
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))

Foo = 'foo',
>Foo : Symbol(Type.Foo, Decl(enumComputedPropertyNonIdentifier.ts, 2, 11))

'3x14' = '3x14',
>'3x14' : Symbol(Type['3x14'], Decl(enumComputedPropertyNonIdentifier.ts, 3, 16))

'hello-world' = 'hello-world'
>'hello-world' : Symbol(Type['hello-world'], Decl(enumComputedPropertyNonIdentifier.ts, 4, 20))
}

// These should work - dot notation
type TypeMapDot = {
>TypeMapDot : Symbol(TypeMapDot, Decl(enumComputedPropertyNonIdentifier.ts, 6, 1))

[Type.Foo]: string;
>[Type.Foo] : Symbol([Type.Foo], Decl(enumComputedPropertyNonIdentifier.ts, 9, 19))
>Type.Foo : Symbol(Type.Foo, Decl(enumComputedPropertyNonIdentifier.ts, 2, 11))
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))
>Foo : Symbol(Type.Foo, Decl(enumComputedPropertyNonIdentifier.ts, 2, 11))
}

// These should also work - bracket notation with valid identifier
type TypeMapBracketValid = {
>TypeMapBracketValid : Symbol(TypeMapBracketValid, Decl(enumComputedPropertyNonIdentifier.ts, 11, 1))

[Type['Foo']]: string; // Now works!
>[Type['Foo']] : Symbol([Type['Foo']], Decl(enumComputedPropertyNonIdentifier.ts, 14, 28))
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))
>'Foo' : Symbol(Type.Foo, Decl(enumComputedPropertyNonIdentifier.ts, 2, 11))
}

// These should work - bracket notation with non-identifier names
type TypeMapBracketNonIdentifier = {
>TypeMapBracketNonIdentifier : Symbol(TypeMapBracketNonIdentifier, Decl(enumComputedPropertyNonIdentifier.ts, 16, 1))

[Type['3x14']]: number; // Now works!
>[Type['3x14']] : Symbol([Type['3x14']], Decl(enumComputedPropertyNonIdentifier.ts, 19, 36))
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))
>'3x14' : Symbol(Type['3x14'], Decl(enumComputedPropertyNonIdentifier.ts, 3, 16))

[Type['hello-world']]: string; // Now works!
>[Type['hello-world']] : Symbol([Type['hello-world']], Decl(enumComputedPropertyNonIdentifier.ts, 20, 27))
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))
>'hello-world' : Symbol(Type['hello-world'], Decl(enumComputedPropertyNonIdentifier.ts, 4, 20))
}

// Test in object types as well
interface TestInterface {
>TestInterface : Symbol(TestInterface, Decl(enumComputedPropertyNonIdentifier.ts, 22, 1))

[Type.Foo]: string; // OK
>[Type.Foo] : Symbol(TestInterface[Type.Foo], Decl(enumComputedPropertyNonIdentifier.ts, 25, 25))
>Type.Foo : Symbol(Type.Foo, Decl(enumComputedPropertyNonIdentifier.ts, 2, 11))
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))
>Foo : Symbol(Type.Foo, Decl(enumComputedPropertyNonIdentifier.ts, 2, 11))

[Type['3x14']]: number; // Now works!
>[Type['3x14']] : Symbol(TestInterface[Type['3x14']], Decl(enumComputedPropertyNonIdentifier.ts, 26, 23))
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))
>'3x14' : Symbol(Type['3x14'], Decl(enumComputedPropertyNonIdentifier.ts, 3, 16))
}

// Verify the enum values work in actual objects
const obj1: Record<Type, any> = {
>obj1 : Symbol(obj1, Decl(enumComputedPropertyNonIdentifier.ts, 31, 5))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))

[Type.Foo]: 'test',
>[Type.Foo] : Symbol([Type.Foo], Decl(enumComputedPropertyNonIdentifier.ts, 31, 33))
>Type.Foo : Symbol(Type.Foo, Decl(enumComputedPropertyNonIdentifier.ts, 2, 11))
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))
>Foo : Symbol(Type.Foo, Decl(enumComputedPropertyNonIdentifier.ts, 2, 11))

[Type['3x14']]: 123,
>[Type['3x14']] : Symbol([Type['3x14']], Decl(enumComputedPropertyNonIdentifier.ts, 32, 23))
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))
>'3x14' : Symbol(Type['3x14'], Decl(enumComputedPropertyNonIdentifier.ts, 3, 16))

[Type['hello-world']]: 'hello'
>[Type['hello-world']] : Symbol([Type['hello-world']], Decl(enumComputedPropertyNonIdentifier.ts, 33, 24))
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))
>'hello-world' : Symbol(Type['hello-world'], Decl(enumComputedPropertyNonIdentifier.ts, 4, 20))

};

// Verify direct access works
const val1 = Type.Foo; // OK
>val1 : Symbol(val1, Decl(enumComputedPropertyNonIdentifier.ts, 38, 5))
>Type.Foo : Symbol(Type.Foo, Decl(enumComputedPropertyNonIdentifier.ts, 2, 11))
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))
>Foo : Symbol(Type.Foo, Decl(enumComputedPropertyNonIdentifier.ts, 2, 11))

const val2 = Type['Foo']; // OK
>val2 : Symbol(val2, Decl(enumComputedPropertyNonIdentifier.ts, 39, 5))
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))
>'Foo' : Symbol(Type.Foo, Decl(enumComputedPropertyNonIdentifier.ts, 2, 11))

const val3 = Type['3x14']; // OK
>val3 : Symbol(val3, Decl(enumComputedPropertyNonIdentifier.ts, 40, 5))
>Type : Symbol(Type, Decl(enumComputedPropertyNonIdentifier.ts, 0, 0))
>'3x14' : Symbol(Type['3x14'], Decl(enumComputedPropertyNonIdentifier.ts, 3, 16))

Loading