Compile static blocks without the intermediate priv field step (#13297)

* Remove ordering constraints for `static-blocks` plugin

* Handle static blocks directly in `helper-create-class-features-plugin`
This commit is contained in:
Nicolò Ribaudo 2021-05-14 17:35:59 +02:00 committed by GitHub
parent b3d35cd412
commit 8732dd39c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 168 additions and 184 deletions

View File

@ -6,6 +6,7 @@ export const FEATURES = Object.freeze({
privateMethods: 1 << 2,
decorators: 1 << 3,
privateIn: 1 << 4,
staticBlocks: 1 << 5,
});
const featuresSameLoose = new Map([
@ -144,8 +145,8 @@ export function verifyUsedFeatures(path, file) {
}
}
// NOTE: We can't use path.isPrivateMethod() because it isn't supported in <7.2.0
if (path.isPrivate() && path.isMethod()) {
// NOTE: path.isPrivateMethod() it isn't supported in <7.2.0
if (path.isPrivateMethod?.()) {
if (!hasFeature(file, FEATURES.privateMethods)) {
throw path.buildCodeFrameError("Class private methods are not enabled.");
}
@ -170,4 +171,13 @@ export function verifyUsedFeatures(path, file) {
throw path.buildCodeFrameError("Class fields are not enabled.");
}
}
if (path.isStaticBlock?.()) {
if (!hasFeature(file, FEATURES.staticBlocks)) {
throw path.buildCodeFrameError(
"Static class blocks are not enabled. " +
"Please add `@babel/plugin-proposal-class-static-block` to your configuration.",
);
}
}
}

View File

@ -669,7 +669,14 @@ const thisContextVisitor = traverse.visitors.merge([
environmentVisitor,
]);
function replaceThisContext(path, ref, superRef, file, constantSuper) {
function replaceThisContext(
path,
ref,
superRef,
file,
isStaticBlock,
constantSuper,
) {
const state = { classRef: ref, needsClassRef: false };
const replacer = new ReplaceSupers({
@ -680,13 +687,13 @@ function replaceThisContext(path, ref, superRef, file, constantSuper) {
refToPreserve: ref,
getObjectRef() {
state.needsClassRef = true;
return path.node.static
return isStaticBlock || path.node.static
? ref
: t.memberExpression(ref, t.identifier("prototype"));
},
});
replacer.replace();
if (path.isProperty()) {
if (isStaticBlock || path.isProperty()) {
path.traverse(thisContextVisitor, state);
}
return state.needsClassRef;
@ -717,19 +724,26 @@ export function buildFieldsInitNodes(
const isPublic = !isPrivate;
const isField = prop.isProperty();
const isMethod = !isField;
const isStaticBlock = prop.isStaticBlock?.();
if (isStatic || (isMethod && isPrivate)) {
if (isStatic || (isMethod && isPrivate) || isStaticBlock) {
const replaced = replaceThisContext(
prop,
ref,
superRef,
state,
isStaticBlock,
constantSuper,
);
needsClassRef = needsClassRef || replaced;
}
switch (true) {
case isStaticBlock:
staticNodes.push(
template.statement.ast`(() => ${t.blockStatement(prop.node.body)})()`,
);
break;
case isStatic && isPrivate && isField && privateFieldsAsProperties:
needsClassRef = true;
staticNodes.push(

View File

@ -146,24 +146,16 @@ export function createClassFeaturePlugin({
constructor = path;
} else {
elements.push(path);
if (path.isProperty() || path.isPrivate()) {
if (
path.isProperty() ||
path.isPrivate() ||
path.isStaticBlock?.()
) {
props.push(path);
}
}
if (!isDecorated) isDecorated = hasOwnDecorators(path.node);
if (path.isStaticBlock?.()) {
throw path.buildCodeFrameError(`Incorrect plugin order, \`@babel/plugin-proposal-class-static-block\` should be placed before class features plugins
{
"plugins": [
"@babel/plugin-proposal-class-static-block",
"@babel/plugin-proposal-private-property-in-object",
"@babel/plugin-proposal-private-methods",
"@babel/plugin-proposal-class-properties",
]
}`);
}
}
if (!props.length && !isDecorated) return;

View File

@ -0,0 +1,4 @@
class A {
#x;
static {}
}

View File

@ -0,0 +1,4 @@
{
"plugins": ["proposal-class-properties", "syntax-class-static-block"],
"throws": "Static class blocks are not enabled. Please add `@babel/plugin-proposal-class-static-block` to your configuration."
}

View File

@ -281,7 +281,8 @@ export default class ReplaceSupers {
this.methodPath = path;
this.isDerivedConstructor =
path.isClassMethod({ kind: "constructor" }) && !!opts.superRef;
this.isStatic = path.isObjectMethod() || path.node.static;
this.isStatic =
path.isObjectMethod() || path.node.static || path.isStaticBlock?.();
this.isPrivateMethod = path.isPrivate() && path.isMethod();
this.file = opts.file;

View File

@ -19,6 +19,7 @@
"babel-plugin"
],
"dependencies": {
"@babel/helper-create-class-features-plugin": "workspace:^7.13.11",
"@babel/helper-plugin-utils": "workspace:^7.13.0",
"@babel/plugin-syntax-class-static-block": "workspace:^7.12.13"
},

View File

@ -1,6 +1,11 @@
import { declare } from "@babel/helper-plugin-utils";
import syntaxClassStaticBlock from "@babel/plugin-syntax-class-static-block";
import {
enableFeature,
FEATURES,
} from "@babel/helper-create-class-features-plugin";
/**
* Generate a uid that is not in `denyList`
*
@ -25,10 +30,19 @@ export default declare(({ types: t, template, assertVersion }) => {
return {
name: "proposal-class-static-block",
inherits: syntaxClassStaticBlock,
pre() {
// Enable this in @babel/helper-create-class-features-plugin, so that it
// can be handled by the private fields and methods transform.
enableFeature(this.file, FEATURES.staticBlocks, /* loose */ false);
},
visitor: {
Class(path: NodePath<Class>) {
const { scope } = path;
const classBody = path.get("body");
// Run on ClassBody and not on class so that if @babel/helper-create-class-features-plugin
// is enabled it can generte optimized output without passing from the intermediate
// private fields representation.
ClassBody(classBody: NodePath<Class>) {
const { scope } = classBody;
const privateNames = new Set();
const body = classBody.get("body");
for (const path of body) {

View File

@ -1,12 +1,9 @@
var _ = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_");
class Foo {}
Foo.bar = 42;
Object.defineProperty(Foo, _, {
writable: true,
value: (() => {
Foo.foo = Foo.bar;
})()
});
(() => {
Foo.foo = Foo.bar;
})();
expect(Foo.foo).toBe(42);

View File

@ -1,12 +1,9 @@
var _ = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_");
class Foo {}
Foo.bar = 42;
Object.defineProperty(Foo, _, {
writable: true,
value: (() => {
Foo.foo = Foo.bar;
})()
});
(() => {
Foo.foo = Foo.bar;
})();
expect(Foo.foo).toBe(42);

View File

@ -1,23 +1,13 @@
var _class, _2, _temp, _class2, _3, _temp2;
var _class, _temp, _class2, _temp2;
var _ = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_");
class Foo extends (_temp = _class = class extends (_temp2 = _class2 = class Base {}, (() => {
_class2.qux = 21;
})(), _temp2) {}, (() => {
_class.bar = 21;
})(), _temp) {}
class Foo extends (_temp = (_2 = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_"), _class = class extends (_temp2 = (_3 = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_"), _class2 = class Base {}), Object.defineProperty(_class2, _3, {
writable: true,
value: (() => {
_class2.qux = 21;
})()
}), _temp2) {}), Object.defineProperty(_class, _2, {
writable: true,
value: (() => {
_class.bar = 21;
})()
}), _temp) {}
(() => {
Foo.foo = Foo.bar + Foo.qux;
})();
Object.defineProperty(Foo, _, {
writable: true,
value: (() => {
Foo.foo = Foo.bar + Foo.qux;
})()
});
expect(Foo.foo).toBe(42);

View File

@ -1,26 +1,19 @@
var _bar = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("bar");
var _ = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_");
var _2 = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_2");
class Foo {}
Object.defineProperty(Foo, _bar, {
writable: true,
value: 21
});
Object.defineProperty(Foo, _, {
writable: true,
value: (() => {
Foo.foo = babelHelpers.classPrivateFieldLooseBase(Foo, _bar)[_bar];
Foo.qux1 = Foo.qux;
})()
});
(() => {
Foo.foo = babelHelpers.classPrivateFieldLooseBase(Foo, _bar)[_bar];
Foo.qux1 = Foo.qux;
})();
Foo.qux = 21;
Object.defineProperty(Foo, _2, {
writable: true,
value: (() => {
Foo.qux2 = Foo.qux;
})()
});
(() => {
Foo.qux2 = Foo.qux;
})();

View File

@ -1,17 +1,14 @@
var _ = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_");
var _2 = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_2");
class Foo {}
Object.defineProperty(Foo, _, {
writable: true,
value: 42
});
Object.defineProperty(Foo, _2, {
writable: true,
value: (() => {
Foo.foo = babelHelpers.classPrivateFieldLooseBase(Foo, _)[_];
})()
});
(() => {
Foo.foo = babelHelpers.classPrivateFieldLooseBase(Foo, _)[_];
})();
expect(Foo.foo).toBe(42);

View File

@ -1,26 +1,17 @@
var _class, _2, _temp;
var _class, _temp;
var _ = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_");
class Foo extends (_temp = (_2 = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_"), _class = class {}), Object.defineProperty(_class, _2, {
writable: true,
value: (() => {
_class.bar = 42;
})()
}), _temp) {}
class Foo extends (_temp = _class = class {}, (() => {
_class.bar = 42;
})(), _temp) {}
Foo.bar = 21;
Object.defineProperty(Foo, _, {
writable: true,
value: (() => {
var _class2, _3, _temp2;
Foo.foo = (_temp2 = (_3 = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_"), _class2 = class {}), Object.defineProperty(_class2, _3, {
writable: true,
value: (() => {
_class2.bar = 42;
})()
}), _temp2).bar;
})()
});
(() => {
var _class2, _temp2;
Foo.foo = (_temp2 = _class2 = class {}, (() => {
_class2.bar = 42;
})(), _temp2).bar;
})();
expect(Foo.foo).toBe(42);

View File

@ -1,10 +1,9 @@
class Foo {}
babelHelpers.defineProperty(Foo, "bar", 42);
var _ = {
writable: true,
value: (() => {
Foo.foo = Foo.bar;
})()
};
(() => {
Foo.foo = Foo.bar;
})();
expect(Foo.foo).toBe(42);

View File

@ -1,10 +1,9 @@
class Foo {}
babelHelpers.defineProperty(Foo, "bar", 42);
var _ = {
writable: true,
value: (() => {
Foo.foo = Foo.bar;
})()
};
(() => {
Foo.foo = Foo.bar;
})();
expect(Foo.foo).toBe(42);

View File

@ -1,21 +1,13 @@
var _class, _temp, _2, _class2, _temp2, _3;
var _class, _temp, _class2, _temp2;
class Foo extends (_temp = _class = class extends (_temp2 = _class2 = class Base {}, _3 = {
writable: true,
value: (() => {
_class2.qux = 21;
})()
}, _temp2) {}, _2 = {
writable: true,
value: (() => {
_class.bar = 21;
})()
}, _temp) {}
class Foo extends (_temp = _class = class extends (_temp2 = _class2 = class Base {}, (() => {
_class2.qux = 21;
})(), _temp2) {}, (() => {
_class.bar = 21;
})(), _temp) {}
(() => {
Foo.foo = Foo.bar + Foo.qux;
})();
var _ = {
writable: true,
value: (() => {
Foo.foo = Foo.bar + Foo.qux;
})()
};
expect(Foo.foo).toBe(42);

View File

@ -4,17 +4,14 @@ var _bar = {
writable: true,
value: 21
};
var _ = {
writable: true,
value: (() => {
Foo.foo = babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _bar);
Foo.qux1 = Foo.qux;
})()
};
(() => {
Foo.foo = babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _bar);
Foo.qux1 = Foo.qux;
})();
babelHelpers.defineProperty(Foo, "qux", 21);
var _2 = {
writable: true,
value: (() => {
Foo.qux2 = Foo.qux;
})()
};
(() => {
Foo.qux2 = Foo.qux;
})();

View File

@ -4,10 +4,9 @@ var _ = {
writable: true,
value: 42
};
var _2 = {
writable: true,
value: (() => {
Foo.foo = babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _);
})()
};
(() => {
Foo.foo = babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _);
})();
expect(Foo.foo).toBe(42);

View File

@ -2,15 +2,15 @@ import * as babel from "@babel/core";
import proposalClassStaticBlock from "..";
describe("plugin ordering", () => {
it("should throw when @babel/plugin-proposal-class-static-block is after class features plugin", () => {
it("should work when @babel/plugin-proposal-class-static-block is after class features plugin", () => {
const source = `class Foo {
static {
this.foo = Foo.bar;
}
static bar = 42;
}
`;
expect(() => {
static {
this.foo = Foo.bar;
}
static bar = 42;
}
`;
expect(
babel.transform(source, {
filename: "example.js",
highlightCode: false,
@ -20,22 +20,17 @@ describe("plugin ordering", () => {
"@babel/plugin-proposal-class-properties",
proposalClassStaticBlock,
],
});
})
.toThrow(`Incorrect plugin order, \`@babel/plugin-proposal-class-static-block\` should be placed before class features plugins
{
"plugins": [
"@babel/plugin-proposal-class-static-block",
"@babel/plugin-proposal-private-property-in-object",
"@babel/plugin-proposal-private-methods",
"@babel/plugin-proposal-class-properties",
]
}
1 | class Foo {
> 2 | static {
| ^
3 | this.foo = Foo.bar;
4 | }
5 | static bar = 42;`);
}).code,
).toMatchInlineSnapshot(`
"function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
class Foo {}
(() => {
Foo.foo = Foo.bar;
})();
_defineProperty(Foo, \\"bar\\", 42);"
`);
});
});

View File

@ -10,9 +10,6 @@ class A {
}
var _ = {
writable: true,
value: (() => {
register(A, _foo.has(A));
})()
};
(() => {
register(A, _foo.has(A));
})();

View File

@ -1111,6 +1111,7 @@ __metadata:
resolution: "@babel/plugin-proposal-class-static-block@workspace:packages/babel-plugin-proposal-class-static-block"
dependencies:
"@babel/core": "workspace:*"
"@babel/helper-create-class-features-plugin": "workspace:^7.13.11"
"@babel/helper-plugin-test-runner": "workspace:*"
"@babel/helper-plugin-utils": "workspace:^7.13.0"
"@babel/plugin-syntax-class-static-block": "workspace:^7.12.13"