Install static fields and private methods on the correct class

This commit is contained in:
Nicolò Ribaudo 2022-01-06 18:30:25 +01:00
parent 910e1a782a
commit e1c0086fe0
19 changed files with 447 additions and 88 deletions

View File

@ -2052,3 +2052,9 @@ if (!process.env.BABEL_8_BREAKING) {
} }
`; `;
} }
helpers.identity = helper("7.16.7")`
export default function _identity(x) {
return x;
}
`;

View File

@ -1,5 +1,5 @@
import type { NodePath } from "@babel/traverse"; import type { NodePath } from "@babel/traverse";
import { types as t } from "@babel/core"; import { types as t, template } from "@babel/core";
import syntaxDecorators from "@babel/plugin-syntax-decorators"; import syntaxDecorators from "@babel/plugin-syntax-decorators";
import ReplaceSupers from "@babel/helper-replace-supers"; import ReplaceSupers from "@babel/helper-replace-supers";
import * as charCodes from "charcodes"; import * as charCodes from "charcodes";
@ -447,6 +447,19 @@ function isClassDecoratableElementPath(
); );
} }
function staticBlockToIIFE(block: t.StaticBlock) {
return t.callExpression(
t.arrowFunctionExpression([], t.blockStatement(block.body)),
[],
);
}
function maybeSequenceExpression(exprs: t.Expression[]) {
if (exprs.length === 0) return t.unaryExpression("void", t.numericLiteral(0));
if (exprs.length === 1) return exprs[0];
return t.sequenceExpression(exprs);
}
function transformClass( function transformClass(
path: NodePath<t.ClassExpression | t.ClassDeclaration>, path: NodePath<t.ClassExpression | t.ClassDeclaration>,
state: any, state: any,
@ -836,38 +849,6 @@ function transformClass(
locals.push(staticInitLocal); locals.push(staticInitLocal);
} }
const staticBlock = t.staticBlock(
[
t.expressionStatement(
t.assignmentExpression(
"=",
t.arrayPattern(locals),
t.callExpression(state.addHelper("applyDecs"), [
t.thisExpression(),
elementDecorations,
classDecorations,
]),
),
),
requiresStaticInit &&
t.expressionStatement(
t.callExpression(t.cloneNode(staticInitLocal), [t.thisExpression()]),
),
].filter(v => v),
);
path.node.body.body.unshift(staticBlock as unknown as ClassElement);
if (classInitLocal) {
path.node.body.body.push(
t.staticBlock([
t.expressionStatement(
t.callExpression(t.cloneNode(classInitLocal), []),
),
]),
);
}
if (decoratedPrivateMethods.size > 0) { if (decoratedPrivateMethods.size > 0) {
path.traverse({ path.traverse({
PrivateName(path) { PrivateName(path) {
@ -902,6 +883,116 @@ function transformClass(
}); });
} }
let classInitInjected = false;
const classInitCall =
classInitLocal && t.callExpression(t.cloneNode(classInitLocal), []);
const originalClass = path.node;
if (classDecorators) {
const statics = [];
let staticBlocks: t.StaticBlock[] = [];
path.get("body.body").forEach(element => {
// Static blocks cannot be compiled to "instance blocks", but we can inline
// them as IIFEs in the next property.
if (element.isStaticBlock()) {
staticBlocks.push(element.node);
element.remove();
return;
}
const isProperty =
element.isClassProperty() || element.isClassPrivateProperty();
if (
(isProperty || element.isClassPrivateMethod()) &&
element.node.static
) {
if (isProperty && staticBlocks.length > 0) {
const allValues: t.Expression[] = staticBlocks.map(staticBlockToIIFE);
if (element.node.value) allValues.push(element.node.value);
element.node.value = maybeSequenceExpression(allValues);
staticBlocks = [];
}
element.node.static = false;
statics.push(element.node);
element.remove();
}
});
if (statics.length > 0 || staticBlocks.length > 0) {
const staticsClass = template.expression.ast`
class extends ${state.addHelper("identity")} {}
` as t.ClassExpression;
staticsClass.body.body = [
t.staticBlock([t.toStatement(path.node, false)]),
...statics,
];
const constructorBody: t.Expression[] = [];
const newExpr = t.newExpression(staticsClass, []);
if (staticBlocks.length > 0) {
constructorBody.push(...staticBlocks.map(staticBlockToIIFE));
}
if (classInitCall) {
classInitInjected = true;
constructorBody.push(classInitCall);
}
if (constructorBody.length > 0) {
constructorBody.unshift(
t.callExpression(t.super(), [t.cloneNode(classLocal)]),
);
staticsClass.body.body.push(
t.classMethod(
"constructor",
t.identifier("constructor"),
[],
t.blockStatement([
t.expressionStatement(t.sequenceExpression(constructorBody)),
]),
),
);
} else {
newExpr.arguments.push(t.cloneNode(classLocal));
}
path.replaceWith(newExpr);
}
}
if (!classInitInjected && classInitCall) {
path.node.body.body.push(
t.staticBlock([t.expressionStatement(classInitCall)]),
);
}
originalClass.body.body.unshift(
t.staticBlock(
[
t.expressionStatement(
t.assignmentExpression(
"=",
t.arrayPattern(locals),
t.callExpression(state.addHelper("applyDecs"), [
t.thisExpression(),
elementDecorations,
classDecorations,
]),
),
),
requiresStaticInit &&
t.expressionStatement(
t.callExpression(t.cloneNode(staticInitLocal), [
t.thisExpression(),
]),
),
].filter(Boolean),
),
);
// Recrawl the scope to make sure new identifiers are properly synced // Recrawl the scope to make sure new identifiers are properly synced
path.scope.crawl(); path.scope.crawl();

View File

@ -1,33 +1,37 @@
var _initClass, _initClass2; var _initClass, _temp2, _initClass2, _temp4;
let _Foo; let _Foo;
new (_temp2 = class extends babelHelpers.identity {
constructor() {
var _temp;
(_temp = super(_Foo), babelHelpers.defineProperty(this, "field", 123), _temp), _initClass();
}
}, (() => {
class Foo {} class Foo {}
(() => { (() => {
[_Foo, _initClass] = babelHelpers.applyDecs(Foo, [], [dec]); [_Foo, _initClass] = babelHelpers.applyDecs(Foo, [], [dec]);
})(); })();
})(), _temp2)();
babelHelpers.defineProperty(Foo, "field", 123);
(() => {
_initClass();
})();
let _Bar; let _Bar;
new (_temp4 = class extends babelHelpers.identity {
constructor() {
var _temp3;
(_temp3 = super(_Bar), babelHelpers.defineProperty(this, "field", ((() => {
this.otherField = 456;
})(), 123)), _temp3), _initClass2();
}
}, (() => {
class Bar extends _Foo {} class Bar extends _Foo {}
(() => { (() => {
[_Bar, _initClass2] = babelHelpers.applyDecs(Bar, [], [dec]); [_Bar, _initClass2] = babelHelpers.applyDecs(Bar, [], [dec]);
})(); })();
})(), _temp4)();
(() => {
Bar.otherField = 456;
})();
babelHelpers.defineProperty(Bar, "field", 123);
(() => {
_initClass2();
})();

View File

@ -0,0 +1,33 @@
let hasX, hasM, OriginalFoo;
class Bar {}
function dec(Foo) {
OriginalFoo = Foo;
return Bar;
}
@dec
class Foo {
static #x;
static #m() {}
static x;
static m() {}
static {
hasX = o => #x in o;
hasM = o => #m in o;
}
}
expect(hasX(Bar)).toBe(true);
expect(hasM(Bar)).toBe(true);
expect(hasX(OriginalFoo)).toBe(false);
expect(hasM(OriginalFoo)).toBe(false);
expect(Bar.hasOwnProperty("x")).toBe(true);
expect(OriginalFoo.hasOwnProperty("x")).toBe(false);
expect(Bar.hasOwnProperty("m")).toBe(false);
expect(OriginalFoo.hasOwnProperty("m")).toBe(true);

View File

@ -0,0 +1,15 @@
let hasX, hasM;
@dec
class Foo {
static #x;
static #m() {}
static x;
static m() {}
static {
hasX = o => #x in o;
hasM = o => #m in o;
}
}

View File

@ -0,0 +1,32 @@
var _initClass, _x, _m, _temp2;
let hasX, hasM;
let _Foo;
new (_temp2 = (_x = /*#__PURE__*/new WeakMap(), _m = /*#__PURE__*/new WeakSet(), class extends babelHelpers.identity {
constructor() {
var _temp;
(_temp = super(_Foo), babelHelpers.classPrivateMethodInitSpec(this, _m), babelHelpers.classPrivateFieldInitSpec(this, _x, {
writable: true,
value: void 0
}), babelHelpers.defineProperty(this, "x", void 0), _temp), (() => {
hasX = o => _x.has(o);
hasM = o => _m.has(o);
})(), _initClass();
}
}), (() => {
class Foo {
static m() {}
}
(() => {
[_Foo, _initClass] = babelHelpers.applyDecs(Foo, [], [dec]);
})();
})(), _temp2)();
function _m2() {}

View File

@ -0,0 +1,19 @@
class Bar {}
let _this, _this2, _this3;
@(() => Bar)
class Foo {
static {
_this = this;
}
static field = (_this2 = this);
static {
_this3 = this;
}
}
expect(_this).toBe(Bar);
expect(_this2).toBe(Bar);
expect(_this3).toBe(Bar);

View File

@ -0,0 +1,10 @@
@dec
class Foo {
static {
this
}
static field = this;
static {
this
}
}

View File

@ -0,0 +1,22 @@
var _initClass, _temp2;
let _Foo;
new (_temp2 = class extends babelHelpers.identity {
constructor() {
var _temp;
(_temp = super(_Foo), babelHelpers.defineProperty(this, "field", ((() => {
this;
})(), this)), _temp), (() => {
this;
})(), _initClass();
}
}, (() => {
class Foo {}
(() => {
[_Foo, _initClass] = babelHelpers.applyDecs(Foo, [], [dec]);
})();
})(), _temp2)();

View File

@ -1,17 +1,19 @@
var _initClass; var _initClass, _temp2;
let _Foo; let _Foo;
new (_temp2 = class extends babelHelpers.identity {
constructor() {
var _temp;
(_temp = super(_Foo), babelHelpers.defineProperty(this, "foo", new _Foo()), _temp), _initClass();
}
}, (() => {
class Foo {} class Foo {}
(() => { (() => {
[_Foo, _initClass] = babelHelpers.applyDecs(Foo, [], [dec]); [_Foo, _initClass] = babelHelpers.applyDecs(Foo, [], [dec]);
})(); })();
})(), _temp2)();
babelHelpers.defineProperty(Foo, "foo", new _Foo());
(() => {
_initClass();
})();
const foo = new _Foo(); const foo = new _Foo();

View File

@ -2,29 +2,40 @@ var _initClass, _initClass2;
let _Foo; let _Foo;
new class extends babelHelpers.identity {
static {
class Foo { class Foo {
static { static {
[_Foo, _initClass] = babelHelpers.applyDecs(this, [], [dec]); [_Foo, _initClass] = babelHelpers.applyDecs(this, [], [dec]);
} }
static field = 123; }
static {
_initClass();
} }
field = 123;
constructor() {
super(_Foo), _initClass();
} }
}();
let _Bar; let _Bar;
new class extends babelHelpers.identity {
static {
class Bar extends _Foo { class Bar extends _Foo {
static { static {
[_Bar, _initClass2] = babelHelpers.applyDecs(this, [], [dec]); [_Bar, _initClass2] = babelHelpers.applyDecs(this, [], [dec]);
} }
static {
this.otherField = 456;
} }
static field = 123;
static {
_initClass2();
} }
field = ((() => {
this.otherField = 456;
})(), 123);
constructor() {
super(_Bar), _initClass2();
} }
}();

View File

@ -0,0 +1,15 @@
let hasX, hasM;
@dec
class Foo {
static #x;
static #m() {}
static x;
static m() {}
static {
hasX = o => #x in o;
hasM = o => #m in o;
}
}

View File

@ -0,0 +1,33 @@
var _initClass;
let hasX, hasM;
let _Foo;
new class extends babelHelpers.identity {
static {
class Foo {
static {
[_Foo, _initClass] = babelHelpers.applyDecs(this, [], [dec]);
}
static m() {}
}
}
#x;
#m() {}
x;
constructor() {
super(_Foo), (() => {
hasX = o => #x in o;
hasM = o => #m in o;
})(), _initClass();
}
}();

View File

@ -0,0 +1,10 @@
@dec
class Foo {
static {
this
}
static field = this;
static {
this
}
}

View File

@ -0,0 +1,24 @@
var _initClass;
let _Foo;
new class extends babelHelpers.identity {
static {
class Foo {
static {
[_Foo, _initClass] = babelHelpers.applyDecs(this, [], [dec]);
}
}
}
field = ((() => {
this;
})(), this);
constructor() {
super(_Foo), (() => {
this;
})(), _initClass();
}
}();

View File

@ -2,15 +2,20 @@ var _initClass;
let _Foo; let _Foo;
new class extends babelHelpers.identity {
static {
class Foo { class Foo {
static { static {
[_Foo, _initClass] = babelHelpers.applyDecs(this, [], [dec]); [_Foo, _initClass] = babelHelpers.applyDecs(this, [], [dec]);
} }
static foo = new _Foo();
static {
_initClass();
}
} }
}
foo = new _Foo();
constructor() {
super(_Foo), _initClass();
}
}();
const foo = new _Foo(); const foo = new _Foo();

View File

@ -846,6 +846,15 @@
"./helpers/classPrivateMethodSet.js" "./helpers/classPrivateMethodSet.js"
], ],
"./helpers/esm/classPrivateMethodSet": "./helpers/esm/classPrivateMethodSet.js", "./helpers/esm/classPrivateMethodSet": "./helpers/esm/classPrivateMethodSet.js",
"./helpers/identity": [
{
"node": "./helpers/identity.js",
"import": "./helpers/esm/identity.js",
"default": "./helpers/identity.js"
},
"./helpers/identity.js"
],
"./helpers/esm/identity": "./helpers/esm/identity.js",
"./package": "./package.json", "./package": "./package.json",
"./package.json": "./package.json", "./package.json": "./package.json",
"./regenerator": "./regenerator/index.js", "./regenerator": "./regenerator/index.js",

View File

@ -845,6 +845,15 @@
"./helpers/classPrivateMethodSet.js" "./helpers/classPrivateMethodSet.js"
], ],
"./helpers/esm/classPrivateMethodSet": "./helpers/esm/classPrivateMethodSet.js", "./helpers/esm/classPrivateMethodSet": "./helpers/esm/classPrivateMethodSet.js",
"./helpers/identity": [
{
"node": "./helpers/identity.js",
"import": "./helpers/esm/identity.js",
"default": "./helpers/identity.js"
},
"./helpers/identity.js"
],
"./helpers/esm/identity": "./helpers/esm/identity.js",
"./package": "./package.json", "./package": "./package.json",
"./package.json": "./package.json", "./package.json": "./package.json",
"./regenerator": "./regenerator/index.js", "./regenerator": "./regenerator/index.js",

View File

@ -845,6 +845,15 @@
"./helpers/classPrivateMethodSet.js" "./helpers/classPrivateMethodSet.js"
], ],
"./helpers/esm/classPrivateMethodSet": "./helpers/esm/classPrivateMethodSet.js", "./helpers/esm/classPrivateMethodSet": "./helpers/esm/classPrivateMethodSet.js",
"./helpers/identity": [
{
"node": "./helpers/identity.js",
"import": "./helpers/esm/identity.js",
"default": "./helpers/identity.js"
},
"./helpers/identity.js"
],
"./helpers/esm/identity": "./helpers/esm/identity.js",
"./package": "./package.json", "./package": "./package.json",
"./package.json": "./package.json", "./package.json": "./package.json",
"./regenerator": "./regenerator/index.js", "./regenerator": "./regenerator/index.js",