Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 73 additions & 17 deletions internal/transformers/tstransforms/legacydecorators.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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))
}
}

Original file line number Diff line number Diff line change
@@ -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
}
}

Loading