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, privateMethods: 1 << 2,
decorators: 1 << 3, decorators: 1 << 3,
privateIn: 1 << 4, privateIn: 1 << 4,
staticBlocks: 1 << 5,
}); });
const featuresSameLoose = new Map([ 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 // NOTE: path.isPrivateMethod() it isn't supported in <7.2.0
if (path.isPrivate() && path.isMethod()) { if (path.isPrivateMethod?.()) {
if (!hasFeature(file, FEATURES.privateMethods)) { if (!hasFeature(file, FEATURES.privateMethods)) {
throw path.buildCodeFrameError("Class private methods are not enabled."); 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."); 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, environmentVisitor,
]); ]);
function replaceThisContext(path, ref, superRef, file, constantSuper) { function replaceThisContext(
path,
ref,
superRef,
file,
isStaticBlock,
constantSuper,
) {
const state = { classRef: ref, needsClassRef: false }; const state = { classRef: ref, needsClassRef: false };
const replacer = new ReplaceSupers({ const replacer = new ReplaceSupers({
@ -680,13 +687,13 @@ function replaceThisContext(path, ref, superRef, file, constantSuper) {
refToPreserve: ref, refToPreserve: ref,
getObjectRef() { getObjectRef() {
state.needsClassRef = true; state.needsClassRef = true;
return path.node.static return isStaticBlock || path.node.static
? ref ? ref
: t.memberExpression(ref, t.identifier("prototype")); : t.memberExpression(ref, t.identifier("prototype"));
}, },
}); });
replacer.replace(); replacer.replace();
if (path.isProperty()) { if (isStaticBlock || path.isProperty()) {
path.traverse(thisContextVisitor, state); path.traverse(thisContextVisitor, state);
} }
return state.needsClassRef; return state.needsClassRef;
@ -717,19 +724,26 @@ export function buildFieldsInitNodes(
const isPublic = !isPrivate; const isPublic = !isPrivate;
const isField = prop.isProperty(); const isField = prop.isProperty();
const isMethod = !isField; const isMethod = !isField;
const isStaticBlock = prop.isStaticBlock?.();
if (isStatic || (isMethod && isPrivate)) { if (isStatic || (isMethod && isPrivate) || isStaticBlock) {
const replaced = replaceThisContext( const replaced = replaceThisContext(
prop, prop,
ref, ref,
superRef, superRef,
state, state,
isStaticBlock,
constantSuper, constantSuper,
); );
needsClassRef = needsClassRef || replaced; needsClassRef = needsClassRef || replaced;
} }
switch (true) { switch (true) {
case isStaticBlock:
staticNodes.push(
template.statement.ast`(() => ${t.blockStatement(prop.node.body)})()`,
);
break;
case isStatic && isPrivate && isField && privateFieldsAsProperties: case isStatic && isPrivate && isField && privateFieldsAsProperties:
needsClassRef = true; needsClassRef = true;
staticNodes.push( staticNodes.push(

View File

@ -146,24 +146,16 @@ export function createClassFeaturePlugin({
constructor = path; constructor = path;
} else { } else {
elements.push(path); elements.push(path);
if (path.isProperty() || path.isPrivate()) { if (
path.isProperty() ||
path.isPrivate() ||
path.isStaticBlock?.()
) {
props.push(path); props.push(path);
} }
} }
if (!isDecorated) isDecorated = hasOwnDecorators(path.node); 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; 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.methodPath = path;
this.isDerivedConstructor = this.isDerivedConstructor =
path.isClassMethod({ kind: "constructor" }) && !!opts.superRef; 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.isPrivateMethod = path.isPrivate() && path.isMethod();
this.file = opts.file; this.file = opts.file;

View File

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

View File

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

View File

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

View File

@ -1,12 +1,9 @@
var _ = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_");
class Foo {} class Foo {}
Foo.bar = 42; Foo.bar = 42;
Object.defineProperty(Foo, _, {
writable: true, (() => {
value: (() => { Foo.foo = Foo.bar;
Foo.foo = Foo.bar; })();
})()
});
expect(Foo.foo).toBe(42); 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, Foo.foo = Foo.bar + Foo.qux;
value: (() => { })();
_class2.qux = 21;
})()
}), _temp2) {}), Object.defineProperty(_class, _2, {
writable: true,
value: (() => {
_class.bar = 21;
})()
}), _temp) {}
Object.defineProperty(Foo, _, {
writable: true,
value: (() => {
Foo.foo = Foo.bar + Foo.qux;
})()
});
expect(Foo.foo).toBe(42); expect(Foo.foo).toBe(42);

View File

@ -1,26 +1,19 @@
var _bar = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("bar"); var _bar = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("bar");
var _ = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_");
var _2 = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_2");
class Foo {} class Foo {}
Object.defineProperty(Foo, _bar, { Object.defineProperty(Foo, _bar, {
writable: true, writable: true,
value: 21 value: 21
}); });
Object.defineProperty(Foo, _, {
writable: true, (() => {
value: (() => { Foo.foo = babelHelpers.classPrivateFieldLooseBase(Foo, _bar)[_bar];
Foo.foo = babelHelpers.classPrivateFieldLooseBase(Foo, _bar)[_bar]; Foo.qux1 = Foo.qux;
Foo.qux1 = Foo.qux; })();
})()
});
Foo.qux = 21; 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 _ = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_");
var _2 = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_2");
class Foo {} class Foo {}
Object.defineProperty(Foo, _, { Object.defineProperty(Foo, _, {
writable: true, writable: true,
value: 42 value: 42
}); });
Object.defineProperty(Foo, _2, {
writable: true, (() => {
value: (() => { Foo.foo = babelHelpers.classPrivateFieldLooseBase(Foo, _)[_];
Foo.foo = babelHelpers.classPrivateFieldLooseBase(Foo, _)[_]; })();
})()
});
expect(Foo.foo).toBe(42); 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 = _class = class {}, (() => {
_class.bar = 42;
class Foo extends (_temp = (_2 = /*#__PURE__*/babelHelpers.classPrivateFieldLooseKey("_"), _class = class {}), Object.defineProperty(_class, _2, { })(), _temp) {}
writable: true,
value: (() => {
_class.bar = 42;
})()
}), _temp) {}
Foo.bar = 21; 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, var _class2, _temp2;
value: (() => {
_class2.bar = 42; Foo.foo = (_temp2 = _class2 = class {}, (() => {
})() _class2.bar = 42;
}), _temp2).bar; })(), _temp2).bar;
})() })();
});
expect(Foo.foo).toBe(42); expect(Foo.foo).toBe(42);

View File

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

View File

@ -1,10 +1,9 @@
class Foo {} class Foo {}
babelHelpers.defineProperty(Foo, "bar", 42); babelHelpers.defineProperty(Foo, "bar", 42);
var _ = {
writable: true, (() => {
value: (() => { Foo.foo = Foo.bar;
Foo.foo = Foo.bar; })();
})()
};
expect(Foo.foo).toBe(42); 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 = { class Foo extends (_temp = _class = class extends (_temp2 = _class2 = class Base {}, (() => {
writable: true, _class2.qux = 21;
value: (() => { })(), _temp2) {}, (() => {
_class2.qux = 21; _class.bar = 21;
})() })(), _temp) {}
}, _temp2) {}, _2 = {
writable: true, (() => {
value: (() => { Foo.foo = Foo.bar + Foo.qux;
_class.bar = 21; })();
})()
}, _temp) {}
var _ = {
writable: true,
value: (() => {
Foo.foo = Foo.bar + Foo.qux;
})()
};
expect(Foo.foo).toBe(42); expect(Foo.foo).toBe(42);

View File

@ -4,17 +4,14 @@ var _bar = {
writable: true, writable: true,
value: 21 value: 21
}; };
var _ = {
writable: true, (() => {
value: (() => { Foo.foo = babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _bar);
Foo.foo = babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _bar); Foo.qux1 = Foo.qux;
Foo.qux1 = Foo.qux; })();
})()
};
babelHelpers.defineProperty(Foo, "qux", 21); 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, writable: true,
value: 42 value: 42
}; };
var _2 = {
writable: true, (() => {
value: (() => { Foo.foo = babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _);
Foo.foo = babelHelpers.classStaticPrivateFieldSpecGet(Foo, Foo, _); })();
})()
};
expect(Foo.foo).toBe(42); expect(Foo.foo).toBe(42);

View File

@ -2,15 +2,15 @@ import * as babel from "@babel/core";
import proposalClassStaticBlock from ".."; import proposalClassStaticBlock from "..";
describe("plugin ordering", () => { 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 { const source = `class Foo {
static { static {
this.foo = Foo.bar; this.foo = Foo.bar;
} }
static bar = 42; static bar = 42;
} }
`; `;
expect(() => { expect(
babel.transform(source, { babel.transform(source, {
filename: "example.js", filename: "example.js",
highlightCode: false, highlightCode: false,
@ -20,22 +20,17 @@ describe("plugin ordering", () => {
"@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-class-properties",
proposalClassStaticBlock, proposalClassStaticBlock,
], ],
}); }).code,
}) ).toMatchInlineSnapshot(`
.toThrow(`Incorrect plugin order, \`@babel/plugin-proposal-class-static-block\` should be placed before class features plugins "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; }
{
"plugins": [ class Foo {}
"@babel/plugin-proposal-class-static-block",
"@babel/plugin-proposal-private-property-in-object", (() => {
"@babel/plugin-proposal-private-methods", Foo.foo = Foo.bar;
"@babel/plugin-proposal-class-properties", })();
]
} _defineProperty(Foo, \\"bar\\", 42);"
1 | class Foo { `);
> 2 | static {
| ^
3 | this.foo = Foo.bar;
4 | }
5 | static bar = 42;`);
}); });
}); });

View File

@ -10,9 +10,6 @@ class A {
} }
var _ = { (() => {
writable: true, register(A, _foo.has(A));
value: (() => { })();
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" resolution: "@babel/plugin-proposal-class-static-block@workspace:packages/babel-plugin-proposal-class-static-block"
dependencies: dependencies:
"@babel/core": "workspace:*" "@babel/core": "workspace:*"
"@babel/helper-create-class-features-plugin": "workspace:^7.13.11"
"@babel/helper-plugin-test-runner": "workspace:*" "@babel/helper-plugin-test-runner": "workspace:*"
"@babel/helper-plugin-utils": "workspace:^7.13.0" "@babel/helper-plugin-utils": "workspace:^7.13.0"
"@babel/plugin-syntax-class-static-block": "workspace:^7.12.13" "@babel/plugin-syntax-class-static-block": "workspace:^7.12.13"