diff --git a/package-lock.json b/package-lock.json index 6e0bc60ef577f..54f5c439b885f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2466,6 +2466,7 @@ "integrity": "sha512-pO9XH79SyXybj2Vhc9ITZMEI8cJkdlQQRoD8oEfPH6Jjpp/7WX5kIgECVd3DBOjjAdCSiW6R47v3gJBx/qZVkw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "dprint": "bin.js" }, diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 48bc0da113816..065969d19273e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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 { diff --git a/tests/baselines/reference/enumComputedPropertyNonIdentifier.js b/tests/baselines/reference/enumComputedPropertyNonIdentifier.js new file mode 100644 index 0000000000000..af6013252bc80 --- /dev/null +++ b/tests/baselines/reference/enumComputedPropertyNonIdentifier.js @@ -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.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; +declare const val1 = Type.Foo; +declare const val2 = Type.Foo; +declare const val3 = Type['3x14']; diff --git a/tests/baselines/reference/enumComputedPropertyNonIdentifier.symbols b/tests/baselines/reference/enumComputedPropertyNonIdentifier.symbols new file mode 100644 index 0000000000000..ff215b6b99976 --- /dev/null +++ b/tests/baselines/reference/enumComputedPropertyNonIdentifier.symbols @@ -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 = { +>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)) + diff --git a/tests/baselines/reference/enumComputedPropertyNonIdentifier.types b/tests/baselines/reference/enumComputedPropertyNonIdentifier.types new file mode 100644 index 0000000000000..8035684f03ab1 --- /dev/null +++ b/tests/baselines/reference/enumComputedPropertyNonIdentifier.types @@ -0,0 +1,185 @@ +//// [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 : Type +> : ^^^^ + + Foo = 'foo', +>Foo : Type.Foo +> : ^^^^^^^^ +>'foo' : "foo" +> : ^^^^^ + + '3x14' = '3x14', +>'3x14' : (typeof Type)["3x14"] +> : ^^^^^^^^^^^^^^^^^^^^^ +>'3x14' : "3x14" +> : ^^^^^^ + + 'hello-world' = 'hello-world' +>'hello-world' : (typeof Type)["hello-world"] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>'hello-world' : "hello-world" +> : ^^^^^^^^^^^^^ +} + +// These should work - dot notation +type TypeMapDot = { +>TypeMapDot : TypeMapDot +> : ^^^^^^^^^^ + + [Type.Foo]: string; +>[Type.Foo] : string +> : ^^^^^^ +>Type.Foo : Type.Foo +> : ^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>Foo : Type.Foo +> : ^^^^^^^^ +} + +// These should also work - bracket notation with valid identifier +type TypeMapBracketValid = { +>TypeMapBracketValid : TypeMapBracketValid +> : ^^^^^^^^^^^^^^^^^^^ + + [Type['Foo']]: string; // Now works! +>[Type['Foo']] : string +> : ^^^^^^ +>Type['Foo'] : Type.Foo +> : ^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>'Foo' : "Foo" +> : ^^^^^ +} + +// These should work - bracket notation with non-identifier names +type TypeMapBracketNonIdentifier = { +>TypeMapBracketNonIdentifier : TypeMapBracketNonIdentifier +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + [Type['3x14']]: number; // Now works! +>[Type['3x14']] : number +> : ^^^^^^ +>Type['3x14'] : (typeof Type)["3x14"] +> : ^^^^^^^^^^^^^^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>'3x14' : "3x14" +> : ^^^^^^ + + [Type['hello-world']]: string; // Now works! +>[Type['hello-world']] : string +> : ^^^^^^ +>Type['hello-world'] : (typeof Type)["hello-world"] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>'hello-world' : "hello-world" +> : ^^^^^^^^^^^^^ +} + +// Test in object types as well +interface TestInterface { + [Type.Foo]: string; // OK +>[Type.Foo] : string +> : ^^^^^^ +>Type.Foo : Type.Foo +> : ^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>Foo : Type.Foo +> : ^^^^^^^^ + + [Type['3x14']]: number; // Now works! +>[Type['3x14']] : number +> : ^^^^^^ +>Type['3x14'] : (typeof Type)["3x14"] +> : ^^^^^^^^^^^^^^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>'3x14' : "3x14" +> : ^^^^^^ +} + +// Verify the enum values work in actual objects +const obj1: Record = { +>obj1 : Record +> : ^^^^^^^^^^^^^^^^^ +>{ [Type.Foo]: 'test', [Type['3x14']]: 123, [Type['hello-world']]: 'hello'} : { foo: string; "3x14": number; "hello-world": string; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + [Type.Foo]: 'test', +>[Type.Foo] : string +> : ^^^^^^ +>Type.Foo : Type.Foo +> : ^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>Foo : Type.Foo +> : ^^^^^^^^ +>'test' : "test" +> : ^^^^^^ + + [Type['3x14']]: 123, +>[Type['3x14']] : number +> : ^^^^^^ +>Type['3x14'] : (typeof Type)["3x14"] +> : ^^^^^^^^^^^^^^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>'3x14' : "3x14" +> : ^^^^^^ +>123 : 123 +> : ^^^ + + [Type['hello-world']]: 'hello' +>[Type['hello-world']] : string +> : ^^^^^^ +>Type['hello-world'] : (typeof Type)["hello-world"] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>'hello-world' : "hello-world" +> : ^^^^^^^^^^^^^ +>'hello' : "hello" +> : ^^^^^^^ + +}; + +// Verify direct access works +const val1 = Type.Foo; // OK +>val1 : Type.Foo +> : ^^^^^^^^ +>Type.Foo : Type.Foo +> : ^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>Foo : Type.Foo +> : ^^^^^^^^ + +const val2 = Type['Foo']; // OK +>val2 : Type.Foo +> : ^^^^^^^^ +>Type['Foo'] : Type.Foo +> : ^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>'Foo' : "Foo" +> : ^^^^^ + +const val3 = Type['3x14']; // OK +>val3 : (typeof Type)["3x14"] +> : ^^^^^^^^^^^^^^^^^^^^^ +>Type['3x14'] : (typeof Type)["3x14"] +> : ^^^^^^^^^^^^^^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>'3x14' : "3x14" +> : ^^^^^^ + diff --git a/tests/baselines/reference/isolatedDeclarationLazySymbols.errors.txt b/tests/baselines/reference/isolatedDeclarationLazySymbols.errors.txt index 812ec2c1bea83..6931ab5f5df2b 100644 --- a/tests/baselines/reference/isolatedDeclarationLazySymbols.errors.txt +++ b/tests/baselines/reference/isolatedDeclarationLazySymbols.errors.txt @@ -1,6 +1,6 @@ isolatedDeclarationLazySymbols.ts(1,17): error TS9007: Function must have an explicit return type annotation with --isolatedDeclarations. +isolatedDeclarationLazySymbols.ts(12,1): error TS9023: Assigning properties to functions without declaring them is not supported with --isolatedDeclarations. Add an explicit declaration for the properties assigned to this function. isolatedDeclarationLazySymbols.ts(13,1): error TS9023: Assigning properties to functions without declaring them is not supported with --isolatedDeclarations. Add an explicit declaration for the properties assigned to this function. -isolatedDeclarationLazySymbols.ts(16,5): error TS1166: A computed property name in a class property declaration must have a simple literal type or a 'unique symbol' type. isolatedDeclarationLazySymbols.ts(16,5): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations. isolatedDeclarationLazySymbols.ts(21,5): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations. isolatedDeclarationLazySymbols.ts(22,5): error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations. @@ -22,6 +22,8 @@ isolatedDeclarationLazySymbols.ts(22,5): error TS9038: Computed property names o } as const foo[o["prop.inner"]] ="A"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS9023: Assigning properties to functions without declaring them is not supported with --isolatedDeclarations. Add an explicit declaration for the properties assigned to this function. foo[o.prop.inner] = "B"; ~~~~~~~~~~~~~~~~~ !!! error TS9023: Assigning properties to functions without declaring them is not supported with --isolatedDeclarations. Add an explicit declaration for the properties assigned to this function. @@ -29,8 +31,6 @@ isolatedDeclarationLazySymbols.ts(22,5): error TS9038: Computed property names o export class Foo { [o["prop.inner"]] ="A" ~~~~~~~~~~~~~~~~~ -!!! error TS1166: A computed property name in a class property declaration must have a simple literal type or a 'unique symbol' type. - ~~~~~~~~~~~~~~~~~ !!! error TS9038: Computed property names on class or object literals cannot be inferred with --isolatedDeclarations. [o.prop.inner] = "B" } diff --git a/tests/baselines/reference/isolatedDeclarationLazySymbols.types b/tests/baselines/reference/isolatedDeclarationLazySymbols.types index c7e91f9680a8c..5a1bfe22638bd 100644 --- a/tests/baselines/reference/isolatedDeclarationLazySymbols.types +++ b/tests/baselines/reference/isolatedDeclarationLazySymbols.types @@ -40,8 +40,8 @@ const o = { foo[o["prop.inner"]] ="A"; >foo[o["prop.inner"]] ="A" : "A" > : ^^^ ->foo[o["prop.inner"]] : any -> : ^^^ +>foo[o["prop.inner"]] : string +> : ^^^^^^ >foo : typeof foo > : ^^^^^^^^^^ >o["prop.inner"] : "a" diff --git a/tests/cases/compiler/enumComputedPropertyNonIdentifier.ts b/tests/cases/compiler/enumComputedPropertyNonIdentifier.ts new file mode 100644 index 0000000000000..39272c2c426f4 --- /dev/null +++ b/tests/cases/compiler/enumComputedPropertyNonIdentifier.ts @@ -0,0 +1,44 @@ +// @strict: true +// @declaration: true + +// 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.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