Skip to content

Commit

Permalink
Transform class static block (#12143)
Browse files Browse the repository at this point in the history
Co-authored-by: Nicol貌 Ribaudo <nicolo.ribaudo@gmail.com>
Co-authored-by: Brian Ng <bng412@gmail.com>
  • Loading branch information
3 people committed Oct 14, 2020
1 parent 164518b commit 8491a14
Show file tree
Hide file tree
Showing 85 changed files with 861 additions and 24 deletions.
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 @@ -21,6 +21,7 @@
"@babel/helper-split-export-declaration": "workspace:^7.11.0",
"@babel/helper-validator-identifier": "workspace:^7.10.4",
"@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
```
31 changes: 31 additions & 0 deletions packages/babel-plugin-proposal-class-static-block/package.json
@@ -0,0 +1,31 @@
{
"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"
},
"devDependencies": {
"@babel/core": "workspace:^7.11.6"
}
}
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);

0 comments on commit 8491a14

Please sign in to comment.