Skip to content

Commit

Permalink
Fix decorator evaluation private environment (#16325)
Browse files Browse the repository at this point in the history
* enable field-initializers-after-methods-private test

* refactor: extract computed key memoisation

* handle computed static field in decorated class

* handle this and super call when moving computed key assignments

* handle other functional context

* fix: insert non-static class in the key of the wrapper

This change ensures that 1) non static computed key will share the same function context when moved into the non-static class 2) evaluations of decorators and computed keys will always precede static computed keys, given that they have been moved into computed key assignments before

* inject computed keys elements to the last non-comptued elements

so that we can still preserve the production params [yield], [await] inherited from the outer scope

* reorg complex AST factories

* add new test cases

* copy test cases to 2023-11-misc--to-es-2015

* update test outputs

* rewrite findLast usage for legacy node support

* make old node happy

* fix: use temp class fields to host decorator expr

* add class-in-for-loop test

* fix: skip decorators and key when replacing super within a class method

* copy tests to es2015

* address review comments

* fix ts errors

* enable the lastPublicElement insertion point for other decorator versions

* update test output

* ensure typescript test works in both Babel 8 and Babel 7

* make old node happy

* workaround pushContainer AST sync issue

* small tweaks

- rename some routines
- improve originalClassPath access, to avoid a full traversal
- add more jsdoc coments

* output: inject computed key assignments in the first public element

* update test output
  • Loading branch information
JLHwung committed Mar 6, 2024
1 parent ec0171e commit 206ebf3
Show file tree
Hide file tree
Showing 209 changed files with 3,756 additions and 1,761 deletions.
449 changes: 368 additions & 81 deletions packages/babel-helper-create-class-features-plugin/src/decorators.ts

Large diffs are not rendered by default.

86 changes: 55 additions & 31 deletions packages/babel-helper-create-class-features-plugin/src/misc.ts
Expand Up @@ -120,14 +120,60 @@ export function injectInitialization(
}
}

type ComputedKeyAssignmentExpression = t.AssignmentExpression & {
left: t.Identifier;
};

/**
* Try to memoise a computed key.
* It returns undefined when the computed key is an uid reference, otherwise
* an assignment expression `memoiserId = computed key`
* @export
* @param {t.Expression} keyNode Computed key
* @param {Scope} scope The scope where memoiser id should be registered
* @param {string} hint The memoiser id hint
* @returns {(ComputedKeyAssignmentExpression | undefined)}
*/
export function memoiseComputedKey(
keyNode: t.Expression,
scope: Scope,
hint: string,
): ComputedKeyAssignmentExpression | undefined {
const isUidReference = t.isIdentifier(keyNode) && scope.hasUid(keyNode.name);
if (isUidReference) {
return;
}
const isMemoiseAssignment =
t.isAssignmentExpression(keyNode, { operator: "=" }) &&
t.isIdentifier(keyNode.left) &&
scope.hasUid(keyNode.left.name);
if (isMemoiseAssignment) {
return t.cloneNode(keyNode as ComputedKeyAssignmentExpression);
} else {
const ident = t.identifier(hint);
// Declaring in the same block scope
// Ref: https://github.com/babel/babel/pull/10029/files#diff-fbbdd83e7a9c998721c1484529c2ce92
scope.push({
id: ident,
kind: "let",
});
return t.assignmentExpression(
"=",
t.cloneNode(ident),
keyNode,
) as ComputedKeyAssignmentExpression;
}
}

export function extractComputedKeys(
path: NodePath<t.Class>,
computedPaths: NodePath<t.ClassProperty | t.ClassMethod>[],
file: File,
) {
const { scope } = path;
const declarations: t.ExpressionStatement[] = [];
const state = {
classBinding: path.node.id && path.scope.getBinding(path.node.id.name),
classBinding: path.node.id && scope.getBinding(path.node.id.name),
file,
};
for (const computedPath of computedPaths) {
Expand All @@ -142,36 +188,14 @@ export function extractComputedKeys(
// Make sure computed property names are only evaluated once (upon class definition)
// and in the right order in combination with static properties
if (!computedKey.isConstantExpression()) {
const scope = path.scope;
const isUidReference =
t.isIdentifier(computedKey.node) && scope.hasUid(computedKey.node.name);
const isMemoiseAssignment =
computedKey.isAssignmentExpression({ operator: "=" }) &&
t.isIdentifier(computedKey.node.left) &&
scope.hasUid(computedKey.node.left.name);
if (isUidReference) {
continue;
} else if (isMemoiseAssignment) {
declarations.push(t.expressionStatement(t.cloneNode(computedNode.key)));
computedNode.key = t.cloneNode(
(computedNode.key as t.AssignmentExpression).left as t.Identifier,
);
} else {
const ident = path.scope.generateUidIdentifierBasedOnNode(
computedNode.key,
);
// Declaring in the same block scope
// Ref: https://github.com/babel/babel/pull/10029/files#diff-fbbdd83e7a9c998721c1484529c2ce92
scope.push({
id: ident,
kind: "let",
});
declarations.push(
t.expressionStatement(
t.assignmentExpression("=", t.cloneNode(ident), computedNode.key),
),
);
computedNode.key = t.cloneNode(ident);
const assignment = memoiseComputedKey(
computedKey.node,
scope,
scope.generateUidBasedOnNode(computedKey.node),
);
if (assignment) {
declarations.push(t.expressionStatement(assignment));
computedNode.key = t.cloneNode(assignment.left);
}
}
}
Expand Down
17 changes: 15 additions & 2 deletions packages/babel-helper-replace-supers/src/index.ts
Expand Up @@ -419,16 +419,29 @@ export default class ReplaceSupers {
}

replace() {
const { methodPath } = this;
// https://github.com/babel/babel/issues/11994
if (this.opts.refToPreserve) {
this.methodPath.traverse(unshadowSuperBindingVisitor, {
methodPath.traverse(unshadowSuperBindingVisitor, {
refName: this.opts.refToPreserve.name,
});
}

const handler = this.constantSuper ? looseHandlers : specHandlers;

memberExpressionToFunctions<ReplaceState>(this.methodPath, visitor, {
// todo: this should have been handled by the environmentVisitor,
// consider add visitSelf support for the path.traverse
// @ts-expect-error: Refine typings in packages/babel-traverse/src/types.ts
// shouldSkip is accepted in traverseNode
visitor.shouldSkip = (path: NodePath) => {
if (path.parentPath === methodPath) {
if (path.parentKey === "decorators" || path.parentKey === "key") {
return true;
}
}
};

memberExpressionToFunctions<ReplaceState>(methodPath, visitor, {
file: this.file,
scope: this.methodPath.scope,
isDerivedConstructor: this.isDerivedConstructor,
Expand Down
@@ -1,4 +1,5 @@
var _initStatic, _init_a, _init_a2, _get_a, _set_a, _init_computedKey, _init_computedKey2, _init_computedKey3, _init_computedKey4, _init_computedKey5, _init_computedKey6, _computedKey, _init_computedKey7, _Foo;
let _computedKey;
var _initStatic, _init_a, _init_a2, _get_a, _set_a, _init_computedKey, _init_computedKey2, _init_computedKey3, _init_computedKey4, _init_computedKey5, _init_computedKey6, _init_computedKey7, _Foo;
const logs = [];
const dec = (value, context) => {
logs.push(context.name);
Expand Down
@@ -1,4 +1,5 @@
var _initStatic, _init_a, _init_a2, _get_a, _set_a, _init_computedKey, _init_computedKey2, _init_computedKey3, _init_computedKey4, _init_computedKey5, _init_computedKey6, _computedKey, _init_computedKey7;
let _computedKey;
var _initStatic, _init_a, _init_a2, _get_a, _set_a, _init_computedKey, _init_computedKey2, _init_computedKey3, _init_computedKey4, _init_computedKey5, _init_computedKey6, _init_computedKey7;
const logs = [];
const dec = (value, context) => {
logs.push(context.name);
Expand All @@ -9,7 +10,6 @@ const f = () => {
[Symbol.toPrimitive]: () => (logs.push("calling toPrimitive"), "f()")
};
};
_computedKey = babelHelpers.toPropertyKey(f());
class Foo {
static {
[_init_a, _init_a2, _get_a, _set_a, _init_computedKey, _init_computedKey2, _init_computedKey3, _init_computedKey4, _init_computedKey5, _init_computedKey6, _init_computedKey7, _initStatic] = babelHelpers.applyDecs(this, [[dec, 6, "a"], [dec, 6, "a", function () {
Expand Down Expand Up @@ -76,7 +76,7 @@ class Foo {
this.#H = v;
}
static #I = _init_computedKey7(this);
static get [_computedKey]() {
static get [_computedKey = babelHelpers.toPropertyKey(f())]() {
return this.#I;
}
static set [_computedKey](v) {
Expand Down
@@ -1,73 +1,51 @@
var _initClass, _A, _temp, _initClass2, _C, _temp2, _initClass3, _D, _temp3, _initClass4, _decorated_class, _temp4, _Class2, _initClass5, _G, _temp5, _initClass6, _decorated_class2, _temp6, _Class3, _initClass7, _H, _temp7, _initClass8, _K, _temp8;
let _A2, _C2, _D2, _ref, _G2, _ref2, _H2, _K2;
var _initClass, _A, _Class, _A3, _initClass2, _C, _Class2, _C3, _initClass3, _D, _Class3, _D3, _initClass4, _decorated_class, _Class4, _Class5, _initClass5, _G, _Class6, _G3, _initClass6, _decorated_class2, _Class7, _Class8, _initClass7, _H, _Class9, _H3, _initClass8, _K, _Class10, _K3;
const dec = () => {};
const A = (new (_temp = class extends babelHelpers.identity {
const A = (new (_A2 = (_A3 = class A {}, [_A, _initClass] = babelHelpers.applyDecs(_A3, [], [dec]), _A3), (_Class = class extends babelHelpers.identity {
constructor() {
super(_A), (() => {})(), _initClass();
}
}, (_A2 => {
class A {}
_A2 = A;
[_A, _initClass] = babelHelpers.applyDecs(_A2, [], [dec]);
})(), _temp)(), _A);
const B = (new (_temp2 = class extends babelHelpers.identity {
}, babelHelpers.defineProperty(_Class, _A2, void 0), _Class))(), _A);
const B = (new (_C2 = (_C3 = class C {}, [_C, _initClass2] = babelHelpers.applyDecs(_C3, [], [dec]), _C3), (_Class2 = class extends babelHelpers.identity {
constructor() {
super(_C), (() => {})(), _initClass2();
}
}, (_C2 => {
class C {}
_C2 = C;
[_C, _initClass2] = babelHelpers.applyDecs(_C2, [], [dec]);
})(), _temp2)(), _C);
const D = (new (_temp3 = class extends babelHelpers.identity {
}, babelHelpers.defineProperty(_Class2, _C2, void 0), _Class2))(), _C);
const D = (new (_D2 = (_D3 = class D {}, [_D, _initClass3] = babelHelpers.applyDecs(_D3, [], [dec]), _D3), (_Class3 = class extends babelHelpers.identity {
constructor() {
super(_D), (() => {})(), _initClass3();
}
}, (_D2 => {
class D {}
_D2 = D;
[_D, _initClass3] = babelHelpers.applyDecs(_D2, [], [dec]);
})(), _temp3)(), _D);
const E = ((new (_temp4 = class extends babelHelpers.identity {
}, babelHelpers.defineProperty(_Class3, _D2, void 0), _Class3))(), _D);
const E = ((new (_ref = (_Class5 = class _ref {}, [_decorated_class, _initClass4] = babelHelpers.applyDecs(_Class5, [], [dec]), _Class5), (_Class4 = class extends babelHelpers.identity {
constructor() {
super(_decorated_class), (() => {})(), _initClass4();
}
}, (_Class2 = class {}, [_decorated_class, _initClass4] = babelHelpers.applyDecs(_Class2, [], [dec])), _temp4)(), _decorated_class), 123);
const F = [(new (_temp5 = class extends babelHelpers.identity {
}, babelHelpers.defineProperty(_Class4, _ref, void 0), _Class4))(), _decorated_class), 123);
const F = [(new (_G2 = (_G3 = class G {}, [_G, _initClass5] = babelHelpers.applyDecs(_G3, [], [dec]), _G3), (_Class6 = class extends babelHelpers.identity {
constructor() {
super(_G), (() => {})(), _initClass5();
}
}, (_G2 => {
class G {}
_G2 = G;
[_G, _initClass5] = babelHelpers.applyDecs(_G2, [], [dec]);
})(), _temp5)(), _G), (new (_temp6 = class extends babelHelpers.identity {
}, babelHelpers.defineProperty(_Class6, _G2, void 0), _Class6))(), _G), (new (_ref2 = (_Class8 = class _ref2 {}, [_decorated_class2, _initClass6] = babelHelpers.applyDecs(_Class8, [], [dec]), _Class8), (_Class7 = class extends babelHelpers.identity {
constructor() {
super(_decorated_class2), (() => {})(), _initClass6();
}
}, (_Class3 = class {}, [_decorated_class2, _initClass6] = babelHelpers.applyDecs(_Class3, [], [dec])), _temp6)(), _decorated_class2)];
const H = (new (_temp7 = class extends babelHelpers.identity {
}, babelHelpers.defineProperty(_Class7, _ref2, void 0), _Class7))(), _decorated_class2)];
const H = (new (_H2 = (_H3 = class H extends I {}, [_H, _initClass7] = babelHelpers.applyDecs(_H3, [], [dec]), _H3), (_Class9 = class extends babelHelpers.identity {
constructor() {
super(_H), (() => {})(), _initClass7();
}
}, (_H2 => {
class H extends I {}
_H2 = H;
[_H, _initClass7] = babelHelpers.applyDecs(_H2, [], [dec]);
})(), _temp7)(), _H);
const J = (new (_temp8 = class extends babelHelpers.identity {
}, babelHelpers.defineProperty(_Class9, _H2, void 0), _Class9))(), _H);
const J = (new (_K2 = (_K3 = class K extends L {}, [_K, _initClass8] = babelHelpers.applyDecs(_K3, [], [dec]), _K3), (_Class10 = class extends babelHelpers.identity {
constructor() {
super(_K), (() => {})(), _initClass8();
}
}, (_K2 => {
class K extends L {}
_K2 = K;
[_K, _initClass8] = babelHelpers.applyDecs(_K2, [], [dec]);
})(), _temp8)(), _K);
}, babelHelpers.defineProperty(_Class10, _K2, void 0), _Class10))(), _K);
function classFactory() {
var _initClass9, _decorated_class3, _temp9, _Class5;
return new (_temp9 = class extends babelHelpers.identity {
let _ref3;
var _initClass9, _decorated_class3, _Class11, _Class12;
return new (_ref3 = (_Class12 = class _ref3 {}, [_decorated_class3, _initClass9] = babelHelpers.applyDecs(_Class12, [], [dec]), _Class12), (_Class11 = class extends babelHelpers.identity {
constructor() {
super(_decorated_class3), (() => {})(), _initClass9();
}
}, (_Class5 = class {}, [_decorated_class3, _initClass9] = babelHelpers.applyDecs(_Class5, [], [dec])), _temp9)(), _decorated_class3;
}, babelHelpers.defineProperty(_Class11, _ref3, void 0), _Class11))(), _decorated_class3;
}
@@ -1,24 +1,17 @@
var _initClass, _temp, _initClass2, _temp2;
let _Foo2, _Bar2;
var _initClass, _Class, _Foo3, _initClass2, _Class2, _Bar3;
const dec = () => {};
let _Foo;
new (_temp = class extends babelHelpers.identity {
new (_Foo2 = (_Foo3 = class Foo {}, [_Foo, _initClass] = babelHelpers.applyDecs(_Foo3, [], [dec]), _Foo3), (_Class = class extends babelHelpers.identity {
constructor() {
(super(_Foo), babelHelpers.defineProperty(this, "field", 123)), _initClass();
}
}, (_Foo2 => {
class Foo {}
_Foo2 = Foo;
[_Foo, _initClass] = babelHelpers.applyDecs(_Foo2, [], [dec]);
})(), _temp)();
}, babelHelpers.defineProperty(_Class, _Foo2, void 0), _Class))();
let _Bar;
new (_temp2 = class extends babelHelpers.identity {
new (_Bar2 = (_Bar3 = class Bar extends _Foo {}, [_Bar, _initClass2] = babelHelpers.applyDecs(_Bar3, [], [dec]), _Bar3), (_Class2 = class extends babelHelpers.identity {
constructor() {
(super(_Bar), babelHelpers.defineProperty(this, "field", ((() => {
this.otherField = 456;
})(), 123))), _initClass2();
}
}, (_Bar2 => {
class Bar extends _Foo {}
_Bar2 = Bar;
[_Bar, _initClass2] = babelHelpers.applyDecs(_Bar2, [], [dec]);
})(), _temp2)();
}, babelHelpers.defineProperty(_Class2, _Bar2, void 0), _Class2))();
@@ -1,28 +1,25 @@
var _initClass, _x, _A, _Class_brand, _B, _temp;
let _Foo2;
var _initClass, _Class, _x, _A, _Class_brand, _B, _Foo3;
const dec = () => {};
let hasX, hasA, hasM;
let _Foo;
new (_x = /*#__PURE__*/new WeakMap(), _A = /*#__PURE__*/new WeakMap(), _Class_brand = /*#__PURE__*/new WeakSet(), _B = /*#__PURE__*/new WeakMap(), (_temp = class extends babelHelpers.identity {
new (_x = /*#__PURE__*/new WeakMap(), _A = /*#__PURE__*/new WeakMap(), _Class_brand = /*#__PURE__*/new WeakSet(), _B = /*#__PURE__*/new WeakMap(), _Foo2 = (_Foo3 = class Foo {
static get a() {
return babelHelpers.classPrivateFieldGet2(_B, this);
}
static set a(v) {
babelHelpers.classPrivateFieldSet2(_B, this, v);
}
static m() {}
}, [_Foo, _initClass] = babelHelpers.applyDecs(_Foo3, [], [dec]), _Foo3), (_Class = class extends babelHelpers.identity {
constructor() {
(super(_Foo), babelHelpers.classPrivateMethodInitSpec(this, _Class_brand), babelHelpers.classPrivateFieldInitSpec(this, _x, void 0), babelHelpers.classPrivateFieldInitSpec(this, _A, void 0), babelHelpers.defineProperty(this, "x", void 0), babelHelpers.classPrivateFieldInitSpec(this, _B, void 0), this), (() => {
hasX = o => _x.has(babelHelpers.checkInRHS(o));
hasA = o => _Class_brand.has(babelHelpers.checkInRHS(o));
hasM = o => _Class_brand.has(babelHelpers.checkInRHS(o));
})(), _initClass();
}
}, (_Foo2 => {
class Foo {
static get a() {
return babelHelpers.classPrivateFieldGet2(_B, this);
}
static set a(v) {
babelHelpers.classPrivateFieldSet2(_B, this, v);
}
static m() {}
}
_Foo2 = Foo;
[_Foo, _initClass] = babelHelpers.applyDecs(_Foo2, [], [dec]);
})(), _temp))();
}, babelHelpers.defineProperty(_Class, _Foo2, void 0), _Class))();
function _get_a(_this) {
return babelHelpers.classPrivateFieldGet2(_A, _this);
}
Expand Down
@@ -1,16 +1,13 @@
var _initClass, _temp;
let _Foo2;
var _initClass, _Class, _Foo3;
const dec = () => {};
let _Foo;
new (_temp = class extends babelHelpers.identity {
new (_Foo2 = (_Foo3 = class Foo {}, [_Foo, _initClass] = babelHelpers.applyDecs(_Foo3, [], [dec]), _Foo3), (_Class = class extends babelHelpers.identity {
constructor() {
(super(_Foo), babelHelpers.defineProperty(this, "field", ((() => {
this;
})(), this))), (() => {
this;
})(), _initClass();
}
}, (_Foo2 => {
class Foo {}
_Foo2 = Foo;
[_Foo, _initClass] = babelHelpers.applyDecs(_Foo2, [], [dec]);
})(), _temp)();
}, babelHelpers.defineProperty(_Class, _Foo2, void 0), _Class))();
@@ -1,13 +1,10 @@
var _initClass, _temp;
let _Foo2;
var _initClass, _Class, _Foo3;
const dec = () => {};
let _Foo;
new (_temp = class extends babelHelpers.identity {
new (_Foo2 = (_Foo3 = class Foo {}, [_Foo, _initClass] = babelHelpers.applyDecs(_Foo3, [], [dec]), _Foo3), (_Class = class extends babelHelpers.identity {
constructor() {
(super(_Foo), babelHelpers.defineProperty(this, "foo", new _Foo())), _initClass();
}
}, (_Foo2 => {
class Foo {}
_Foo2 = Foo;
[_Foo, _initClass] = babelHelpers.applyDecs(_Foo2, [], [dec]);
})(), _temp)();
}, babelHelpers.defineProperty(_Class, _Foo2, void 0), _Class))();
const foo = new _Foo();

0 comments on commit 206ebf3

Please sign in to comment.