From 6734016ae901d8af07a429d8c4907000bf607423 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 20:39:51 +0000 Subject: [PATCH 1/2] Initial plan From c5960d2a9dd61a50af92c35eb670ef45d4d5c6dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 21:36:48 +0000 Subject: [PATCH 2/2] Fix legacy decorator class name substitution in name positions (issue #4632) Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- .../tstransforms/legacydecorators.go | 90 +++++++++++++++---- ...cyDecoratorsObjectLiteralKeyNameAsClass.js | 88 ++++++++++++++++++ ...oratorsObjectLiteralKeyNameAsClass.symbols | 72 +++++++++++++++ ...ecoratorsObjectLiteralKeyNameAsClass.types | 80 +++++++++++++++++ ...cyDecoratorsObjectLiteralKeyNameAsClass.ts | 39 ++++++++ 5 files changed, 352 insertions(+), 17 deletions(-) create mode 100644 testdata/baselines/reference/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.js create mode 100644 testdata/baselines/reference/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.symbols create mode 100644 testdata/baselines/reference/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.types create mode 100644 testdata/tests/cases/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.ts diff --git a/internal/transformers/tstransforms/legacydecorators.go b/internal/transformers/tstransforms/legacydecorators.go index 22d0192e806..463af8a8514 100644 --- a/internal/transformers/tstransforms/legacydecorators.go +++ b/internal/transformers/tstransforms/legacydecorators.go @@ -1,6 +1,8 @@ package tstransforms import ( + "slices" + "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/binder" "github.com/microsoft/typescript-go/internal/collections" @@ -38,6 +40,8 @@ func (tx *LegacyDecoratorsTransformer) visit(node *ast.Node) *ast.Node { return tx.visitIdentifier(node.AsIdentifier()) case ast.KindPropertyAccessExpression: return tx.visitPropertyAccessExpression(node.AsPropertyAccessExpression()) + case ast.KindPropertyAssignment: + return tx.visitPropertyAssignment(node.AsPropertyAssignment()) case ast.KindDecorator: // Decorators are elided. They will be emitted as part of `visitClassDeclaration`. return nil @@ -91,6 +95,23 @@ func (tx *LegacyDecoratorsTransformer) visitPropertyAccessExpression(node *ast.P return node.AsNode() } +func (tx *LegacyDecoratorsTransformer) visitPropertyAssignment(node *ast.PropertyAssignment) *ast.Node { + // Visit the initializer but not the property key name, since the key name is not a value + // reference to the class. If the key is a computed property name, visit its expression. + name := node.Name() + var visitedName *ast.Node + if ast.IsComputedPropertyName(name) { + visitedName = tx.Visitor().VisitNode(name) + } else { + visitedName = name + } + initializer := tx.Visitor().VisitNode(node.Initializer) + if visitedName != name || initializer != node.Initializer { + return tx.Factory().UpdatePropertyAssignment(node, node.Modifiers(), visitedName, node.PostfixToken, node.Type, initializer) + } + return node.AsNode() +} + func elideNodes(f *printer.NodeFactory, nodes *ast.NodeList) *ast.NodeList { if nodes == nil { return nil @@ -152,16 +173,22 @@ func (tx *LegacyDecoratorsTransformer) visitParamerDeclaration(node *ast.Paramet // with decorators, a temporary value is stored for later use. func (tx *LegacyDecoratorsTransformer) visitPropertyNameOfClassElement(member *ast.Node) *ast.Node { name := member.Name() - if ast.IsComputedPropertyName(name) && ast.HasDecorators(member) { - expression := tx.Visitor().VisitNode(name.AsComputedPropertyName().Expression) - innerExpression := ast.SkipPartiallyEmittedExpressions(expression) - if !transformers.IsSimpleInlineableExpression(innerExpression) { - generatedName := tx.Factory().NewGeneratedNameForNode(name) - tx.EmitContext().AddVariableDeclaration(generatedName) - return tx.Factory().UpdateComputedPropertyName(name.AsComputedPropertyName(), tx.Factory().NewAssignmentExpression(generatedName.AsNode(), expression)) + if ast.IsComputedPropertyName(name) { + if ast.HasDecorators(member) { + expression := tx.Visitor().VisitNode(name.AsComputedPropertyName().Expression) + innerExpression := ast.SkipPartiallyEmittedExpressions(expression) + if !transformers.IsSimpleInlineableExpression(innerExpression) { + generatedName := tx.Factory().NewGeneratedNameForNode(name) + tx.EmitContext().AddVariableDeclaration(generatedName) + return tx.Factory().UpdateComputedPropertyName(name.AsComputedPropertyName(), tx.Factory().NewAssignmentExpression(generatedName.AsNode(), expression)) + } } + return tx.Visitor().VisitNode(name) } - return tx.Visitor().VisitNode(name) + // For non-computed names, return the name as-is. + // Non-computed property names are declaration names, not value references to the class, + // so they must not be substituted with the class alias. + return name } func (tx *LegacyDecoratorsTransformer) visitPropertyDeclaration(node *ast.PropertyDeclaration) *ast.Node { @@ -516,19 +543,48 @@ func (tx *LegacyDecoratorsTransformer) hasInternalStaticReference(node *ast.Clas if ast.IsIdentifier(n) && tx.referenceResolver.GetReferencedValueDeclaration(tx.EmitContext().MostOriginal(n)) == classNode { return true } - // For PropertyAccessExpression, only check the expression, not the name. - // The .Name() is a property access name, not a value reference to the class. - if ast.IsPropertyAccessExpression(n) { + // For nodes with declaration name positions or property key positions, skip the name + // and only check expression/value children. This prevents declaration names and + // property keys from being mistakenly treated as constructor references to the class. + switch n.Kind { + case ast.KindPropertyAccessExpression: + // Only check the expression, not the property name. return isOrContainsStaticSelfReference(n.Expression()) + case ast.KindPropertyAssignment: + // Only check the initializer (and computed key expression), not the property key name. + pa := n.AsPropertyAssignment() + if ast.IsComputedPropertyName(pa.Name()) { + return isOrContainsStaticSelfReference(pa.Name().AsComputedPropertyName().Expression) || + isOrContainsStaticSelfReference(pa.Initializer) + } + return isOrContainsStaticSelfReference(pa.Initializer) + case ast.KindPropertyDeclaration: + // Only check the initializer (and computed key expression), not the declaration name. + pd := n.AsPropertyDeclaration() + if ast.IsComputedPropertyName(pd.Name()) { + if isOrContainsStaticSelfReference(pd.Name().AsComputedPropertyName().Expression) { + return true + } + } + return pd.Initializer != nil && isOrContainsStaticSelfReference(pd.Initializer) + case ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor: + // Only check the computed key expression, parameters, and body, not the declaration name. + fn := n.FunctionLikeData() + if ast.IsComputedPropertyName(n.Name()) { + if isOrContainsStaticSelfReference(n.Name().AsComputedPropertyName().Expression) { + return true + } + } + if fn.Parameters != nil { + if slices.ContainsFunc(fn.Parameters.Nodes, isOrContainsStaticSelfReference) { + return true + } + } + return n.Body() != nil && isOrContainsStaticSelfReference(n.Body()) } return n.ForEachChild(isOrContainsStaticSelfReference) } - for _, member := range node.Members.Nodes { - if member.ForEachChild(isOrContainsStaticSelfReference) { - return true - } - } - return false + return slices.ContainsFunc(node.Members.Nodes, isOrContainsStaticSelfReference) } /** diff --git a/testdata/baselines/reference/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.js b/testdata/baselines/reference/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.js new file mode 100644 index 00000000000..78569e9ead1 --- /dev/null +++ b/testdata/baselines/reference/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.js @@ -0,0 +1,88 @@ +//// [tests/cases/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.ts] //// + +//// [legacyDecoratorsObjectLiteralKeyNameAsClass.ts] +// Repro from issue: class name used as object literal key should not be renamed to the alias + +const dec = (t: any) => t; + +@dec +class SessionAuth { + static requirement() { + return { SessionAuth: [] }; + } +} + +// Method shorthand name in object literal should not be renamed +@dec +class Foo { + static methods() { + return { + Foo() { return 1; } + }; + } +} + +// Class field name should not be renamed +@dec +class Bar { + Bar = 42; +} + +// Self-reference in expression positions SHOULD still be renamed +@dec +class SelfRef { + static instance = new SelfRef(); + method() { + return SelfRef; + } +} + + +//// [legacyDecoratorsObjectLiteralKeyNameAsClass.js] +"use strict"; +// Repro from issue: class name used as object literal key should not be renamed to the alias +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var SelfRef_1; +const dec = (t) => t; +let SessionAuth = class SessionAuth { + static requirement() { + return { SessionAuth: [] }; + } +}; +SessionAuth = __decorate([ + dec +], SessionAuth); +// Method shorthand name in object literal should not be renamed +let Foo = class Foo { + static methods() { + return { + Foo() { return 1; } + }; + } +}; +Foo = __decorate([ + dec +], Foo); +// Class field name should not be renamed +let Bar = class Bar { + Bar = 42; +}; +Bar = __decorate([ + dec +], Bar); +// Self-reference in expression positions SHOULD still be renamed +let SelfRef = class SelfRef { + static { SelfRef_1 = this; } + static instance = new SelfRef_1(); + method() { + return SelfRef_1; + } +}; +SelfRef = SelfRef_1 = __decorate([ + dec +], SelfRef); diff --git a/testdata/baselines/reference/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.symbols b/testdata/baselines/reference/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.symbols new file mode 100644 index 00000000000..a7ab653bae5 --- /dev/null +++ b/testdata/baselines/reference/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.symbols @@ -0,0 +1,72 @@ +//// [tests/cases/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.ts] //// + +=== legacyDecoratorsObjectLiteralKeyNameAsClass.ts === +// Repro from issue: class name used as object literal key should not be renamed to the alias + +const dec = (t: any) => t; +>dec : Symbol(dec, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 2, 5)) +>t : Symbol(t, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 2, 13)) +>t : Symbol(t, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 2, 13)) + +@dec +>dec : Symbol(dec, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 2, 5)) + +class SessionAuth { +>SessionAuth : Symbol(SessionAuth, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 2, 26)) + + static requirement() { +>requirement : Symbol(SessionAuth.requirement, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 5, 19)) + + return { SessionAuth: [] }; +>SessionAuth : Symbol(SessionAuth, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 7, 16)) + } +} + +// Method shorthand name in object literal should not be renamed +@dec +>dec : Symbol(dec, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 2, 5)) + +class Foo { +>Foo : Symbol(Foo, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 9, 1)) + + static methods() { +>methods : Symbol(Foo.methods, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 13, 11)) + + return { + Foo() { return 1; } +>Foo : Symbol(Foo, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 15, 16)) + + }; + } +} + +// Class field name should not be renamed +@dec +>dec : Symbol(dec, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 2, 5)) + +class Bar { +>Bar : Symbol(Bar, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 19, 1)) + + Bar = 42; +>Bar : Symbol(Bar.Bar, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 23, 11)) +} + +// Self-reference in expression positions SHOULD still be renamed +@dec +>dec : Symbol(dec, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 2, 5)) + +class SelfRef { +>SelfRef : Symbol(SelfRef, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 25, 1)) + + static instance = new SelfRef(); +>instance : Symbol(SelfRef.instance, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 29, 15)) +>SelfRef : Symbol(SelfRef, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 25, 1)) + + method() { +>method : Symbol(SelfRef.method, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 30, 36)) + + return SelfRef; +>SelfRef : Symbol(SelfRef, Decl(legacyDecoratorsObjectLiteralKeyNameAsClass.ts, 25, 1)) + } +} + diff --git a/testdata/baselines/reference/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.types b/testdata/baselines/reference/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.types new file mode 100644 index 00000000000..852e9221ab2 --- /dev/null +++ b/testdata/baselines/reference/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.types @@ -0,0 +1,80 @@ +//// [tests/cases/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.ts] //// + +=== legacyDecoratorsObjectLiteralKeyNameAsClass.ts === +// Repro from issue: class name used as object literal key should not be renamed to the alias + +const dec = (t: any) => t; +>dec : (t: any) => any +>(t: any) => t : (t: any) => any +>t : any +>t : any + +@dec +>dec : (t: any) => any + +class SessionAuth { +>SessionAuth : SessionAuth + + static requirement() { +>requirement : () => { SessionAuth: never[]; } + + return { SessionAuth: [] }; +>{ SessionAuth: [] } : { SessionAuth: never[]; } +>SessionAuth : never[] +>[] : never[] + } +} + +// Method shorthand name in object literal should not be renamed +@dec +>dec : (t: any) => any + +class Foo { +>Foo : Foo + + static methods() { +>methods : () => { Foo(): number; } + + return { +>{ Foo() { return 1; } } : { Foo(): number; } + + Foo() { return 1; } +>Foo : () => number +>1 : 1 + + }; + } +} + +// Class field name should not be renamed +@dec +>dec : (t: any) => any + +class Bar { +>Bar : Bar + + Bar = 42; +>Bar : number +>42 : 42 +} + +// Self-reference in expression positions SHOULD still be renamed +@dec +>dec : (t: any) => any + +class SelfRef { +>SelfRef : SelfRef + + static instance = new SelfRef(); +>instance : SelfRef +>new SelfRef() : SelfRef +>SelfRef : typeof SelfRef + + method() { +>method : () => typeof SelfRef + + return SelfRef; +>SelfRef : typeof SelfRef + } +} + diff --git a/testdata/tests/cases/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.ts b/testdata/tests/cases/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.ts new file mode 100644 index 00000000000..504b76d001f --- /dev/null +++ b/testdata/tests/cases/compiler/legacyDecoratorsObjectLiteralKeyNameAsClass.ts @@ -0,0 +1,39 @@ +// @target: esnext +// @experimentalDecorators: true +// @strict: true + +// Repro from issue: class name used as object literal key should not be renamed to the alias + +const dec = (t: any) => t; + +@dec +class SessionAuth { + static requirement() { + return { SessionAuth: [] }; + } +} + +// Method shorthand name in object literal should not be renamed +@dec +class Foo { + static methods() { + return { + Foo() { return 1; } + }; + } +} + +// Class field name should not be renamed +@dec +class Bar { + Bar = 42; +} + +// Self-reference in expression positions SHOULD still be renamed +@dec +class SelfRef { + static instance = new SelfRef(); + method() { + return SelfRef; + } +}