Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transform class static block #12143

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions Makefile
Expand Up @@ -220,6 +220,14 @@ prepublish:

# --exclude-dependents support is added by .yarn-patches/@lerna/version
new-version:
@echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
@echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
@echo "!!!!!! !!!!!!"
@echo "!!!!!! Enable the check in proposal-class-static-block !!!!!!"
@echo "!!!!!! !!!!!!"
@echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
@echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
@exit 1
git pull --rebase
$(YARN) lerna version --exclude-dependents --force-publish=$(FORCE_PUBLISH)

Expand Down
10 changes: 10 additions & 0 deletions packages/babel-core/src/parser/util/missing-plugin-helper.js
Expand Up @@ -31,6 +31,16 @@ const pluginNameMap = {
url: "https://git.io/JvpRG",
},
},
classStaticBlock: {
syntax: {
name: "@babel/plugin-syntax-class-static-block",
url: "https://git.io/JTLB6",
},
transform: {
name: "@babel/plugin-proposal-class-static-block",
url: "https://git.io/JTLBP",
},
},
decimal: {
syntax: {
name: "@babel/plugin-syntax-decimal",
Expand Down
19 changes: 18 additions & 1 deletion packages/babel-helper-create-class-features-plugin/src/index.js
Expand Up @@ -120,6 +120,18 @@ export function createClassFeaturePlugin({
}

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 Expand Up @@ -188,7 +200,12 @@ export function createClassFeaturePlugin({
},

PrivateName(path) {
if (this.file.get(versionKey) !== version) return;
if (
this.file.get(versionKey) !== version ||
path.parentPath.isPrivate({ key: path.node })
) {
return;
}

throw path.buildCodeFrameError(`Unknown PrivateName "${path}"`);
},
Expand Down
1 change: 1 addition & 0 deletions packages/babel-helper-module-transforms/package.json
Expand Up @@ -20,6 +20,7 @@
"@babel/helper-simple-access": "workspace:^7.10.4",
"@babel/helper-split-export-declaration": "workspace:^7.11.0",
"@babel/template": "workspace:^7.10.4",
"@babel/traverse": "workspace:^7.11.5",
"@babel/types": "workspace:^7.11.0",
"lodash": "^4.17.19"
}
Expand Down
30 changes: 13 additions & 17 deletions packages/babel-helper-module-transforms/src/rewrite-this.js
@@ -1,25 +1,21 @@
import { skipAllButComputedKey } from "@babel/helper-replace-supers";
import { environmentVisitor } from "@babel/helper-replace-supers";
import traverse from "@babel/traverse";
import * as t from "@babel/types";

export default function rewriteThis(programPath: NodePath) {
// Rewrite "this" to be "undefined".
programPath.traverse(rewriteThisVisitor);
traverse(programPath.node, { ...rewriteThisVisitor, noScope: true });
}

/**
* A visitor to walk the tree, rewriting all `this` references in the top-level scope to be
* `undefined`.
* `void 0` (undefined).
*/
const rewriteThisVisitor = {
ThisExpression(path) {
path.replaceWith(path.scope.buildUndefinedNode());
},
Function(path) {
if (path.isMethod()) skipAllButComputedKey(path);
else if (!path.isArrowFunctionExpression()) path.skip();
},
ClassProperty(path) {
skipAllButComputedKey(path);
},
ClassPrivateProperty(path) {
path.skip();
const rewriteThisVisitor = traverse.visitors.merge([
environmentVisitor,
{
ThisExpression(path) {
path.replaceWith(t.unaryExpression("void", t.numericLiteral(0), true));
},
},
};
]);
9 changes: 7 additions & 2 deletions packages/babel-helper-replace-supers/src/index.js
Expand Up @@ -42,8 +42,13 @@ export function skipAllButComputedKey(path: NodePath) {
}

// environmentVisitor should be used when traversing the whole class and not for specific class elements/methods.
// For perf reasons, the environmentVisitor will be traversed with `{ noScope: true }`, which means `path.scope` is undefined.
// Avoid using `path.scope` here
export const environmentVisitor = {
TypeAnnotation(path: NodePath) {
// todo (Babel 8): remove StaticBlock brand checks
[`${t.StaticBlock ? "StaticBlock|" : ""}ClassPrivateProperty|TypeAnnotation`](
path: NodePath,
) {
path.skip();
},

Expand All @@ -55,7 +60,7 @@ export const environmentVisitor = {
path.skip();
},

"Method|ClassProperty|ClassPrivateProperty"(path: NodePath) {
"Method|ClassProperty"(path: NodePath) {
skipAllButComputedKey(path);
},
};
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-plugin-proposal-class-static-block/.npmignore
@@ -0,0 +1,3 @@
src
test
*.log
19 changes: 19 additions & 0 deletions packages/babel-plugin-proposal-class-static-block/README.md
@@ -0,0 +1,19 @@
# @babel/plugin-proposal-class-static-block

> Allow transforming of class static blocks
See our website [@babel/plugin-proposal-class-static-block](https://babeljs.io/docs/en/next/babel-plugin-proposal-class-static-block.html) for more information.

## Install

Using npm:

```sh
npm install --save-dev @babel/plugin-proposal-class-static-block
```

or using yarn:

```sh
yarn add @babel/plugin-proposal-class-static-block --dev
```
28 changes: 28 additions & 0 deletions packages/babel-plugin-proposal-class-static-block/package.json
@@ -0,0 +1,28 @@
{
"name": "@babel/plugin-proposal-class-static-block",
"version": "7.11.0",
"description": "Allow parsing of class static blocks",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-plugin-proposal-class-static-block"
},
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "./lib/index.js",
"exports": {
".": "./lib/index.js"
},
"keywords": [
"babel-plugin"
],
"dependencies": {
"@babel/helper-plugin-utils": "workspace:^7.10.1",
"@babel/plugin-syntax-class-static-block": "workspace:^7.11.0"
},
"peerDependencies": {
"@babel/core": "^7.12.0"
}
}
61 changes: 61 additions & 0 deletions packages/babel-plugin-proposal-class-static-block/src/index.js
@@ -0,0 +1,61 @@
import { declare } from "@babel/helper-plugin-utils";
import syntaxClassStaticBlock from "@babel/plugin-syntax-class-static-block";

/**
* Generate a uid that is not in `denyList`
*
* @param {*} scope
* @param {Set<string>} a deny list that the generated uid should avoid
* @returns
*/
function generateUid(scope, denyList: Set<string>) {
const name = "";
let uid;
let i = 1;
do {
uid = scope._generateUid(name, i);
i++;
} while (denyList.has(uid));
return uid;
}

export default declare(({ types: t, template, assertVersion }) => {
// todo: change to ^7.12.0 when it is published
assertVersion("^7.11.6");

return {
name: "proposal-class-static-block",
inherits: syntaxClassStaticBlock,
visitor: {
Class(path: NodePath<Class>) {
const { scope } = path;
const classBody = path.get("body");
const privateNames = new Set();
let staticBlockPath;
for (const path of classBody.get("body")) {
if (path.isPrivate()) {
privateNames.add(path.get("key.id").node.name);
} else if (path.isStaticBlock()) {
staticBlockPath = path;
}
}
if (!staticBlockPath) {
return;
}
const staticBlockRef = t.privateName(
t.identifier(generateUid(scope, privateNames)),
);
classBody.pushContainer(
"body",
t.classPrivateProperty(
staticBlockRef,
template.expression.ast`(() => { ${staticBlockPath.node.body} })()`,
[],
/* static */ true,
),
);
staticBlockPath.remove();
},
},
};
});
@@ -0,0 +1,7 @@
class Foo {
static {
this.foo = Foo.bar;
}
static bar = 42;
}
expect(Foo.foo).toBe(42);
@@ -0,0 +1,7 @@
class Foo {
static {
this.foo = this.bar;
}
static bar = 42;
}
expect(Foo.foo).toBe(42);
@@ -0,0 +1,7 @@
class Foo {
static {
this.foo = this.bar;
}
static bar = 42;
}
expect(Foo.foo).toBe(42);
@@ -0,0 +1,8 @@
class Foo {
static bar = 42;
static #_ = (() => {
this.foo = this.bar;
})();
}

expect(Foo.foo).toBe(42);
@@ -0,0 +1,6 @@
class Foo {
static {
this.foo = 42;
}
}
expect(Foo.foo).toBe(42);
@@ -0,0 +1,14 @@
class Foo extends class extends class Base {
static {
this.qux = 21;
}
} {
static {
this.bar = 21;
}
} {
static {
this.foo = this.bar + this.qux;
}
}
expect(Foo.foo).toBe(42);
@@ -0,0 +1,14 @@
class Foo extends class extends class Base {
static {
this.qux = 21;
}
} {
static {
this.bar = 21;
}
} {
static {
this.foo = this.bar + this.qux;
}
}
expect(Foo.foo).toBe(42);
@@ -0,0 +1,15 @@
class Foo extends class extends class Base {
static #_ = (() => {
this.qux = 21;
})();
} {
static #_ = (() => {
this.bar = 21;
})();
} {
static #_ = (() => {
this.foo = this.bar + this.qux;
})();
}

expect(Foo.foo).toBe(42);
@@ -0,0 +1,8 @@
class Foo {
static #bar = 21;
static {
this.foo = this.#bar + this.qux;
}
static qux = 21;
}
expect(Foo.foo).toBe(42);
@@ -0,0 +1,8 @@
class Foo {
static #_ = 42;
// static block can not be tranformed as `#_` here
static {
this.foo = this.#_;
}
}
expect(Foo.foo).toBe(42);
@@ -0,0 +1,8 @@
class Foo {
static #_ = 42;
// static block can not be tranformed as `#_` here
static {
this.foo = this.#_;
}
}
expect(Foo.foo).toBe(42);
@@ -0,0 +1,9 @@
class Foo {
static #_ = 42; // static block can not be tranformed as `#_` here

static #_2 = (() => {
this.foo = this.#_;
})();
}

expect(Foo.foo).toBe(42);
@@ -0,0 +1,7 @@
class Foo {
bar = 21;
static {
this.foo = this.bar;
}
}
expect(Foo.foo).toBe(undefined);
@@ -0,0 +1,4 @@
{
"plugins": ["proposal-class-static-block", "syntax-class-properties"],
"minNodeVersion": "12.0.0"
}
@@ -0,0 +1,8 @@
let getFoo;
class Foo {
static #foo = 42;
static {
getFoo = () => this.#foo;
}
}
expect(getFoo()).toBe(42);