Skip to content

Commit

Permalink
Compile static blocks without the intermediate priv field step (#13297)
Browse files Browse the repository at this point in the history
* Remove ordering constraints for `static-blocks` plugin

* Handle static blocks directly in `helper-create-class-features-plugin`
  • Loading branch information
nicolo-ribaudo committed May 14, 2021
1 parent b3d35cd commit 8732dd3
Show file tree
Hide file tree
Showing 22 changed files with 168 additions and 184 deletions.
14 changes: 12 additions & 2 deletions packages/babel-helper-create-class-features-plugin/src/features.js
Expand Up @@ -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([
Expand Down Expand Up @@ -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.");
}
Expand All @@ -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.",
);
}
}
}
22 changes: 18 additions & 4 deletions packages/babel-helper-create-class-features-plugin/src/fields.js
Expand Up @@ -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({
Expand All @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
18 changes: 5 additions & 13 deletions packages/babel-helper-create-class-features-plugin/src/index.js
Expand Up @@ -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;
Expand Down
@@ -0,0 +1,4 @@
class A {
#x;
static {}
}
@@ -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."
}
3 changes: 2 additions & 1 deletion packages/babel-helper-replace-supers/src/index.ts
Expand Up @@ -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;
Expand Down
Expand Up @@ -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"
},
Expand Down
20 changes: 17 additions & 3 deletions packages/babel-plugin-proposal-class-static-block/src/index.js
@@ -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`
*
Expand All @@ -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) {
Expand Down
@@ -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);
@@ -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);
@@ -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);
@@ -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;
})();
@@ -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);
@@ -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);
@@ -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);
@@ -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);

0 comments on commit 8732dd3

Please sign in to comment.