From ec84ad1924e388f8108b50f1c710f78c07c2442d Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Fri, 12 Dec 2025 18:30:17 +0800 Subject: [PATCH 1/5] Allow enum keys accessed with bracket notation as computed properties This fixes #25083 where enum keys accessed with bracket notation (e.g., `Type['3x14']`) were not recognized as valid computed property names in type literals, even when they resolved to literal types. The fix extends `isLateBindableAST` to recognize `ElementAccessExpression` with string or numeric literal keys as valid late-bindable expressions, similar to how `PropertyAccessExpression` is already handled. This also updates `checkGrammarForInvalidDynamicName` to allow such expressions as computed property names. --- src/compiler/checker.ts | 22 ++- ...AsComputedPropertiesWithBracketNotation.js | 54 ++++++++ ...putedPropertiesWithBracketNotation.symbols | 78 +++++++++++ ...omputedPropertiesWithBracketNotation.types | 127 ++++++++++++++++++ .../isolatedDeclarationLazySymbols.errors.txt | 6 +- .../isolatedDeclarationLazySymbols.types | 4 +- ...AsComputedPropertiesWithBracketNotation.ts | 36 +++++ 7 files changed, 319 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.js create mode 100644 tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.symbols create mode 100644 tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.types create mode 100644 tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8c52e9d172877..05eae6acdddea 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13721,7 +13721,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; } const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression; - return isEntityNameExpression(expr); + return isEntityNameOrElementAccessExpression(expr); + } + + /** + * Returns true if the expression is a valid late-bindable expression. + * A late-bindable expression is an entity name expression (Identifier or PropertyAccessExpression) + * or an ElementAccessExpression with a string or numeric literal key where the base expression + * is itself a valid late-bindable expression. + */ + function isEntityNameOrElementAccessExpression(node: Node): boolean { + if (isEntityNameExpression(node)) { + return true; + } + if (isElementAccessExpression(node) && isStringOrNumericLiteralLike(node.argumentExpression)) { + return isEntityNameOrElementAccessExpression(node.expression); + } + return false; } function isTypeUsableAsIndexSignature(type: Type): boolean { @@ -52910,8 +52926,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) { - // Even non-bindable names are allowed as late-bound implied index signatures so long as the name is a simple `a.b.c` type name expression - if (isNonBindableDynamicName(node) && !isEntityNameExpression(isElementAccessExpression(node) ? skipParentheses(node.argumentExpression) : (node as ComputedPropertyName).expression)) { + // Even non-bindable names are allowed as late-bound implied index signatures so long as the name is a simple `a.b.c` or `a['b']` type name expression + if (isNonBindableDynamicName(node) && !isEntityNameOrElementAccessExpression(isElementAccessExpression(node) ? skipParentheses(node.argumentExpression) : (node as ComputedPropertyName).expression)) { return grammarErrorOnNode(node, message); } } diff --git a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.js b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.js new file mode 100644 index 0000000000000..17b2dc3be817c --- /dev/null +++ b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.js @@ -0,0 +1,54 @@ +//// [tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts] //// + +//// [enumKeysAsComputedPropertiesWithBracketNotation.ts] +// Test that enum keys accessed with bracket notation can be used as computed properties +// Regression test for https://github.com/microsoft/TypeScript/issues/25083 + +enum Type { + Foo = 'foo', + '3x14' = '3x14' +} + +// All of these should work +type TypeMap = { + [Type.Foo]: string; // Property access + [Type['3x14']]: number; // Element access with non-identifier key +} + +// Bracket notation with identifier key should also work (equivalent to property access) +type TypeMap2 = { + [Type['Foo']]: boolean; +} + +// Nested element access should work +const nested = { + inner: { + key: 'hello' as const + } +}; + +type TypeMap3 = { + [nested.inner.key]: string; +} + +// Element access on deeply nested path +type TypeMap4 = { + [nested['inner']['key']]: string; +} + + +//// [enumKeysAsComputedPropertiesWithBracketNotation.js] +"use strict"; +// Test that enum keys accessed with bracket notation can be used as computed properties +// Regression test for https://github.com/microsoft/TypeScript/issues/25083 +var Type; +(function (Type) { + Type["Foo"] = "foo"; + Type["3x14"] = "3x14"; +})(Type || (Type = {})); +// Nested element access should work +var nested = { + inner: { + key: 'hello' + } +}; diff --git a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.symbols b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.symbols new file mode 100644 index 0000000000000..997957478788b --- /dev/null +++ b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.symbols @@ -0,0 +1,78 @@ +//// [tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts] //// + +=== enumKeysAsComputedPropertiesWithBracketNotation.ts === +// Test that enum keys accessed with bracket notation can be used as computed properties +// Regression test for https://github.com/microsoft/TypeScript/issues/25083 + +enum Type { +>Type : Symbol(Type, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 0, 0)) + + Foo = 'foo', +>Foo : Symbol(Type.Foo, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 3, 11)) + + '3x14' = '3x14' +>'3x14' : Symbol(Type['3x14'], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 4, 16)) +} + +// All of these should work +type TypeMap = { +>TypeMap : Symbol(TypeMap, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 6, 1)) + + [Type.Foo]: string; // Property access +>[Type.Foo] : Symbol([Type.Foo], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 9, 16)) +>Type.Foo : Symbol(Type.Foo, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 3, 11)) +>Type : Symbol(Type, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 0, 0)) +>Foo : Symbol(Type.Foo, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 3, 11)) + + [Type['3x14']]: number; // Element access with non-identifier key +>[Type['3x14']] : Symbol([Type['3x14']], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 10, 23)) +>Type : Symbol(Type, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 0, 0)) +>'3x14' : Symbol(Type['3x14'], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 4, 16)) +} + +// Bracket notation with identifier key should also work (equivalent to property access) +type TypeMap2 = { +>TypeMap2 : Symbol(TypeMap2, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 12, 1)) + + [Type['Foo']]: boolean; +>[Type['Foo']] : Symbol([Type['Foo']], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 15, 17)) +>Type : Symbol(Type, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 0, 0)) +>'Foo' : Symbol(Type.Foo, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 3, 11)) +} + +// Nested element access should work +const nested = { +>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5)) + + inner: { +>inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16)) + + key: 'hello' as const +>key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +>const : Symbol(const) + } +}; + +type TypeMap3 = { +>TypeMap3 : Symbol(TypeMap3, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 24, 2)) + + [nested.inner.key]: string; +>[nested.inner.key] : Symbol([nested.inner.key], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 26, 17)) +>nested.inner.key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +>nested.inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16)) +>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5)) +>inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16)) +>key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +} + +// Element access on deeply nested path +type TypeMap4 = { +>TypeMap4 : Symbol(TypeMap4, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 28, 1)) + + [nested['inner']['key']]: string; +>[nested['inner']['key']] : Symbol([nested['inner']['key']], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 31, 17)) +>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5)) +>'inner' : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16)) +>'key' : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +} + diff --git a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.types b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.types new file mode 100644 index 0000000000000..680eb21c70fd9 --- /dev/null +++ b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.types @@ -0,0 +1,127 @@ +//// [tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts] //// + +=== enumKeysAsComputedPropertiesWithBracketNotation.ts === +// Test that enum keys accessed with bracket notation can be used as computed properties +// Regression test for https://github.com/microsoft/TypeScript/issues/25083 + +enum Type { +>Type : Type +> : ^^^^ + + Foo = 'foo', +>Foo : Type.Foo +> : ^^^^^^^^ +>'foo' : "foo" +> : ^^^^^ + + '3x14' = '3x14' +>'3x14' : (typeof Type)["3x14"] +> : ^^^^^^^^^^^^^^^^^^^^^ +>'3x14' : "3x14" +> : ^^^^^^ +} + +// All of these should work +type TypeMap = { +>TypeMap : TypeMap +> : ^^^^^^^ + + [Type.Foo]: string; // Property access +>[Type.Foo] : string +> : ^^^^^^ +>Type.Foo : Type.Foo +> : ^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>Foo : Type.Foo +> : ^^^^^^^^ + + [Type['3x14']]: number; // Element access with non-identifier key +>[Type['3x14']] : number +> : ^^^^^^ +>Type['3x14'] : (typeof Type)["3x14"] +> : ^^^^^^^^^^^^^^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>'3x14' : "3x14" +> : ^^^^^^ +} + +// Bracket notation with identifier key should also work (equivalent to property access) +type TypeMap2 = { +>TypeMap2 : TypeMap2 +> : ^^^^^^^^ + + [Type['Foo']]: boolean; +>[Type['Foo']] : boolean +> : ^^^^^^^ +>Type['Foo'] : Type.Foo +> : ^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>'Foo' : "Foo" +> : ^^^^^ +} + +// Nested element access should work +const nested = { +>nested : { inner: { key: "hello"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ inner: { key: 'hello' as const }} : { inner: { key: "hello"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + inner: { +>inner : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>{ key: 'hello' as const } : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ + + key: 'hello' as const +>key : "hello" +> : ^^^^^^^ +>'hello' as const : "hello" +> : ^^^^^^^ +>'hello' : "hello" +> : ^^^^^^^ + } +}; + +type TypeMap3 = { +>TypeMap3 : TypeMap3 +> : ^^^^^^^^ + + [nested.inner.key]: string; +>[nested.inner.key] : string +> : ^^^^^^ +>nested.inner.key : "hello" +> : ^^^^^^^ +>nested.inner : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>nested : { inner: { key: "hello"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>inner : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>key : "hello" +> : ^^^^^^^ +} + +// Element access on deeply nested path +type TypeMap4 = { +>TypeMap4 : TypeMap4 +> : ^^^^^^^^ + + [nested['inner']['key']]: string; +>[nested['inner']['key']] : string +> : ^^^^^^ +>nested['inner']['key'] : "hello" +> : ^^^^^^^ +>nested['inner'] : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>nested : { inner: { key: "hello"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>'inner' : "inner" +> : ^^^^^^^ +>'key' : "key" +> : ^^^^^ +} + 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/enumKeysAsComputedPropertiesWithBracketNotation.ts b/tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts new file mode 100644 index 0000000000000..a4f46714ba24c --- /dev/null +++ b/tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts @@ -0,0 +1,36 @@ +// @strict: true + +// Test that enum keys accessed with bracket notation can be used as computed properties +// Regression test for https://github.com/microsoft/TypeScript/issues/25083 + +enum Type { + Foo = 'foo', + '3x14' = '3x14' +} + +// All of these should work +type TypeMap = { + [Type.Foo]: string; // Property access + [Type['3x14']]: number; // Element access with non-identifier key +} + +// Bracket notation with identifier key should also work (equivalent to property access) +type TypeMap2 = { + [Type['Foo']]: boolean; +} + +// Nested element access should work +const nested = { + inner: { + key: 'hello' as const + } +}; + +type TypeMap3 = { + [nested.inner.key]: string; +} + +// Element access on deeply nested path +type TypeMap4 = { + [nested['inner']['key']]: string; +} From 7f835dea430a055ed0178e198a3b4b693c034b58 Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Fri, 12 Dec 2025 21:02:54 +0800 Subject: [PATCH 2/5] fix(checker): improve late-bindable access expression handling - Rename isEntityNameOrElementAccessExpression to isLateBindableAccessExpression - Support mixed chains like obj.a['b'].c['d'] - Add skipParentheses handling for expressions like (obj.a)['b'] - Ensure PropertyAccessExpression name is Identifier (not PrivateIdentifier) - Add comprehensive test cases for mixed chains and parenthesized expressions --- src/compiler/checker.ts | 29 ++- ...AsComputedPropertiesWithBracketNotation.js | 52 +++++ ...putedPropertiesWithBracketNotation.symbols | 109 ++++++++++ ...omputedPropertiesWithBracketNotation.types | 195 ++++++++++++++++++ ...AsComputedPropertiesWithBracketNotation.ts | 42 ++++ 5 files changed, 418 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 05eae6acdddea..18a55a769d592 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13721,21 +13721,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; } const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression; - return isEntityNameOrElementAccessExpression(expr); + return isLateBindableAccessExpression(expr); } /** - * Returns true if the expression is a valid late-bindable expression. - * A late-bindable expression is an entity name expression (Identifier or PropertyAccessExpression) - * or an ElementAccessExpression with a string or numeric literal key where the base expression - * is itself a valid late-bindable expression. + * Returns true if the expression is a valid late-bindable access expression. + * A late-bindable access expression is: + * - An Identifier + * - A PropertyAccessExpression where the base is a late-bindable access expression + * - An ElementAccessExpression with a string/numeric literal key where the base is a late-bindable access expression + * + * This supports mixed chains like: obj.a['b'].c['d'] + * Parentheses are skipped to support expressions like: (obj.a)['b'] */ - function isEntityNameOrElementAccessExpression(node: Node): boolean { - if (isEntityNameExpression(node)) { + function isLateBindableAccessExpression(node: Node): boolean { + node = skipParentheses(node); + + if (isIdentifier(node)) { return true; } + // For PropertyAccessExpression, require the name to be an Identifier (not PrivateIdentifier) + if (isPropertyAccessExpression(node) && isIdentifier(node.name)) { + return isLateBindableAccessExpression(node.expression); + } if (isElementAccessExpression(node) && isStringOrNumericLiteralLike(node.argumentExpression)) { - return isEntityNameOrElementAccessExpression(node.expression); + return isLateBindableAccessExpression(node.expression); } return false; } @@ -52927,7 +52937,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) { // Even non-bindable names are allowed as late-bound implied index signatures so long as the name is a simple `a.b.c` or `a['b']` type name expression - if (isNonBindableDynamicName(node) && !isEntityNameOrElementAccessExpression(isElementAccessExpression(node) ? skipParentheses(node.argumentExpression) : (node as ComputedPropertyName).expression)) { + // isLateBindableAccessExpression handles skipParentheses internally + if (isNonBindableDynamicName(node) && !isLateBindableAccessExpression(isElementAccessExpression(node) ? node.argumentExpression : (node as ComputedPropertyName).expression)) { return grammarErrorOnNode(node, message); } } diff --git a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.js b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.js index 17b2dc3be817c..5bb8bedd9a9e8 100644 --- a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.js +++ b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.js @@ -35,6 +35,48 @@ type TypeMap3 = { type TypeMap4 = { [nested['inner']['key']]: string; } + +// Mixed chain: element access followed by property access +type TypeMap5 = { + [nested['inner'].key]: string; +} + +// Mixed chain: property access followed by element access +type TypeMap6 = { + [nested.inner['key']]: string; +} + +// Complex mixed chain +const deep = { + a: { + b: { + c: { + d: 'value' as const + } + } + } +}; + +type TypeMap7 = { + [deep.a['b'].c['d']]: string; +} + +type TypeMap8 = { + [deep['a'].b['c'].d]: string; +} + +// Parenthesized expressions +type TypeMap9 = { + [(nested.inner).key]: string; +} + +type TypeMap10 = { + [(nested['inner']).key]: string; +} + +type TypeMap11 = { + [(nested).inner.key]: string; +} //// [enumKeysAsComputedPropertiesWithBracketNotation.js] @@ -52,3 +94,13 @@ var nested = { key: 'hello' } }; +// Complex mixed chain +var deep = { + a: { + b: { + c: { + d: 'value' + } + } + } +}; diff --git a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.symbols b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.symbols index 997957478788b..bee5b1bedb9af 100644 --- a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.symbols +++ b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.symbols @@ -76,3 +76,112 @@ type TypeMap4 = { >'key' : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) } +// Mixed chain: element access followed by property access +type TypeMap5 = { +>TypeMap5 : Symbol(TypeMap5, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 33, 1)) + + [nested['inner'].key]: string; +>[nested['inner'].key] : Symbol([nested['inner'].key], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 36, 17)) +>nested['inner'].key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5)) +>'inner' : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16)) +>key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +} + +// Mixed chain: property access followed by element access +type TypeMap6 = { +>TypeMap6 : Symbol(TypeMap6, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 38, 1)) + + [nested.inner['key']]: string; +>[nested.inner['key']] : Symbol([nested.inner['key']], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 41, 17)) +>nested.inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16)) +>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5)) +>inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16)) +>'key' : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +} + +// Complex mixed chain +const deep = { +>deep : Symbol(deep, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 5)) + + a: { +>a : Symbol(a, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 14)) + + b: { +>b : Symbol(b, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 47, 8)) + + c: { +>c : Symbol(c, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 48, 12)) + + d: 'value' as const +>d : Symbol(d, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 49, 16)) +>const : Symbol(const) + } + } + } +}; + +type TypeMap7 = { +>TypeMap7 : Symbol(TypeMap7, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 54, 2)) + + [deep.a['b'].c['d']]: string; +>[deep.a['b'].c['d']] : Symbol([deep.a['b'].c['d']], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 56, 17)) +>deep.a['b'].c : Symbol(c, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 48, 12)) +>deep.a : Symbol(a, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 14)) +>deep : Symbol(deep, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 5)) +>a : Symbol(a, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 14)) +>'b' : Symbol(b, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 47, 8)) +>c : Symbol(c, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 48, 12)) +>'d' : Symbol(d, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 49, 16)) +} + +type TypeMap8 = { +>TypeMap8 : Symbol(TypeMap8, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 58, 1)) + + [deep['a'].b['c'].d]: string; +>[deep['a'].b['c'].d] : Symbol([deep['a'].b['c'].d], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 60, 17)) +>deep['a'].b['c'].d : Symbol(d, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 49, 16)) +>deep['a'].b : Symbol(b, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 47, 8)) +>deep : Symbol(deep, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 5)) +>'a' : Symbol(a, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 14)) +>b : Symbol(b, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 47, 8)) +>'c' : Symbol(c, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 48, 12)) +>d : Symbol(d, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 49, 16)) +} + +// Parenthesized expressions +type TypeMap9 = { +>TypeMap9 : Symbol(TypeMap9, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 62, 1)) + + [(nested.inner).key]: string; +>[(nested.inner).key] : Symbol([(nested.inner).key], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 65, 17)) +>(nested.inner).key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +>nested.inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16)) +>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5)) +>inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16)) +>key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +} + +type TypeMap10 = { +>TypeMap10 : Symbol(TypeMap10, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 67, 1)) + + [(nested['inner']).key]: string; +>[(nested['inner']).key] : Symbol([(nested['inner']).key], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 69, 18)) +>(nested['inner']).key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5)) +>'inner' : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16)) +>key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +} + +type TypeMap11 = { +>TypeMap11 : Symbol(TypeMap11, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 71, 1)) + + [(nested).inner.key]: string; +>[(nested).inner.key] : Symbol([(nested).inner.key], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 73, 18)) +>(nested).inner.key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +>(nested).inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16)) +>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5)) +>inner : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16)) +>key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +} + diff --git a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.types b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.types index 680eb21c70fd9..2b7113ef31bb9 100644 --- a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.types +++ b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.types @@ -125,3 +125,198 @@ type TypeMap4 = { > : ^^^^^ } +// Mixed chain: element access followed by property access +type TypeMap5 = { +>TypeMap5 : TypeMap5 +> : ^^^^^^^^ + + [nested['inner'].key]: string; +>[nested['inner'].key] : string +> : ^^^^^^ +>nested['inner'].key : "hello" +> : ^^^^^^^ +>nested['inner'] : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>nested : { inner: { key: "hello"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>'inner' : "inner" +> : ^^^^^^^ +>key : "hello" +> : ^^^^^^^ +} + +// Mixed chain: property access followed by element access +type TypeMap6 = { +>TypeMap6 : TypeMap6 +> : ^^^^^^^^ + + [nested.inner['key']]: string; +>[nested.inner['key']] : string +> : ^^^^^^ +>nested.inner['key'] : "hello" +> : ^^^^^^^ +>nested.inner : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>nested : { inner: { key: "hello"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>inner : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>'key' : "key" +> : ^^^^^ +} + +// Complex mixed chain +const deep = { +>deep : { a: { b: { c: { d: "value"; }; }; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ a: { b: { c: { d: 'value' as const } } }} : { a: { b: { c: { d: "value"; }; }; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + a: { +>a : { b: { c: { d: "value"; }; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ b: { c: { d: 'value' as const } } } : { b: { c: { d: "value"; }; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + b: { +>b : { c: { d: "value"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^ +>{ c: { d: 'value' as const } } : { c: { d: "value"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^ + + c: { +>c : { d: "value"; } +> : ^^^^^^^^^^^^^^^ +>{ d: 'value' as const } : { d: "value"; } +> : ^^^^^^^^^^^^^^^ + + d: 'value' as const +>d : "value" +> : ^^^^^^^ +>'value' as const : "value" +> : ^^^^^^^ +>'value' : "value" +> : ^^^^^^^ + } + } + } +}; + +type TypeMap7 = { +>TypeMap7 : TypeMap7 +> : ^^^^^^^^ + + [deep.a['b'].c['d']]: string; +>[deep.a['b'].c['d']] : string +> : ^^^^^^ +>deep.a['b'].c['d'] : "value" +> : ^^^^^^^ +>deep.a['b'].c : { d: "value"; } +> : ^^^^^^^^^^^^^^^ +>deep.a['b'] : { c: { d: "value"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^ +>deep.a : { b: { c: { d: "value"; }; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>deep : { a: { b: { c: { d: "value"; }; }; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>a : { b: { c: { d: "value"; }; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>'b' : "b" +> : ^^^ +>c : { d: "value"; } +> : ^^^^^^^^^^^^^^^ +>'d' : "d" +> : ^^^ +} + +type TypeMap8 = { +>TypeMap8 : TypeMap8 +> : ^^^^^^^^ + + [deep['a'].b['c'].d]: string; +>[deep['a'].b['c'].d] : string +> : ^^^^^^ +>deep['a'].b['c'].d : "value" +> : ^^^^^^^ +>deep['a'].b['c'] : { d: "value"; } +> : ^^^^^^^^^^^^^^^ +>deep['a'].b : { c: { d: "value"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^ +>deep['a'] : { b: { c: { d: "value"; }; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>deep : { a: { b: { c: { d: "value"; }; }; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>'a' : "a" +> : ^^^ +>b : { c: { d: "value"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^ +>'c' : "c" +> : ^^^ +>d : "value" +> : ^^^^^^^ +} + +// Parenthesized expressions +type TypeMap9 = { +>TypeMap9 : TypeMap9 +> : ^^^^^^^^ + + [(nested.inner).key]: string; +>[(nested.inner).key] : string +> : ^^^^^^ +>(nested.inner).key : "hello" +> : ^^^^^^^ +>(nested.inner) : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>nested.inner : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>nested : { inner: { key: "hello"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>inner : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>key : "hello" +> : ^^^^^^^ +} + +type TypeMap10 = { +>TypeMap10 : TypeMap10 +> : ^^^^^^^^^ + + [(nested['inner']).key]: string; +>[(nested['inner']).key] : string +> : ^^^^^^ +>(nested['inner']).key : "hello" +> : ^^^^^^^ +>(nested['inner']) : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>nested['inner'] : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>nested : { inner: { key: "hello"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>'inner' : "inner" +> : ^^^^^^^ +>key : "hello" +> : ^^^^^^^ +} + +type TypeMap11 = { +>TypeMap11 : TypeMap11 +> : ^^^^^^^^^ + + [(nested).inner.key]: string; +>[(nested).inner.key] : string +> : ^^^^^^ +>(nested).inner.key : "hello" +> : ^^^^^^^ +>(nested).inner : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>(nested) : { inner: { key: "hello"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>nested : { inner: { key: "hello"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>inner : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>key : "hello" +> : ^^^^^^^ +} + diff --git a/tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts b/tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts index a4f46714ba24c..19aa1879d703d 100644 --- a/tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts +++ b/tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts @@ -34,3 +34,45 @@ type TypeMap3 = { type TypeMap4 = { [nested['inner']['key']]: string; } + +// Mixed chain: element access followed by property access +type TypeMap5 = { + [nested['inner'].key]: string; +} + +// Mixed chain: property access followed by element access +type TypeMap6 = { + [nested.inner['key']]: string; +} + +// Complex mixed chain +const deep = { + a: { + b: { + c: { + d: 'value' as const + } + } + } +}; + +type TypeMap7 = { + [deep.a['b'].c['d']]: string; +} + +type TypeMap8 = { + [deep['a'].b['c'].d]: string; +} + +// Parenthesized expressions +type TypeMap9 = { + [(nested.inner).key]: string; +} + +type TypeMap10 = { + [(nested['inner']).key]: string; +} + +type TypeMap11 = { + [(nested).inner.key]: string; +} From be051c4c7b441ac57e6562d216ddf7aeb3310ddc Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Fri, 12 Dec 2025 21:17:56 +0800 Subject: [PATCH 3/5] fix(checker): also skip parentheses on element access keys Support obj[('a')] by applying skipParentheses to argumentExpression before checking isStringOrNumericLiteralLike. Added test cases for parenthesized keys in element access. --- src/compiler/checker.ts | 2 +- ...AsComputedPropertiesWithBracketNotation.js | 13 ++++ ...putedPropertiesWithBracketNotation.symbols | 30 ++++++++ ...omputedPropertiesWithBracketNotation.types | 74 +++++++++++++++++++ ...AsComputedPropertiesWithBracketNotation.ts | 13 ++++ 5 files changed, 131 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 18a55a769d592..01c34051a1286 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13744,7 +13744,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isPropertyAccessExpression(node) && isIdentifier(node.name)) { return isLateBindableAccessExpression(node.expression); } - if (isElementAccessExpression(node) && isStringOrNumericLiteralLike(node.argumentExpression)) { + if (isElementAccessExpression(node) && isStringOrNumericLiteralLike(skipParentheses(node.argumentExpression))) { return isLateBindableAccessExpression(node.expression); } return false; diff --git a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.js b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.js index 5bb8bedd9a9e8..a71c8d78f5c82 100644 --- a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.js +++ b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.js @@ -77,6 +77,19 @@ type TypeMap10 = { type TypeMap11 = { [(nested).inner.key]: string; } + +// Parenthesized keys in element access +type TypeMap12 = { + [nested[('inner')]['key']]: string; +} + +type TypeMap13 = { + [nested['inner'][('key')]]: string; +} + +type TypeMap14 = { + [deep[('a')][('b')].c['d']]: string; +} //// [enumKeysAsComputedPropertiesWithBracketNotation.js] diff --git a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.symbols b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.symbols index bee5b1bedb9af..15fb6a08116b9 100644 --- a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.symbols +++ b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.symbols @@ -185,3 +185,33 @@ type TypeMap11 = { >key : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) } +// Parenthesized keys in element access +type TypeMap12 = { +>TypeMap12 : Symbol(TypeMap12, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 75, 1)) + + [nested[('inner')]['key']]: string; +>[nested[('inner')]['key']] : Symbol([nested[('inner')]['key']], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 78, 18)) +>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5)) +>'key' : Symbol(key, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 21, 12)) +} + +type TypeMap13 = { +>TypeMap13 : Symbol(TypeMap13, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 80, 1)) + + [nested['inner'][('key')]]: string; +>[nested['inner'][('key')]] : Symbol([nested['inner'][('key')]], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 82, 18)) +>nested : Symbol(nested, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 5)) +>'inner' : Symbol(inner, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 20, 16)) +} + +type TypeMap14 = { +>TypeMap14 : Symbol(TypeMap14, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 84, 1)) + + [deep[('a')][('b')].c['d']]: string; +>[deep[('a')][('b')].c['d']] : Symbol([deep[('a')][('b')].c['d']], Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 86, 18)) +>deep[('a')][('b')].c : Symbol(c, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 48, 12)) +>deep : Symbol(deep, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 46, 5)) +>c : Symbol(c, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 48, 12)) +>'d' : Symbol(d, Decl(enumKeysAsComputedPropertiesWithBracketNotation.ts, 49, 16)) +} + diff --git a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.types b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.types index 2b7113ef31bb9..05baaf4bdf480 100644 --- a/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.types +++ b/tests/baselines/reference/enumKeysAsComputedPropertiesWithBracketNotation.types @@ -320,3 +320,77 @@ type TypeMap11 = { > : ^^^^^^^ } +// Parenthesized keys in element access +type TypeMap12 = { +>TypeMap12 : TypeMap12 +> : ^^^^^^^^^ + + [nested[('inner')]['key']]: string; +>[nested[('inner')]['key']] : string +> : ^^^^^^ +>nested[('inner')]['key'] : "hello" +> : ^^^^^^^ +>nested[('inner')] : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>nested : { inner: { key: "hello"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>('inner') : "inner" +> : ^^^^^^^ +>'inner' : "inner" +> : ^^^^^^^ +>'key' : "key" +> : ^^^^^ +} + +type TypeMap13 = { +>TypeMap13 : TypeMap13 +> : ^^^^^^^^^ + + [nested['inner'][('key')]]: string; +>[nested['inner'][('key')]] : string +> : ^^^^^^ +>nested['inner'][('key')] : "hello" +> : ^^^^^^^ +>nested['inner'] : { key: "hello"; } +> : ^^^^^^^^^^^^^^^^^ +>nested : { inner: { key: "hello"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>'inner' : "inner" +> : ^^^^^^^ +>('key') : "key" +> : ^^^^^ +>'key' : "key" +> : ^^^^^ +} + +type TypeMap14 = { +>TypeMap14 : TypeMap14 +> : ^^^^^^^^^ + + [deep[('a')][('b')].c['d']]: string; +>[deep[('a')][('b')].c['d']] : string +> : ^^^^^^ +>deep[('a')][('b')].c['d'] : "value" +> : ^^^^^^^ +>deep[('a')][('b')].c : { d: "value"; } +> : ^^^^^^^^^^^^^^^ +>deep[('a')][('b')] : { c: { d: "value"; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^ +>deep[('a')] : { b: { c: { d: "value"; }; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>deep : { a: { b: { c: { d: "value"; }; }; }; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>('a') : "a" +> : ^^^ +>'a' : "a" +> : ^^^ +>('b') : "b" +> : ^^^ +>'b' : "b" +> : ^^^ +>c : { d: "value"; } +> : ^^^^^^^^^^^^^^^ +>'d' : "d" +> : ^^^ +} + diff --git a/tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts b/tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts index 19aa1879d703d..af3a3d17e06e3 100644 --- a/tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts +++ b/tests/cases/compiler/enumKeysAsComputedPropertiesWithBracketNotation.ts @@ -76,3 +76,16 @@ type TypeMap10 = { type TypeMap11 = { [(nested).inner.key]: string; } + +// Parenthesized keys in element access +type TypeMap12 = { + [nested[('inner')]['key']]: string; +} + +type TypeMap13 = { + [nested['inner'][('key')]]: string; +} + +type TypeMap14 = { + [deep[('a')][('b')].c['d']]: string; +} From 1d0e4743c41dd9a157dbe5cb646ba9d9f4ca6c16 Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Sat, 13 Dec 2025 17:17:52 +0800 Subject: [PATCH 4/5] fix(declarations): preserve element access computed properties in .d.ts - Fix declaration emit elide condition to recognize ElementAccessExpression as a valid late-bindable expression (declarations.ts:1026) - Update isolatedDeclarations check to accept late-bindable access expressions (declarations.ts:1019) - Add LateBindableAccessExpression type alias for clearer semantics - Extract isLateBindableAccessExpression to utilities.ts and remove duplicate implementation from checker.ts - Update getFirstIdentifier to support ElementAccessExpression chains - Update isEntityNameVisible and related APIs to accept ElementAccessExpression - Add test case with @declaration: true to verify computed properties are preserved in generated .d.ts files --- src/compiler/checker.ts | 42 ++++------------- src/compiler/transformers/declarations.ts | 17 ++++--- src/compiler/types.ts | 16 +++++-- src/compiler/utilities.ts | 45 ++++++++++++++++--- .../cases/compiler/enumKeysExportScenario.ts | 20 +++++++++ 5 files changed, 92 insertions(+), 48 deletions(-) create mode 100644 tests/cases/compiler/enumKeysExportScenario.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 01c34051a1286..33de3b9692edb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -655,6 +655,7 @@ import { isJsxSpreadAttribute, isJSXTagName, isKnownSymbol, + isLateBindableAccessExpression, isLateVisibilityPaintedStatement, isLeftHandSideExpression, isLineBreak, @@ -6042,8 +6043,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function getMeaningOfEntityNameReference(entityName: EntityNameOrEntityNameExpression): SymbolFlags { - // get symbol of the first identifier of the entityName + function getMeaningOfEntityNameReference(entityName: EntityNameOrEntityNameExpression | ElementAccessExpression): SymbolFlags { + // get symbol of the first identifier of the entityName or element access chain let meaning: SymbolFlags; if ( entityName.parent.kind === SyntaxKind.TypeQuery || @@ -6056,6 +6057,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } else if ( entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression || + entityName.kind === SyntaxKind.ElementAccessExpression || entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration || (entityName.parent.kind === SyntaxKind.QualifiedName && (entityName.parent as QualifiedName).left === entityName) || (entityName.parent.kind === SyntaxKind.PropertyAccessExpression && (entityName.parent as PropertyAccessExpression).expression === entityName) || @@ -6072,7 +6074,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return meaning; } - function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node, shouldComputeAliasToMakeVisible = true): SymbolVisibilityResult { + function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression | ElementAccessExpression, enclosingDeclaration: Node, shouldComputeAliasToMakeVisible = true): SymbolVisibilityResult { const meaning = getMeaningOfEntityNameReference(entityName); const firstIdentifier = getFirstIdentifier(entityName); const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); @@ -8369,9 +8371,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) { + function trackComputedName(accessExpression: EntityNameOrEntityNameExpression | ElementAccessExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) { if (!context.tracker.canTrackSymbol) return; - // get symbol of the first identifier of the entityName + // get symbol of the first identifier of the entityName or element access chain const firstIdentifier = getFirstIdentifier(accessExpression); const name = resolveName(enclosingDeclaration, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); if (name) { @@ -13724,32 +13726,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return isLateBindableAccessExpression(expr); } - /** - * Returns true if the expression is a valid late-bindable access expression. - * A late-bindable access expression is: - * - An Identifier - * - A PropertyAccessExpression where the base is a late-bindable access expression - * - An ElementAccessExpression with a string/numeric literal key where the base is a late-bindable access expression - * - * This supports mixed chains like: obj.a['b'].c['d'] - * Parentheses are skipped to support expressions like: (obj.a)['b'] - */ - function isLateBindableAccessExpression(node: Node): boolean { - node = skipParentheses(node); - - if (isIdentifier(node)) { - return true; - } - // For PropertyAccessExpression, require the name to be an Identifier (not PrivateIdentifier) - if (isPropertyAccessExpression(node) && isIdentifier(node.name)) { - return isLateBindableAccessExpression(node.expression); - } - if (isElementAccessExpression(node) && isStringOrNumericLiteralLike(skipParentheses(node.argumentExpression))) { - return isLateBindableAccessExpression(node.expression); - } - return false; - } - function isTypeUsableAsIndexSignature(type: Type): boolean { return isTypeAssignableTo(type, stringNumberSymbolType); } @@ -51470,9 +51446,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return result; - function trackComputedName(accessExpression: EntityNameOrEntityNameExpression) { + function trackComputedName(accessExpression: EntityNameOrEntityNameExpression | ElementAccessExpression) { if (!tracker.trackSymbol) return; - // get symbol of the first identifier of the entityName + // get symbol of the first identifier of the entityName or element access chain const firstIdentifier = getFirstIdentifier(accessExpression); const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); if (name) { diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 66c2dbc00ebd9..5505ed184609d 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -32,6 +32,7 @@ import { declarationNameToString, Diagnostics, DiagnosticWithLocation, + ElementAccessExpression, EmitFlags, EmitHost, EmitResolver, @@ -118,6 +119,7 @@ import { isInternalDeclaration, isJSDocImportTag, isJsonSourceFile, + isLateBindableAccessExpression, isLateVisibilityPaintedStatement, isLiteralImportTypeNode, isMappedTypeNode, @@ -616,8 +618,11 @@ export function transformDeclarations(context: TransformationContext): Transform if (elem.kind === SyntaxKind.OmittedExpression) { return elem; } - if (elem.propertyName && isComputedPropertyName(elem.propertyName) && isEntityNameExpression(elem.propertyName.expression)) { - checkEntityNameVisibility(elem.propertyName.expression, enclosingDeclaration); + if (elem.propertyName && isComputedPropertyName(elem.propertyName)) { + const expr = elem.propertyName.expression; + if (isLateBindableAccessExpression(expr)) { + checkEntityNameVisibility(expr, enclosingDeclaration); + } } return factory.updateBindingElement( @@ -815,7 +820,7 @@ export function transformDeclarations(context: TransformationContext): Transform || isMappedTypeNode(node); } - function checkEntityNameVisibility(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node) { + function checkEntityNameVisibility(entityName: EntityNameOrEntityNameExpression | ElementAccessExpression, enclosingDeclaration: Node) { const visibilityResult = resolver.isEntityNameVisible(entityName, enclosingDeclaration); handleSymbolAccessibilityError(visibilityResult); } @@ -1012,16 +1017,16 @@ export function transformDeclarations(context: TransformationContext): Transform return; } else if ( - // Type declarations just need to double-check that the input computed name is an entity name expression + // Type declarations just need to double-check that the input computed name is a late-bindable access expression (isInterfaceDeclaration(input.parent) || isTypeLiteralNode(input.parent)) - && !isEntityNameExpression(input.name.expression) + && !isLateBindableAccessExpression(input.name.expression) ) { context.addDiagnostic(createDiagnosticForNode(input, Diagnostics.Computed_properties_must_be_number_or_string_literals_variables_or_dotted_expressions_with_isolatedDeclarations)); return; } } } - else if (!resolver.isLateBound(getParseTreeNode(input) as Declaration) || !isEntityNameExpression(input.name.expression)) { + else if (!resolver.isLateBound(getParseTreeNode(input) as Declaration) || !isLateBindableAccessExpression(input.name.expression)) { return; } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ef01015982139..76f8f14f76122 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1813,11 +1813,19 @@ export interface GeneratedPrivateIdentifier extends PrivateIdentifier { } /** @internal */ -// A name that supports late-binding (used in checker) +// A name that supports late-binding (used in checker). +// Supports both property access chains (a.b.c) and element access chains (a['b']['c']). export interface LateBoundName extends ComputedPropertyName { - readonly expression: EntityNameExpression; + readonly expression: EntityNameExpression | ElementAccessExpression; } +/** + * An expression that can be used in a late-bindable computed property name. + * Includes property access chains (a.b.c) and element access chains with literal keys (a['b']['c']). + * @internal + */ +export type LateBindableAccessExpression = EntityNameExpression | ElementAccessExpression; + export interface Decorator extends Node { readonly kind: SyntaxKind.Decorator; readonly parent: NamedDeclaration; @@ -5917,7 +5925,7 @@ export interface EmitResolver { createTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: NodeBuilderFlags, internalFlags: InternalNodeBuilderFlags, tracker: SymbolTracker): TypeNode | undefined; createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker): Expression; isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags | undefined, shouldComputeAliasToMarkVisible: boolean): SymbolAccessibilityResult; - isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult; + isEntityNameVisible(entityName: EntityNameOrEntityNameExpression | ElementAccessExpression, enclosingDeclaration: Node): SymbolVisibilityResult; // Returns the constant value this property access resolves to, or 'undefined' for a non-constant getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined; getEnumMemberValue(node: EnumMember): EvaluatorResult | undefined; @@ -10614,7 +10622,7 @@ export interface SyntacticTypeNodeBuilderResolver { getAllAccessorDeclarations(declaration: AccessorDeclaration): AllAccessorDeclarations; requiresAddingImplicitUndefined(declaration: ParameterDeclaration | PropertySignature | JSDocParameterTag | JSDocPropertyTag | PropertyDeclaration, symbol: Symbol | undefined, enclosingDeclaration: Node | undefined): boolean; isDefinitelyReferenceToGlobalSymbolObject(node: Node): boolean; - isEntityNameVisible(context: SyntacticTypeNodeBuilderContext, entityName: EntityNameOrEntityNameExpression, shouldComputeAliasToMakeVisible?: boolean): SymbolVisibilityResult; + isEntityNameVisible(context: SyntacticTypeNodeBuilderContext, entityName: EntityNameOrEntityNameExpression | ElementAccessExpression, shouldComputeAliasToMakeVisible?: boolean): SymbolVisibilityResult; serializeExistingTypeNode(context: SyntacticTypeNodeBuilderContext, node: TypeNode, addUndefined?: boolean): TypeNode | undefined; serializeReturnTypeForSignature(context: SyntacticTypeNodeBuilderContext, signatureDeclaration: SignatureDeclaration | JSDocSignature, symbol: Symbol | undefined): TypeNode | undefined; serializeTypeOfExpression(context: SyntacticTypeNodeBuilderContext, expr: Expression): TypeNode; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ba6b6d253a31c..b9a0286b174a0 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -412,6 +412,7 @@ import { LanguageVariant, last, lastOrUndefined, + LateBindableAccessExpression, LateVisibilityPaintedStatement, length, libMap, @@ -7454,8 +7455,13 @@ export function isEntityNameExpression(node: Node): node is EntityNameExpression return node.kind === SyntaxKind.Identifier || isPropertyAccessEntityNameExpression(node); } -/** @internal */ -export function getFirstIdentifier(node: EntityNameOrEntityNameExpression): Identifier { +/** + * Gets the first identifier in a name chain. + * Supports qualified names (a.b.c), property access expressions (a.b.c), + * and element access expressions (a['b']['c']). + * @internal + */ +export function getFirstIdentifier(node: EntityNameOrEntityNameExpression | ElementAccessExpression): Identifier { switch (node.kind) { case SyntaxKind.Identifier: return node; @@ -7466,14 +7472,43 @@ export function getFirstIdentifier(node: EntityNameOrEntityNameExpression): Iden while (node.kind !== SyntaxKind.Identifier); return node; case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + let expr: Expression = node; do { - node = node.expression; + expr = (expr as PropertyAccessExpression | ElementAccessExpression).expression; } - while (node.kind !== SyntaxKind.Identifier); - return node; + while (expr.kind !== SyntaxKind.Identifier); + return expr as Identifier; } } +/** + * Returns true if the expression is a valid late-bindable access expression. + * A late-bindable access expression is: + * - An Identifier + * - A PropertyAccessExpression where the base is a late-bindable access expression + * - An ElementAccessExpression with a string/numeric literal key where the base is a late-bindable access expression + * + * This supports mixed chains like: obj.a['b'].c['d'] + * Parentheses are skipped to support expressions like: (obj.a)['b'] + * @internal + */ +export function isLateBindableAccessExpression(node: Node): node is LateBindableAccessExpression { + node = skipParentheses(node as Expression); + + if (isIdentifier(node)) { + return true; + } + // For PropertyAccessExpression, require the name to be an Identifier (not PrivateIdentifier) + if (isPropertyAccessExpression(node) && isIdentifier(node.name)) { + return isLateBindableAccessExpression(node.expression); + } + if (isElementAccessExpression(node) && isStringOrNumericLiteralLike(skipParentheses(node.argumentExpression))) { + return isLateBindableAccessExpression(node.expression); + } + return false; +} + /** @internal */ export function isDottedName(node: Expression): boolean { return node.kind === SyntaxKind.Identifier diff --git a/tests/cases/compiler/enumKeysExportScenario.ts b/tests/cases/compiler/enumKeysExportScenario.ts new file mode 100644 index 0000000000000..266b23616fcf5 --- /dev/null +++ b/tests/cases/compiler/enumKeysExportScenario.ts @@ -0,0 +1,20 @@ +// @strict: true +// @declaration: true + +// Test that export scenario with enum bracket notation doesn't crash +// This tests the trackComputedName -> getFirstIdentifier path + +enum Type { + Foo = 'foo', + '3x14' = '3x14' +} + +// Export interface with bracket notation computed property +// This should trigger the tracker path +export interface TypeMap { + [Type['3x14']]: number; +} + +export type TypeMap2 = { + [Type['3x14']]: string; +} From 3d5199c2f31dad458ba30479db7c9848724c0162 Mon Sep 17 00:00:00 2001 From: Tu Shaokun <2801884530@qq.com> Date: Sat, 13 Dec 2025 17:32:33 +0800 Subject: [PATCH 5/5] fix: add baselines and handle parentheses in getFirstIdentifier - Add reference baselines for enumKeysExportScenario test - Fix getFirstIdentifier to skipParentheses when traversing access chains to handle cases like (obj.a)['b'] --- src/compiler/utilities.ts | 2 +- .../reference/enumKeysExportScenario.js | 46 ++++++++++++++++ .../reference/enumKeysExportScenario.symbols | 36 +++++++++++++ .../reference/enumKeysExportScenario.types | 52 +++++++++++++++++++ 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/enumKeysExportScenario.js create mode 100644 tests/baselines/reference/enumKeysExportScenario.symbols create mode 100644 tests/baselines/reference/enumKeysExportScenario.types diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b9a0286b174a0..14ea77ba45376 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7475,7 +7475,7 @@ export function getFirstIdentifier(node: EntityNameOrEntityNameExpression | Elem case SyntaxKind.ElementAccessExpression: let expr: Expression = node; do { - expr = (expr as PropertyAccessExpression | ElementAccessExpression).expression; + expr = skipParentheses((expr as PropertyAccessExpression | ElementAccessExpression).expression); } while (expr.kind !== SyntaxKind.Identifier); return expr as Identifier; diff --git a/tests/baselines/reference/enumKeysExportScenario.js b/tests/baselines/reference/enumKeysExportScenario.js new file mode 100644 index 0000000000000..8590e21799cbf --- /dev/null +++ b/tests/baselines/reference/enumKeysExportScenario.js @@ -0,0 +1,46 @@ +//// [tests/cases/compiler/enumKeysExportScenario.ts] //// + +//// [enumKeysExportScenario.ts] +// Test that export scenario with enum bracket notation doesn't crash +// This tests the trackComputedName -> getFirstIdentifier path + +enum Type { + Foo = 'foo', + '3x14' = '3x14' +} + +// Export interface with bracket notation computed property +// This should trigger the tracker path +export interface TypeMap { + [Type['3x14']]: number; +} + +export type TypeMap2 = { + [Type['3x14']]: string; +} + + +//// [enumKeysExportScenario.js] +"use strict"; +// Test that export scenario with enum bracket notation doesn't crash +// This tests the trackComputedName -> getFirstIdentifier path +Object.defineProperty(exports, "__esModule", { value: true }); +var Type; +(function (Type) { + Type["Foo"] = "foo"; + Type["3x14"] = "3x14"; +})(Type || (Type = {})); + + +//// [enumKeysExportScenario.d.ts] +declare enum Type { + Foo = "foo", + '3x14' = "3x14" +} +export interface TypeMap { + [Type['3x14']]: number; +} +export type TypeMap2 = { + [Type['3x14']]: string; +}; +export {}; diff --git a/tests/baselines/reference/enumKeysExportScenario.symbols b/tests/baselines/reference/enumKeysExportScenario.symbols new file mode 100644 index 0000000000000..ddd1b9c6431b0 --- /dev/null +++ b/tests/baselines/reference/enumKeysExportScenario.symbols @@ -0,0 +1,36 @@ +//// [tests/cases/compiler/enumKeysExportScenario.ts] //// + +=== enumKeysExportScenario.ts === +// Test that export scenario with enum bracket notation doesn't crash +// This tests the trackComputedName -> getFirstIdentifier path + +enum Type { +>Type : Symbol(Type, Decl(enumKeysExportScenario.ts, 0, 0)) + + Foo = 'foo', +>Foo : Symbol(Type.Foo, Decl(enumKeysExportScenario.ts, 3, 11)) + + '3x14' = '3x14' +>'3x14' : Symbol(Type['3x14'], Decl(enumKeysExportScenario.ts, 4, 16)) +} + +// Export interface with bracket notation computed property +// This should trigger the tracker path +export interface TypeMap { +>TypeMap : Symbol(TypeMap, Decl(enumKeysExportScenario.ts, 6, 1)) + + [Type['3x14']]: number; +>[Type['3x14']] : Symbol(TypeMap[Type['3x14']], Decl(enumKeysExportScenario.ts, 10, 26)) +>Type : Symbol(Type, Decl(enumKeysExportScenario.ts, 0, 0)) +>'3x14' : Symbol(Type['3x14'], Decl(enumKeysExportScenario.ts, 4, 16)) +} + +export type TypeMap2 = { +>TypeMap2 : Symbol(TypeMap2, Decl(enumKeysExportScenario.ts, 12, 1)) + + [Type['3x14']]: string; +>[Type['3x14']] : Symbol([Type['3x14']], Decl(enumKeysExportScenario.ts, 14, 24)) +>Type : Symbol(Type, Decl(enumKeysExportScenario.ts, 0, 0)) +>'3x14' : Symbol(Type['3x14'], Decl(enumKeysExportScenario.ts, 4, 16)) +} + diff --git a/tests/baselines/reference/enumKeysExportScenario.types b/tests/baselines/reference/enumKeysExportScenario.types new file mode 100644 index 0000000000000..de12eadc2fa3a --- /dev/null +++ b/tests/baselines/reference/enumKeysExportScenario.types @@ -0,0 +1,52 @@ +//// [tests/cases/compiler/enumKeysExportScenario.ts] //// + +=== enumKeysExportScenario.ts === +// Test that export scenario with enum bracket notation doesn't crash +// This tests the trackComputedName -> getFirstIdentifier path + +enum Type { +>Type : Type +> : ^^^^ + + Foo = 'foo', +>Foo : Type.Foo +> : ^^^^^^^^ +>'foo' : "foo" +> : ^^^^^ + + '3x14' = '3x14' +>'3x14' : (typeof Type)["3x14"] +> : ^^^^^^^^^^^^^^^^^^^^^ +>'3x14' : "3x14" +> : ^^^^^^ +} + +// Export interface with bracket notation computed property +// This should trigger the tracker path +export interface TypeMap { + [Type['3x14']]: number; +>[Type['3x14']] : number +> : ^^^^^^ +>Type['3x14'] : (typeof Type)["3x14"] +> : ^^^^^^^^^^^^^^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>'3x14' : "3x14" +> : ^^^^^^ +} + +export type TypeMap2 = { +>TypeMap2 : TypeMap2 +> : ^^^^^^^^ + + [Type['3x14']]: string; +>[Type['3x14']] : string +> : ^^^^^^ +>Type['3x14'] : (typeof Type)["3x14"] +> : ^^^^^^^^^^^^^^^^^^^^^ +>Type : typeof Type +> : ^^^^^^^^^^^ +>'3x14' : "3x14" +> : ^^^^^^ +} +