diff --git a/tsc/internal/transformers/estransforms/classfields.go b/tsc/internal/transformers/estransforms/classfields.go index bf1b79fc3225f..6b3b5aa4482d2 100644 --- a/tsc/internal/transformers/estransforms/classfields.go +++ b/tsc/internal/transformers/estransforms/classfields.go @@ -267,7 +267,7 @@ func (tx *classFieldsTransformer) visitForSubstitution(node *ast.Node) *ast.Node return tx.visitIdentifier(node.AsIdentifier()) } if node.Kind == ast.KindPropertyAccessExpression && ast.IsIdentifier(node.AsPropertyAccessExpression().Name()) { - return tx.visitPropertyAccessExpressionForSubstitution(node.AsPropertyAccessExpression()) + return tx.visitPropertyAccessExpressionForSubstitution(node.AsPropertyAccessExpression(), tx.substitutionVisitor) } return tx.substitutionVisitor.VisitEachChild(node) } @@ -1097,7 +1097,7 @@ func (tx *classFieldsTransformer) visitPropertyAccessExpression(node *ast.Proper // Private identifier names are still visited through VisitEachChild so they can be // transformed by visitPrivateIdentifier. if ast.IsIdentifier(node.Name()) { - return tx.visitPropertyAccessExpressionForSubstitution(node) + return tx.visitPropertyAccessExpressionForSubstitution(node, tx.Visitor()) } return tx.Visitor().VisitEachChild(node.AsNode()) } @@ -1105,8 +1105,8 @@ func (tx *classFieldsTransformer) visitPropertyAccessExpression(node *ast.Proper // visitPropertyAccessExpressionForSubstitution visits only the expression of a PropertyAccessExpression, // leaving the name unchanged. This prevents the name from being treated as a standalone identifier // reference and incorrectly substituted with a class alias. -func (tx *classFieldsTransformer) visitPropertyAccessExpressionForSubstitution(node *ast.PropertyAccessExpression) *ast.Node { - expression := tx.Visitor().VisitNode(node.Expression) +func (tx *classFieldsTransformer) visitPropertyAccessExpressionForSubstitution(node *ast.PropertyAccessExpression, visitor *ast.NodeVisitor) *ast.Node { + expression := visitor.VisitNode(node.Expression) if expression != node.Expression { return tx.Factory().UpdatePropertyAccessExpression(node, expression, node.QuestionDotToken, node.Name(), node.Flags) } diff --git a/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2021).js b/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2021).js new file mode 100644 index 0000000000000..cde7164e2b360 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2021).js @@ -0,0 +1,65 @@ +//// [tests/cases/conformance/classes/classStaticBlock/classStaticBlock29.ts] //// + +//// [classStaticBlock29.ts] +// https://github.com/microsoft/TypeScript/issues/64733 + +// Inside a downleveled `static {}` block, `this` refers to the class and is rewritten +// to the class alias, but `this` inside a regular function nested in the block refers +// to the function's own receiver and must be kept. +class Foo { + declare static count: number; + declare toObject: () => number; + declare getBar: () => () => number; + declare getIndexed: () => number; + + bar() { + return 1; + } + + static { + // Referencing the class by name makes the downleveled block use a class alias. + Foo.count = 0; + this.count++; + Foo.prototype.toObject = function (this: Foo) { + return this.bar(); + }; + Foo.prototype.getBar = function (this: Foo) { + return () => this.bar(); + }; + this.prototype.getIndexed = function (this: Foo) { + return this["bar"](); + }; + } +} + +new Foo().toObject(); + + +//// [classStaticBlock29.js] +"use strict"; +// https://github.com/microsoft/TypeScript/issues/64733 +var _a; +// Inside a downleveled `static {}` block, `this` refers to the class and is rewritten +// to the class alias, but `this` inside a regular function nested in the block refers +// to the function's own receiver and must be kept. +class Foo { + bar() { + return 1; + } +} +_a = Foo; +(() => { + // Referencing the class by name makes the downleveled block use a class alias. + _a.count = 0; + _a.count++; + _a.prototype.toObject = function () { + return this.bar(); + }; + _a.prototype.getBar = function () { + return () => this.bar(); + }; + _a.prototype.getIndexed = function () { + return this["bar"](); + }; +})(); +new Foo().toObject(); diff --git a/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2021).symbols b/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2021).symbols new file mode 100644 index 0000000000000..f5bbf7452fef5 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2021).symbols @@ -0,0 +1,93 @@ +//// [tests/cases/conformance/classes/classStaticBlock/classStaticBlock29.ts] //// + +=== classStaticBlock29.ts === +// https://github.com/microsoft/TypeScript/issues/64733 + +// Inside a downleveled `static {}` block, `this` refers to the class and is rewritten +// to the class alias, but `this` inside a regular function nested in the block refers +// to the function's own receiver and must be kept. +class Foo { +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) + + declare static count: number; +>count : Symbol(Foo.count, Decl(classStaticBlock29.ts, 5, 11)) + + declare toObject: () => number; +>toObject : Symbol(Foo.toObject, Decl(classStaticBlock29.ts, 6, 33)) + + declare getBar: () => () => number; +>getBar : Symbol(Foo.getBar, Decl(classStaticBlock29.ts, 7, 35)) + + declare getIndexed: () => number; +>getIndexed : Symbol(Foo.getIndexed, Decl(classStaticBlock29.ts, 8, 39)) + + bar() { +>bar : Symbol(Foo.bar, Decl(classStaticBlock29.ts, 9, 37)) + + return 1; + } + + static { + // Referencing the class by name makes the downleveled block use a class alias. + Foo.count = 0; +>Foo.count : Symbol(Foo.count, Decl(classStaticBlock29.ts, 5, 11)) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) +>count : Symbol(Foo.count, Decl(classStaticBlock29.ts, 5, 11)) + + this.count++; +>this.count : Symbol(Foo.count, Decl(classStaticBlock29.ts, 5, 11)) +>this : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) +>count : Symbol(Foo.count, Decl(classStaticBlock29.ts, 5, 11)) + + Foo.prototype.toObject = function (this: Foo) { +>Foo.prototype.toObject : Symbol(Foo.toObject, Decl(classStaticBlock29.ts, 6, 33)) +>Foo.prototype : Symbol(Foo.prototype) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) +>prototype : Symbol(Foo.prototype) +>toObject : Symbol(Foo.toObject, Decl(classStaticBlock29.ts, 6, 33)) +>this : Symbol(this, Decl(classStaticBlock29.ts, 19, 43)) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) + + return this.bar(); +>this.bar : Symbol(Foo.bar, Decl(classStaticBlock29.ts, 9, 37)) +>this : Symbol(this, Decl(classStaticBlock29.ts, 19, 43)) +>bar : Symbol(Foo.bar, Decl(classStaticBlock29.ts, 9, 37)) + + }; + Foo.prototype.getBar = function (this: Foo) { +>Foo.prototype.getBar : Symbol(Foo.getBar, Decl(classStaticBlock29.ts, 7, 35)) +>Foo.prototype : Symbol(Foo.prototype) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) +>prototype : Symbol(Foo.prototype) +>getBar : Symbol(Foo.getBar, Decl(classStaticBlock29.ts, 7, 35)) +>this : Symbol(this, Decl(classStaticBlock29.ts, 22, 41)) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) + + return () => this.bar(); +>this.bar : Symbol(Foo.bar, Decl(classStaticBlock29.ts, 9, 37)) +>this : Symbol(this, Decl(classStaticBlock29.ts, 22, 41)) +>bar : Symbol(Foo.bar, Decl(classStaticBlock29.ts, 9, 37)) + + }; + this.prototype.getIndexed = function (this: Foo) { +>this.prototype.getIndexed : Symbol(Foo.getIndexed, Decl(classStaticBlock29.ts, 8, 39)) +>this.prototype : Symbol(Foo.prototype) +>this : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) +>prototype : Symbol(Foo.prototype) +>getIndexed : Symbol(Foo.getIndexed, Decl(classStaticBlock29.ts, 8, 39)) +>this : Symbol(this, Decl(classStaticBlock29.ts, 25, 46)) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) + + return this["bar"](); +>this : Symbol(this, Decl(classStaticBlock29.ts, 25, 46)) +>"bar" : Symbol(Foo.bar, Decl(classStaticBlock29.ts, 9, 37)) + + }; + } +} + +new Foo().toObject(); +>new Foo().toObject : Symbol(Foo.toObject, Decl(classStaticBlock29.ts, 6, 33)) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) +>toObject : Symbol(Foo.toObject, Decl(classStaticBlock29.ts, 6, 33)) + diff --git a/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2021).types b/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2021).types new file mode 100644 index 0000000000000..7261cbe60051f --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2021).types @@ -0,0 +1,107 @@ +//// [tests/cases/conformance/classes/classStaticBlock/classStaticBlock29.ts] //// + +=== classStaticBlock29.ts === +// https://github.com/microsoft/TypeScript/issues/64733 + +// Inside a downleveled `static {}` block, `this` refers to the class and is rewritten +// to the class alias, but `this` inside a regular function nested in the block refers +// to the function's own receiver and must be kept. +class Foo { +>Foo : Foo + + declare static count: number; +>count : number + + declare toObject: () => number; +>toObject : () => number + + declare getBar: () => () => number; +>getBar : () => () => number + + declare getIndexed: () => number; +>getIndexed : () => number + + bar() { +>bar : () => number + + return 1; +>1 : 1 + } + + static { + // Referencing the class by name makes the downleveled block use a class alias. + Foo.count = 0; +>Foo.count = 0 : 0 +>Foo.count : number +>Foo : typeof Foo +>count : number +>0 : 0 + + this.count++; +>this.count++ : number +>this.count : number +>this : typeof Foo +>count : number + + Foo.prototype.toObject = function (this: Foo) { +>Foo.prototype.toObject = function (this: Foo) { return this.bar(); } : (this: Foo) => number +>Foo.prototype.toObject : () => number +>Foo.prototype : Foo +>Foo : typeof Foo +>prototype : Foo +>toObject : () => number +>function (this: Foo) { return this.bar(); } : (this: Foo) => number +>this : Foo + + return this.bar(); +>this.bar() : number +>this.bar : () => number +>this : Foo +>bar : () => number + + }; + Foo.prototype.getBar = function (this: Foo) { +>Foo.prototype.getBar = function (this: Foo) { return () => this.bar(); } : (this: Foo) => () => number +>Foo.prototype.getBar : () => () => number +>Foo.prototype : Foo +>Foo : typeof Foo +>prototype : Foo +>getBar : () => () => number +>function (this: Foo) { return () => this.bar(); } : (this: Foo) => () => number +>this : Foo + + return () => this.bar(); +>() => this.bar() : () => number +>this.bar() : number +>this.bar : () => number +>this : Foo +>bar : () => number + + }; + this.prototype.getIndexed = function (this: Foo) { +>this.prototype.getIndexed = function (this: Foo) { return this["bar"](); } : (this: Foo) => number +>this.prototype.getIndexed : () => number +>this.prototype : Foo +>this : typeof Foo +>prototype : Foo +>getIndexed : () => number +>function (this: Foo) { return this["bar"](); } : (this: Foo) => number +>this : Foo + + return this["bar"](); +>this["bar"]() : number +>this["bar"] : () => number +>this : Foo +>"bar" : "bar" + + }; + } +} + +new Foo().toObject(); +>new Foo().toObject() : number +>new Foo().toObject : () => number +>new Foo() : Foo +>Foo : typeof Foo +>toObject : () => number + diff --git a/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2022).js b/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2022).js new file mode 100644 index 0000000000000..717d25cf01f88 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2022).js @@ -0,0 +1,63 @@ +//// [tests/cases/conformance/classes/classStaticBlock/classStaticBlock29.ts] //// + +//// [classStaticBlock29.ts] +// https://github.com/microsoft/TypeScript/issues/64733 + +// Inside a downleveled `static {}` block, `this` refers to the class and is rewritten +// to the class alias, but `this` inside a regular function nested in the block refers +// to the function's own receiver and must be kept. +class Foo { + declare static count: number; + declare toObject: () => number; + declare getBar: () => () => number; + declare getIndexed: () => number; + + bar() { + return 1; + } + + static { + // Referencing the class by name makes the downleveled block use a class alias. + Foo.count = 0; + this.count++; + Foo.prototype.toObject = function (this: Foo) { + return this.bar(); + }; + Foo.prototype.getBar = function (this: Foo) { + return () => this.bar(); + }; + this.prototype.getIndexed = function (this: Foo) { + return this["bar"](); + }; + } +} + +new Foo().toObject(); + + +//// [classStaticBlock29.js] +"use strict"; +// https://github.com/microsoft/TypeScript/issues/64733 +// Inside a downleveled `static {}` block, `this` refers to the class and is rewritten +// to the class alias, but `this` inside a regular function nested in the block refers +// to the function's own receiver and must be kept. +class Foo { + bar() { + return 1; + } + static { + // Referencing the class by name makes the downleveled block use a class alias. + Foo.count = 0; + this.count++; + Foo.prototype.toObject = function () { + return this.bar(); + }; + Foo.prototype.getBar = function () { + return () => this.bar(); + }; + this.prototype.getIndexed = function () { + return this["bar"](); + }; + } +} +new Foo().toObject(); diff --git a/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2022).symbols b/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2022).symbols new file mode 100644 index 0000000000000..f5bbf7452fef5 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2022).symbols @@ -0,0 +1,93 @@ +//// [tests/cases/conformance/classes/classStaticBlock/classStaticBlock29.ts] //// + +=== classStaticBlock29.ts === +// https://github.com/microsoft/TypeScript/issues/64733 + +// Inside a downleveled `static {}` block, `this` refers to the class and is rewritten +// to the class alias, but `this` inside a regular function nested in the block refers +// to the function's own receiver and must be kept. +class Foo { +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) + + declare static count: number; +>count : Symbol(Foo.count, Decl(classStaticBlock29.ts, 5, 11)) + + declare toObject: () => number; +>toObject : Symbol(Foo.toObject, Decl(classStaticBlock29.ts, 6, 33)) + + declare getBar: () => () => number; +>getBar : Symbol(Foo.getBar, Decl(classStaticBlock29.ts, 7, 35)) + + declare getIndexed: () => number; +>getIndexed : Symbol(Foo.getIndexed, Decl(classStaticBlock29.ts, 8, 39)) + + bar() { +>bar : Symbol(Foo.bar, Decl(classStaticBlock29.ts, 9, 37)) + + return 1; + } + + static { + // Referencing the class by name makes the downleveled block use a class alias. + Foo.count = 0; +>Foo.count : Symbol(Foo.count, Decl(classStaticBlock29.ts, 5, 11)) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) +>count : Symbol(Foo.count, Decl(classStaticBlock29.ts, 5, 11)) + + this.count++; +>this.count : Symbol(Foo.count, Decl(classStaticBlock29.ts, 5, 11)) +>this : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) +>count : Symbol(Foo.count, Decl(classStaticBlock29.ts, 5, 11)) + + Foo.prototype.toObject = function (this: Foo) { +>Foo.prototype.toObject : Symbol(Foo.toObject, Decl(classStaticBlock29.ts, 6, 33)) +>Foo.prototype : Symbol(Foo.prototype) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) +>prototype : Symbol(Foo.prototype) +>toObject : Symbol(Foo.toObject, Decl(classStaticBlock29.ts, 6, 33)) +>this : Symbol(this, Decl(classStaticBlock29.ts, 19, 43)) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) + + return this.bar(); +>this.bar : Symbol(Foo.bar, Decl(classStaticBlock29.ts, 9, 37)) +>this : Symbol(this, Decl(classStaticBlock29.ts, 19, 43)) +>bar : Symbol(Foo.bar, Decl(classStaticBlock29.ts, 9, 37)) + + }; + Foo.prototype.getBar = function (this: Foo) { +>Foo.prototype.getBar : Symbol(Foo.getBar, Decl(classStaticBlock29.ts, 7, 35)) +>Foo.prototype : Symbol(Foo.prototype) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) +>prototype : Symbol(Foo.prototype) +>getBar : Symbol(Foo.getBar, Decl(classStaticBlock29.ts, 7, 35)) +>this : Symbol(this, Decl(classStaticBlock29.ts, 22, 41)) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) + + return () => this.bar(); +>this.bar : Symbol(Foo.bar, Decl(classStaticBlock29.ts, 9, 37)) +>this : Symbol(this, Decl(classStaticBlock29.ts, 22, 41)) +>bar : Symbol(Foo.bar, Decl(classStaticBlock29.ts, 9, 37)) + + }; + this.prototype.getIndexed = function (this: Foo) { +>this.prototype.getIndexed : Symbol(Foo.getIndexed, Decl(classStaticBlock29.ts, 8, 39)) +>this.prototype : Symbol(Foo.prototype) +>this : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) +>prototype : Symbol(Foo.prototype) +>getIndexed : Symbol(Foo.getIndexed, Decl(classStaticBlock29.ts, 8, 39)) +>this : Symbol(this, Decl(classStaticBlock29.ts, 25, 46)) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) + + return this["bar"](); +>this : Symbol(this, Decl(classStaticBlock29.ts, 25, 46)) +>"bar" : Symbol(Foo.bar, Decl(classStaticBlock29.ts, 9, 37)) + + }; + } +} + +new Foo().toObject(); +>new Foo().toObject : Symbol(Foo.toObject, Decl(classStaticBlock29.ts, 6, 33)) +>Foo : Symbol(Foo, Decl(classStaticBlock29.ts, 0, 0)) +>toObject : Symbol(Foo.toObject, Decl(classStaticBlock29.ts, 6, 33)) + diff --git a/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2022).types b/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2022).types new file mode 100644 index 0000000000000..7261cbe60051f --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/classStaticBlock29(target=es2022).types @@ -0,0 +1,107 @@ +//// [tests/cases/conformance/classes/classStaticBlock/classStaticBlock29.ts] //// + +=== classStaticBlock29.ts === +// https://github.com/microsoft/TypeScript/issues/64733 + +// Inside a downleveled `static {}` block, `this` refers to the class and is rewritten +// to the class alias, but `this` inside a regular function nested in the block refers +// to the function's own receiver and must be kept. +class Foo { +>Foo : Foo + + declare static count: number; +>count : number + + declare toObject: () => number; +>toObject : () => number + + declare getBar: () => () => number; +>getBar : () => () => number + + declare getIndexed: () => number; +>getIndexed : () => number + + bar() { +>bar : () => number + + return 1; +>1 : 1 + } + + static { + // Referencing the class by name makes the downleveled block use a class alias. + Foo.count = 0; +>Foo.count = 0 : 0 +>Foo.count : number +>Foo : typeof Foo +>count : number +>0 : 0 + + this.count++; +>this.count++ : number +>this.count : number +>this : typeof Foo +>count : number + + Foo.prototype.toObject = function (this: Foo) { +>Foo.prototype.toObject = function (this: Foo) { return this.bar(); } : (this: Foo) => number +>Foo.prototype.toObject : () => number +>Foo.prototype : Foo +>Foo : typeof Foo +>prototype : Foo +>toObject : () => number +>function (this: Foo) { return this.bar(); } : (this: Foo) => number +>this : Foo + + return this.bar(); +>this.bar() : number +>this.bar : () => number +>this : Foo +>bar : () => number + + }; + Foo.prototype.getBar = function (this: Foo) { +>Foo.prototype.getBar = function (this: Foo) { return () => this.bar(); } : (this: Foo) => () => number +>Foo.prototype.getBar : () => () => number +>Foo.prototype : Foo +>Foo : typeof Foo +>prototype : Foo +>getBar : () => () => number +>function (this: Foo) { return () => this.bar(); } : (this: Foo) => () => number +>this : Foo + + return () => this.bar(); +>() => this.bar() : () => number +>this.bar() : number +>this.bar : () => number +>this : Foo +>bar : () => number + + }; + this.prototype.getIndexed = function (this: Foo) { +>this.prototype.getIndexed = function (this: Foo) { return this["bar"](); } : (this: Foo) => number +>this.prototype.getIndexed : () => number +>this.prototype : Foo +>this : typeof Foo +>prototype : Foo +>getIndexed : () => number +>function (this: Foo) { return this["bar"](); } : (this: Foo) => number +>this : Foo + + return this["bar"](); +>this["bar"]() : number +>this["bar"] : () => number +>this : Foo +>"bar" : "bar" + + }; + } +} + +new Foo().toObject(); +>new Foo().toObject() : number +>new Foo().toObject : () => number +>new Foo() : Foo +>Foo : typeof Foo +>toObject : () => number + diff --git a/tsc/testdata/tests/cases/conformance/classes/classStaticBlock/classStaticBlock29.ts b/tsc/testdata/tests/cases/conformance/classes/classStaticBlock/classStaticBlock29.ts new file mode 100644 index 0000000000000..3a6e9f2dbea8b --- /dev/null +++ b/tsc/testdata/tests/cases/conformance/classes/classStaticBlock/classStaticBlock29.ts @@ -0,0 +1,33 @@ +// @target: es2022, es2021 +// https://github.com/microsoft/TypeScript/issues/64733 + +// Inside a downleveled `static {}` block, `this` refers to the class and is rewritten +// to the class alias, but `this` inside a regular function nested in the block refers +// to the function's own receiver and must be kept. +class Foo { + declare static count: number; + declare toObject: () => number; + declare getBar: () => () => number; + declare getIndexed: () => number; + + bar() { + return 1; + } + + static { + // Referencing the class by name makes the downleveled block use a class alias. + Foo.count = 0; + this.count++; + Foo.prototype.toObject = function (this: Foo) { + return this.bar(); + }; + Foo.prototype.getBar = function (this: Foo) { + return () => this.bar(); + }; + this.prototype.getIndexed = function (this: Foo) { + return this["bar"](); + }; + } +} + +new Foo().toObject();