diff --git a/internal/bundler_tests/snapshots/snapshots_lower.txt b/internal/bundler_tests/snapshots/snapshots_lower.txt index e19692e0c7..deb16099df 100644 --- a/internal/bundler_tests/snapshots/snapshots_lower.txt +++ b/internal/bundler_tests/snapshots/snapshots_lower.txt @@ -436,7 +436,7 @@ class StaticPrivate { } ---------- /out/ts-assign/ts-assign.js ---------- -var _a, _b; +var _a, _b, _a2, __a; class Foo { #one = 1; get one() { @@ -483,30 +483,30 @@ class Foo { } class Normal { constructor() { - this.#a = b; + __privateAdd(this, _a2, b); this.c = d; } - #a; get a() { - return this.#a; + return __privateGet(this, _a2); } set a(_) { - this.#a = _; + __privateSet(this, _a2, _); } } +_a2 = new WeakMap(); class Private { constructor() { - this.#_a = b; + __privateAdd(this, __a, b); this.c = d; } - #_a; get #a() { - return this.#_a; + return __privateGet(this, __a); } set #a(_) { - this.#_a = _; + __privateSet(this, __a, _); } } +__a = new WeakMap(); class StaticNormal { static #a = b; static get a() { @@ -571,6 +571,7 @@ class StaticPrivate { } ---------- /out/ts-assign/ts-assign.js ---------- +var _a, __a; class Foo { accessor one = 1; accessor #two = 2; @@ -581,30 +582,30 @@ class Foo { } class Normal { constructor() { - this.#a = b; + __privateAdd(this, _a, b); this.c = d; } - #a; get a() { - return this.#a; + return __privateGet(this, _a); } set a(_) { - this.#a = _; + __privateSet(this, _a, _); } } +_a = new WeakMap(); class Private { constructor() { - this.#_a = b; + __privateAdd(this, __a, b); this.c = d; } - #_a; get #a() { - return this.#_a; + return __privateGet(this, __a); } set #a(_) { - this.#_a = _; + __privateSet(this, __a, _); } } +__a = new WeakMap(); class StaticNormal { static accessor a = b; static { diff --git a/internal/js_parser/js_parser_lower_class.go b/internal/js_parser/js_parser_lower_class.go index 88949a8ce9..89138bc1d9 100644 --- a/internal/js_parser/js_parser_lower_class.go +++ b/internal/js_parser/js_parser_lower_class.go @@ -1462,6 +1462,13 @@ func (ctx *lowerClassContext) rewriteAutoAccessorToGetSet( // Replace this accessor with other properties loc := keyExprNoSideEffects.Loc storagePrivate := &js_ast.EPrivateIdentifier{Ref: storageRef} + if mustLowerField { + // Forward the accessor's lowering status on to the storage field. If we + // don't do this, then we risk having the underlying private symbol + // behaving differently than if it were authored manually (e.g. being + // placed outside of the class body, which is a syntax error). + p.symbols[storageRef.InnerIndex].Flags |= ast.PrivateSymbolMustBeLowered + } storageNeedsToBeLowered := p.privateSymbolNeedsToBeLowered(storagePrivate) storageProp := js_ast.Property{ Loc: prop.Loc, diff --git a/internal/js_parser/js_parser_lower_test.go b/internal/js_parser/js_parser_lower_test.go index 2afc1586a6..0c29abfb2d 100644 --- a/internal/js_parser/js_parser_lower_test.go +++ b/internal/js_parser/js_parser_lower_test.go @@ -794,4 +794,105 @@ func TestLowerAutoAccessors(t *testing.T) { "class Foo {\n static #x = null;\n static get x() {\n return this.#x;\n }\n static set x(_) {\n this.#x = _;\n }\n}\n") expectPrintedWithUnsupportedFeatures(t, compat.Decorators, "class Foo { static accessor [x] = null }", "var _a;\nclass Foo {\n static #a = null;\n static get [_a = x]() {\n return this.#a;\n }\n static set [_a](_) {\n this.#a = _;\n }\n}\n") + + // Test various combinations of flags + expectPrintedWithUnsupportedFeatures(t, compat.Decorators|compat.ClassPrivateField, "class Foo { accessor x = null }", + `var _x; +class Foo { + constructor() { + __privateAdd(this, _x, null); + } + get x() { + return __privateGet(this, _x); + } + set x(_) { + __privateSet(this, _x, _); + } +} +_x = new WeakMap(); +`) + expectPrintedWithUnsupportedFeatures(t, compat.Decorators|compat.ClassPrivateStaticField, "class Foo { static accessor x = null }", + `var _x; +class Foo { + static get x() { + return __privateGet(this, _x); + } + static set x(_) { + __privateSet(this, _x, _); + } +} +_x = new WeakMap(); +__privateAdd(Foo, _x, null); +`) + expectPrintedWithUnsupportedFeatures(t, compat.Decorators|compat.ClassField|compat.ClassPrivateField, "class Foo { accessor x = null }", + `var _x; +class Foo { + constructor() { + __privateAdd(this, _x, null); + } + get x() { + return __privateGet(this, _x); + } + set x(_) { + __privateSet(this, _x, _); + } +} +_x = new WeakMap(); +`) + expectPrintedWithUnsupportedFeatures(t, compat.Decorators|compat.ClassStaticField|compat.ClassPrivateStaticField, "class Foo { static accessor x = null }", + `var _x; +class Foo { + static get x() { + return __privateGet(this, _x); + } + static set x(_) { + __privateSet(this, _x, _); + } +} +_x = new WeakMap(); +__privateAdd(Foo, _x, null); +`) + expectPrintedWithUnsupportedFeatures(t, compat.Decorators|compat.ClassField|compat.ClassPrivateField, "class Foo { accessor x = 1; static accessor y = 2 }", + `var _x, _y; +class Foo { + constructor() { + __privateAdd(this, _x, 1); + } + get x() { + return __privateGet(this, _x); + } + set x(_) { + __privateSet(this, _x, _); + } + static get y() { + return __privateGet(this, _y); + } + static set y(_) { + __privateSet(this, _y, _); + } +} +_x = new WeakMap(); +_y = new WeakMap(); +__privateAdd(Foo, _y, 2); +`) + expectPrintedWithUnsupportedFeatures(t, compat.Decorators|compat.ClassStaticField|compat.ClassPrivateStaticField, "class Foo { accessor x = 1; static accessor y = 2 }", + `var _y; +class Foo { + #x = 1; + get x() { + return this.#x; + } + set x(_) { + this.#x = _; + } + static get y() { + return __privateGet(this, _y); + } + static set y(_) { + __privateSet(this, _y, _); + } +} +_y = new WeakMap(); +__privateAdd(Foo, _y, 2); +`) } diff --git a/scripts/end-to-end-tests.js b/scripts/end-to-end-tests.js index a89469bffd..463e664f4d 100644 --- a/scripts/end-to-end-tests.js +++ b/scripts/end-to-end-tests.js @@ -5732,6 +5732,26 @@ for (let flags of [['--target=es2022'], ['--target=es6'], ['--bundle', '--target }`, }), + // Check various combinations of flags + test(['in.ts', '--outfile=node.js', '--supported:class-field=false'].concat(flags), { + 'in.ts': ` + class Foo { + accessor foo = 1 + static accessor bar = 2 + } + if (new Foo().foo !== 1 || Foo.bar !== 2) throw 'fail' + `, + }), + test(['in.ts', '--outfile=node.js', '--supported:class-static-field=false'].concat(flags), { + 'in.ts': ` + class Foo { + accessor foo = 1 + static accessor bar = 2 + } + if (new Foo().foo !== 1 || Foo.bar !== 2) throw 'fail' + `, + }), + // Make sure class body side effects aren't reordered test(['in.ts', '--outfile=node.js', '--supported:class-field=false'].concat(flags), { 'in.ts': `