From 92ca31ce455189207894be9eef80d92136180122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 13 Mar 2024 11:28:30 -0400 Subject: [PATCH] fix: memoise computed keys in decorated class --- .../src/decorators.ts | 36 +++++- .../exec.js | 69 ++++++++++ .../exec.js | 34 ----- .../exec.js | 69 ++++++++++ .../exec.js | 118 ++++++++++++++++++ .../exec.js | 118 ++++++++++++++++++ 6 files changed, 409 insertions(+), 35 deletions(-) delete mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-classes/.replacement-static-private-environment-super-2/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-ordering--to-es2015/class-decorators-without-element-decorators/exec.js create mode 100644 packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-ordering/class-decorators-without-element-decorators/exec.js diff --git a/packages/babel-helper-create-class-features-plugin/src/decorators.ts b/packages/babel-helper-create-class-features-plugin/src/decorators.ts index e5fea47dbdf4..3ddb9581f065 100644 --- a/packages/babel-helper-create-class-features-plugin/src/decorators.ts +++ b/packages/babel-helper-create-class-features-plugin/src/decorators.ts @@ -1219,6 +1219,7 @@ function transformClass( let classDecorationsFlag = 0; let classDecorations: t.Expression[] = []; let classDecorationsId: t.Identifier; + let computedKeyAssignments: t.AssignmentExpression[] = []; if (classDecorators) { classInitLocal = generateLetUidIdentifier(scopeParent, "initClass"); needsDeclaraionForClassBinding = path.isClassDeclaration(); @@ -1249,6 +1250,40 @@ function transformClass( classAssignments, ); } + + if (!hasElementDecorators) { + // Sync body paths as non-decorated computed accessors have been transpiled + // to getter-setter pairs. + for (const element of path.get("body.body")) { + const { node } = element; + const isComputed = "computed" in node && node.computed; + if (isComputed) { + if (element.isClassProperty({ static: true })) { + if (!element.get("key").isConstantExpression()) { + const key = (node as t.ClassProperty).key; + const maybeAssignment = memoiseComputedKey( + key, + scopeParent, + scopeParent.generateUid("computedKey"), + ); + if (maybeAssignment != null) { + // If it is a static computed field within a decorated class, we move the computed key + // into `computedKeyAssignments` which will be then moved into the non-static class, + // to ensure that the evaluation order and private environment are correct + node.key = t.cloneNode(maybeAssignment.left); + computedKeyAssignments.push(maybeAssignment); + } + } + } else if (computedKeyAssignments.length > 0) { + prependExpressionsToComputedKey( + computedKeyAssignments, + element as NodePath, + ); + computedKeyAssignments = []; + } + } + } + } } else { if (!path.node.id) { path.node.id = path.scope.generateUidIdentifier("Class"); @@ -1259,7 +1294,6 @@ function transformClass( let lastInstancePrivateName: t.PrivateName; let needsInstancePrivateBrandCheck = false; - let computedKeyAssignments: t.AssignmentExpression[] = []; let fieldInitializerExpressions = []; let staticFieldInitializerExpressions: t.Expression[] = []; diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-classes--to-es2015/replacement-static-private-environment-super/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-classes--to-es2015/replacement-static-private-environment-super/exec.js index 3ef74c770f6a..b7b650a062ce 100644 --- a/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-classes--to-es2015/replacement-static-private-environment-super/exec.js +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-classes--to-es2015/replacement-static-private-environment-super/exec.js @@ -36,3 +36,72 @@ } expect([...A].map(fn => fn(b)).join()).toBe("B#x,B#x"); } + +{ + "different private/super access in a non-decorated static method"; + + let b; + + const idFactory = (hint) => { + return class { static ["id" + hint](v) { return v } } + } + + class C extends idFactory("C") { #x = "C#x"; } + + const dec = (b) => { + originalB = b; + return C; + } + + class A extends idFactory("A") { + #x = "A#x"; + static *[Symbol.iterator]() { + @dec + class B extends idFactory("B") { + #x = "B#x"; + static [(yield super.idA(o => o.#x), Symbol.iterator)] = function* () { + yield (o => o.#x); + } + } + b = new originalB(); + yield* B; + } + } + expect([...A].map(fn => fn(b)).join()).toBe("B#x,B#x"); +} + +{ + "different private/super access in a decorated static method"; + + const dummy = () => {}; + + let b; + + const idFactory = (hint) => { + return class { static ["id" + hint](v) { return v } } + } + + class C extends idFactory("C") { #x = "C#x"; } + + const dec = (b) => { + originalB = b; + return C; + } + + class A extends idFactory("A") { + #x = "A#x"; + static *[Symbol.iterator]() { + @dec + class B extends idFactory("B") { + #x = "B#x"; + @(yield super.idA(o => o.#x), dummy) + static [(yield super.idA(o => o.#x), Symbol.iterator)] = function* () { + yield (o => o.#x); + } + } + b = new originalB(); + yield* B; + } + } + expect([...A].map(fn => fn(b)).join()).toBe("B#x,B#x,B#x"); +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-classes/.replacement-static-private-environment-super-2/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-classes/.replacement-static-private-environment-super-2/exec.js deleted file mode 100644 index 3c87ec804631..000000000000 --- a/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-classes/.replacement-static-private-environment-super-2/exec.js +++ /dev/null @@ -1,34 +0,0 @@ -{ - "different private/super access in a static method"; - - let b; - - const idFactory = (hint) => { - return class { static ["id" + hint](v) { return v } } - } - - class C extends idFactory("C") { #x = "C#x"; } - - const dec = (b) => { - originalB = b; - return C; - } - - const fns = []; - - class A extends idFactory("A") { - #x = "A#x"; - static { - @dec - class B extends idFactory("B") { - #x = "B#x"; - static [(fns.push(super.idA(o => o.#x)), "iter")] = () => { - fns.push(o => o.#x); - } - } - b = new originalB(); - B.iter(); - } - } - expect(fns.map(fn => fn(b)).join()).toBe("B#x,B#x"); -} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-classes/replacement-static-private-environment-super/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-classes/replacement-static-private-environment-super/exec.js index 3ef74c770f6a..b7b650a062ce 100644 --- a/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-classes/replacement-static-private-environment-super/exec.js +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-classes/replacement-static-private-environment-super/exec.js @@ -36,3 +36,72 @@ } expect([...A].map(fn => fn(b)).join()).toBe("B#x,B#x"); } + +{ + "different private/super access in a non-decorated static method"; + + let b; + + const idFactory = (hint) => { + return class { static ["id" + hint](v) { return v } } + } + + class C extends idFactory("C") { #x = "C#x"; } + + const dec = (b) => { + originalB = b; + return C; + } + + class A extends idFactory("A") { + #x = "A#x"; + static *[Symbol.iterator]() { + @dec + class B extends idFactory("B") { + #x = "B#x"; + static [(yield super.idA(o => o.#x), Symbol.iterator)] = function* () { + yield (o => o.#x); + } + } + b = new originalB(); + yield* B; + } + } + expect([...A].map(fn => fn(b)).join()).toBe("B#x,B#x"); +} + +{ + "different private/super access in a decorated static method"; + + const dummy = () => {}; + + let b; + + const idFactory = (hint) => { + return class { static ["id" + hint](v) { return v } } + } + + class C extends idFactory("C") { #x = "C#x"; } + + const dec = (b) => { + originalB = b; + return C; + } + + class A extends idFactory("A") { + #x = "A#x"; + static *[Symbol.iterator]() { + @dec + class B extends idFactory("B") { + #x = "B#x"; + @(yield super.idA(o => o.#x), dummy) + static [(yield super.idA(o => o.#x), Symbol.iterator)] = function* () { + yield (o => o.#x); + } + } + b = new originalB(); + yield* B; + } + } + expect([...A].map(fn => fn(b)).join()).toBe("B#x,B#x,B#x"); +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-ordering--to-es2015/class-decorators-without-element-decorators/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-ordering--to-es2015/class-decorators-without-element-decorators/exec.js new file mode 100644 index 000000000000..8f39f3ddf6e8 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-ordering--to-es2015/class-decorators-without-element-decorators/exec.js @@ -0,0 +1,118 @@ +const classDec1 = (log) => (cls, ctxClass) => { + log.push("c2"); + ctxClass.addInitializer(() => log.push("c5")); + ctxClass.addInitializer(() => log.push("c6")); +}; +const classDec2 = (log) => (cls, ctxClass) => { + log.push("c1"); + ctxClass.addInitializer(() => log.push("c3")); + ctxClass.addInitializer(() => log.push("c4")); +}; + +{ + const log = []; + + @classDec1(log) + @classDec2(log) + class C { + [log.push("k1")]; + [log.push("k2")]; + [log.push("k3")]; + } + + expect(log.join()).toBe( + "k1,k2,k3," + // ClassElementEvaluation + "c1,c2," + // ApplyDecoratorsToClassDefinition + "c3,c4,c5,c6", // classExtraInitializers + ); +} + +{ + const log = []; + + @classDec1(log) + @classDec2(log) + class C { + static [log.push("k1")]; + async [log.push("k2")](v) {}; + [log.push("k3")]; + } + + expect(log.join()).toBe( + "k1,k2,k3," + // ClassElementEvaluation + "c1,c2," + // ApplyDecoratorsToClassDefinition + "c3,c4,c5,c6", // classExtraInitializers + ); +} + +{ + const log = []; + + @classDec1(log) + @classDec2(log) + class C { + get [log.push("k1")]() {}; + static [log.push("k2")]; + [log.push("k3")]; + } + + expect(log.join()).toBe( + "k1,k2,k3," + // ClassElementEvaluation + "c1,c2," + // ApplyDecoratorsToClassDefinition + "c3,c4,c5,c6", // classExtraInitializers + ); +} + +{ + const log = []; + + @classDec1(log) + @classDec2(log) + class C { + [log.push("k1")]; + accessor [log.push("k2")]; + static [log.push("k3")]; + } + + expect(log.join()).toBe( + "k1,k2,k3," + // ClassElementEvaluation + "c1,c2," + // ApplyDecoratorsToClassDefinition + "c3,c4,c5,c6", // classExtraInitializers + ); +} + +{ + const log = []; + + @classDec1(log) + @classDec2(log) + class C { + static set [log.push("k1")](v) {}; + [log.push("k2")]; + static [log.push("k3")]; + } + + expect(log.join()).toBe( + "k1,k2,k3," + // ClassElementEvaluation + "c1,c2," + // ApplyDecoratorsToClassDefinition + "c3,c4,c5,c6", // classExtraInitializers + ); +} + +{ + const log = []; + + @classDec1(log) + @classDec2(log) + class C { + static [log.push("k1")]; + static [log.push("k2")]; + static [log.push("k3")]; + } + + expect(log.join()).toBe( + "k1,k2,k3," + // ClassElementEvaluation + "c1,c2," + // ApplyDecoratorsToClassDefinition + "c3,c4,c5,c6", // classExtraInitializers + ); +} diff --git a/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-ordering/class-decorators-without-element-decorators/exec.js b/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-ordering/class-decorators-without-element-decorators/exec.js new file mode 100644 index 000000000000..8f39f3ddf6e8 --- /dev/null +++ b/packages/babel-plugin-proposal-decorators/test/fixtures/2023-11-ordering/class-decorators-without-element-decorators/exec.js @@ -0,0 +1,118 @@ +const classDec1 = (log) => (cls, ctxClass) => { + log.push("c2"); + ctxClass.addInitializer(() => log.push("c5")); + ctxClass.addInitializer(() => log.push("c6")); +}; +const classDec2 = (log) => (cls, ctxClass) => { + log.push("c1"); + ctxClass.addInitializer(() => log.push("c3")); + ctxClass.addInitializer(() => log.push("c4")); +}; + +{ + const log = []; + + @classDec1(log) + @classDec2(log) + class C { + [log.push("k1")]; + [log.push("k2")]; + [log.push("k3")]; + } + + expect(log.join()).toBe( + "k1,k2,k3," + // ClassElementEvaluation + "c1,c2," + // ApplyDecoratorsToClassDefinition + "c3,c4,c5,c6", // classExtraInitializers + ); +} + +{ + const log = []; + + @classDec1(log) + @classDec2(log) + class C { + static [log.push("k1")]; + async [log.push("k2")](v) {}; + [log.push("k3")]; + } + + expect(log.join()).toBe( + "k1,k2,k3," + // ClassElementEvaluation + "c1,c2," + // ApplyDecoratorsToClassDefinition + "c3,c4,c5,c6", // classExtraInitializers + ); +} + +{ + const log = []; + + @classDec1(log) + @classDec2(log) + class C { + get [log.push("k1")]() {}; + static [log.push("k2")]; + [log.push("k3")]; + } + + expect(log.join()).toBe( + "k1,k2,k3," + // ClassElementEvaluation + "c1,c2," + // ApplyDecoratorsToClassDefinition + "c3,c4,c5,c6", // classExtraInitializers + ); +} + +{ + const log = []; + + @classDec1(log) + @classDec2(log) + class C { + [log.push("k1")]; + accessor [log.push("k2")]; + static [log.push("k3")]; + } + + expect(log.join()).toBe( + "k1,k2,k3," + // ClassElementEvaluation + "c1,c2," + // ApplyDecoratorsToClassDefinition + "c3,c4,c5,c6", // classExtraInitializers + ); +} + +{ + const log = []; + + @classDec1(log) + @classDec2(log) + class C { + static set [log.push("k1")](v) {}; + [log.push("k2")]; + static [log.push("k3")]; + } + + expect(log.join()).toBe( + "k1,k2,k3," + // ClassElementEvaluation + "c1,c2," + // ApplyDecoratorsToClassDefinition + "c3,c4,c5,c6", // classExtraInitializers + ); +} + +{ + const log = []; + + @classDec1(log) + @classDec2(log) + class C { + static [log.push("k1")]; + static [log.push("k2")]; + static [log.push("k3")]; + } + + expect(log.join()).toBe( + "k1,k2,k3," + // ClassElementEvaluation + "c1,c2," + // ApplyDecoratorsToClassDefinition + "c3,c4,c5,c6", // classExtraInitializers + ); +}