Allow compiling #foo in obj without compiling private fields (#13172)

This commit is contained in:
Nicolò Ribaudo 2021-04-21 15:46:40 +02:00
parent 3c2b98ff06
commit 2fc288576e
45 changed files with 712 additions and 16 deletions

View File

@ -20,7 +20,7 @@ import {
isLoose,
} from "./features";
export { FEATURES, injectInitialization };
export { FEATURES, enableFeature, injectInitialization };
// Note: Versions are represented as an integer. e.g. 7.1.5 is represented
// as 70000100005. This method is easier than using a semver-parsing

View File

@ -17,8 +17,10 @@
"babel-plugin"
],
"dependencies": {
"@babel/helper-compilation-targets": "workspace:^7.12.17",
"@babel/helper-create-class-features-plugin": "workspace:^7.13.0",
"@babel/helper-plugin-utils": "workspace:^7.13.0"
"@babel/helper-plugin-utils": "workspace:^7.13.0",
"@babel/plugin-syntax-private-property-in-object": "workspace:^7.13.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"

View File

@ -1,23 +1,158 @@
/* eslint-disable @babel/development/plugin-name */
import { declare } from "@babel/helper-plugin-utils";
import syntaxPlugin from "@babel/plugin-syntax-private-property-in-object";
import {
createClassFeaturePlugin,
enableFeature,
FEATURES,
injectInitialization as injectConstructorInit,
} from "@babel/helper-create-class-features-plugin";
export default declare((api, options) => {
api.assertVersion(7);
export default declare(({ assertVersion, types: t, template }, { loose }) => {
assertVersion(7);
return createClassFeaturePlugin({
name: "proposal-class-properties",
// NOTE: When using the class fields or private methods plugins,
// they will also take care of '#priv in obj' checks when visiting
// the ClassExpression or ClassDeclaration nodes.
// The visitor of this plugin is only effective when not compiling
// private fields and methods.
api,
feature: FEATURES.privateIn,
loose: options.loose,
const classWeakSets = new WeakMap();
const fieldsWeakSets = new WeakMap();
manipulateOptions(opts, parserOpts) {
parserOpts.plugins.push("privateIn");
function unshadow(name, targetScope, scope) {
while (scope !== targetScope) {
if (scope.hasOwnBinding(name)) scope.rename(name);
scope = scope.parent;
}
}
function injectToFieldInit(fieldPath, expr, before = false) {
if (fieldPath.node.value) {
if (before) {
fieldPath.get("value").insertBefore(expr);
} else {
fieldPath.get("value").insertAfter(expr);
}
} else {
fieldPath.set("value", t.unaryExpression("void", expr));
}
}
function injectInitialization(classPath, init) {
let firstFieldPath;
let consturctorPath;
for (const el of classPath.get("body.body")) {
if (
(el.isClassProperty() || el.isClassPrivateProperty()) &&
!el.node.static
) {
firstFieldPath = el;
break;
}
if (!consturctorPath && el.isClassMethod({ kind: "constructor" })) {
consturctorPath = el;
}
}
if (firstFieldPath) {
injectToFieldInit(firstFieldPath, init, true);
} else {
injectConstructorInit(classPath, consturctorPath, [
t.expressionStatement(init),
]);
}
}
function getWeakSetId(weakSets, outerClass, reference, name = "", inject) {
let id = classWeakSets.get(reference.node);
if (!id) {
id = outerClass.scope.generateUidIdentifier(`${name || ""} brandCheck`);
classWeakSets.set(reference.node, id);
inject(reference, template.expression.ast`${t.cloneNode(id)}.add(this)`);
outerClass.insertBefore(template.ast`var ${id} = new WeakSet()`);
}
return t.cloneNode(id);
}
return {
name: "proposal-private-property-in-object",
inherits: syntaxPlugin,
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.privateIn, loose);
},
visitor: {
BinaryExpression(path) {
const { node } = path;
if (node.operator !== "in") return;
if (!t.isPrivateName(node.left)) return;
const { name } = node.left.id;
let privateElement;
const outerClass = path.findParent(path => {
if (!path.isClass()) return false;
privateElement = path
.get("body.body")
.find(({ node }) => t.isPrivate(node) && node.key.id.name === name);
return !!privateElement;
});
if (outerClass.parentPath.scope.path.isPattern()) {
outerClass.replaceWith(template.ast`(() => ${outerClass.node})()`);
// The injected class will be queued and eventually transformed when visited
return;
}
if (privateElement.isMethod()) {
if (privateElement.node.static) {
if (outerClass.node.id) {
unshadow(outerClass.node.id.name, outerClass.scope, path.scope);
} else {
outerClass.set("id", path.scope.generateUidIdentifier("class"));
}
path.replaceWith(
template.expression.ast`
${t.cloneNode(outerClass.node.id)} === ${path.node.right}
`,
);
} else {
const id = getWeakSetId(
classWeakSets,
outerClass,
outerClass,
outerClass.node.id?.name,
injectInitialization,
);
path.replaceWith(
template.expression.ast`${id}.has(${path.node.right})`,
);
}
} else {
// Private fields might not all be initialized: see the 'halfConstructed'
// example at https://v8.dev/features/private-brand-checks.
const id = getWeakSetId(
fieldsWeakSets,
outerClass,
privateElement,
privateElement.node.key.id.name,
injectToFieldInit,
);
path.replaceWith(
template.expression.ast`${id}.has(${path.node.right})`,
);
}
},
},
};
});

View File

@ -0,0 +1,7 @@
class Foo {
get #foo() {}
test(other) {
return #foo in other;
}
}

View File

@ -0,0 +1,14 @@
var _FooBrandCheck = new WeakSet();
class Foo {
constructor() {
_FooBrandCheck.add(this);
}
get #foo() {}
test(other) {
return _FooBrandCheck.has(other);
}
}

View File

@ -0,0 +1,4 @@
(x = class {
#foo;
test(other) { return #foo in other }
}) => {}

View File

@ -0,0 +1,12 @@
(x = (() => {
var _fooBrandCheck;
return _fooBrandCheck = new WeakSet(), class {
#foo = void _fooBrandCheck.add(this);
test(other) {
return _fooBrandCheck.has(other);
}
};
})()) => {};

View File

@ -0,0 +1,9 @@
function fn() {
return new class {
#priv;
method(obj) {
return #priv in obj;
}
}
}

View File

@ -0,0 +1,12 @@
function fn() {
var _privBrandCheck;
return new (_privBrandCheck = new WeakSet(), class {
#priv = void _privBrandCheck.add(this);
method(obj) {
return _privBrandCheck.has(obj);
}
})();
}

View File

@ -0,0 +1,9 @@
function fn() {
return new class {
static #priv;
method(obj) {
return #priv in obj;
}
}
}

View File

@ -0,0 +1,12 @@
function fn() {
var _privBrandCheck;
return new (_privBrandCheck = new WeakSet(), class {
static #priv = void _privBrandCheck.add(this);
method(obj) {
return _privBrandCheck.has(obj);
}
})();
}

View File

@ -0,0 +1,7 @@
class Foo {
#foo = 1;
test(other) {
return #foo in other;
}
}

View File

@ -0,0 +1,12 @@
var _temp;
var _fooBrandCheck = new WeakSet();
class Foo {
#foo = (_temp = 1, _fooBrandCheck.add(this), _temp);
test(other) {
return _fooBrandCheck.has(other);
}
}

View File

@ -0,0 +1,28 @@
let hasW, hasX, hasY, hasZ;
let halfConstructed;
class F {
m() {
hasW = #w in this;
hasX = #x in this;
hasY = #y in this;
hasZ = #z in this;
}
get #w() {}
#x = 0;
#y = (() => {
halfConstructed = this;
throw "error";
})();
#z() {}
}
try {
new F();
} catch {}
halfConstructed.m();
expect(hasW).toBe(true);
expect(hasX).toBe(true);
expect(hasY).toBe(false);
expect(hasZ).toBe(true);

View File

@ -0,0 +1,14 @@
class F {
m() {
#w in this;
#x in this;
#y in this;
#z in this;
}
get #w() {}
#x = 0;
#y = (() => {
throw 'error';
})();
#z() {}
}

View File

@ -0,0 +1,29 @@
var _temp, _temp2;
var _FBrandCheck = new WeakSet();
var _xBrandCheck = new WeakSet();
var _yBrandCheck = new WeakSet();
class F {
m() {
_FBrandCheck.has(this);
_xBrandCheck.has(this);
_yBrandCheck.has(this);
_FBrandCheck.has(this);
}
get #w() {}
#x = (_temp = (_FBrandCheck.add(this), 0), _xBrandCheck.add(this), _temp);
#y = (_temp2 = (() => {
throw 'error';
})(), _yBrandCheck.add(this), _temp2);
#z() {}
}

View File

@ -0,0 +1,27 @@
let hasW, hasX, hasY, hasZ;
let halfConstructed;
try {
class F {
static m() {
hasW = #w in this;
hasX = #x in this;
hasY = #y in this;
hasZ = #z in this;
}
static get #w() {}
static #x = 0;
static #y = (() => {
halfConstructed = this;
throw "error";
})();
static #z() {}
}
} catch {}
halfConstructed.m();
expect(hasW).toBe(true);
expect(hasX).toBe(true);
expect(hasY).toBe(false);
expect(hasZ).toBe(true);

View File

@ -0,0 +1,12 @@
class F {
static m() {
#x in this;
#y in this;
#z in this;
}
static #x = 0;
static #y = (() => {
throw 'error';
})();
static #z() {}
}

View File

@ -0,0 +1,23 @@
var _temp, _temp2;
var _xBrandCheck = new WeakSet();
var _yBrandCheck = new WeakSet();
class F {
static m() {
_xBrandCheck.has(this);
_yBrandCheck.has(this);
F === this;
}
static #x = (_temp = 0, _xBrandCheck.add(this), _temp);
static #y = (_temp2 = (() => {
throw 'error';
})(), _yBrandCheck.add(this), _temp2);
static #z() {}
}

View File

@ -0,0 +1,7 @@
class Foo {
#foo() {}
test(other) {
return #foo in other;
}
}

View File

@ -0,0 +1,14 @@
var _FooBrandCheck = new WeakSet();
class Foo {
constructor() {
_FooBrandCheck.add(this);
}
#foo() {}
test(other) {
return _FooBrandCheck.has(other);
}
}

View File

@ -0,0 +1,11 @@
class A {
#x;
#m() {}
test() {
#x in this;
#m in this;
#x in this;
#m in this;
}
}

View File

@ -0,0 +1,20 @@
var _xBrandCheck = new WeakSet();
var _ABrandCheck = new WeakSet();
class A {
#x = (_ABrandCheck.add(this), void _xBrandCheck.add(this));
#m() {}
test() {
_xBrandCheck.has(this);
_ABrandCheck.has(this);
_xBrandCheck.has(this);
_ABrandCheck.has(this);
}
}

View File

@ -0,0 +1,18 @@
class Foo {
#foo = 1;
#bar = 1;
test() {
class Nested {
#bar = 2;
test() {
#foo in this;
#bar in this;
}
}
#foo in this;
#bar in this;
}
}

View File

@ -0,0 +1,32 @@
var _temp, _temp3;
var _fooBrandCheck = new WeakSet();
var _barBrandCheck2 = new WeakSet();
class Foo {
#foo = (_temp = 1, _fooBrandCheck.add(this), _temp);
#bar = (_temp3 = 1, _barBrandCheck2.add(this), _temp3);
test() {
var _temp2;
var _barBrandCheck = new WeakSet();
class Nested {
#bar = (_temp2 = 2, _barBrandCheck.add(this), _temp2);
test() {
_fooBrandCheck.has(this);
_barBrandCheck.has(this);
}
}
_fooBrandCheck.has(this);
_barBrandCheck2.has(this);
}
}

View File

@ -0,0 +1,15 @@
class Foo {
#foo = 1;
test() {
class Nested {
#foo = 2;
test() {
#foo in this;
}
}
#foo in this;
}
}

View File

@ -0,0 +1,25 @@
var _temp2;
var _fooBrandCheck2 = new WeakSet();
class Foo {
#foo = (_temp2 = 1, _fooBrandCheck2.add(this), _temp2);
test() {
var _temp;
var _fooBrandCheck = new WeakSet();
class Nested {
#foo = (_temp = 2, _fooBrandCheck.add(this), _temp);
test() {
_fooBrandCheck.has(this);
}
}
_fooBrandCheck2.has(this);
}
}

View File

@ -0,0 +1,13 @@
class Foo {
#foo = 1;
test() {
class Nested {
test() {
#foo in this;
}
}
#foo in this;
}
}

View File

@ -0,0 +1,19 @@
var _temp;
var _fooBrandCheck = new WeakSet();
class Foo {
#foo = (_temp = 1, _fooBrandCheck.add(this), _temp);
test() {
class Nested {
test() {
_fooBrandCheck.has(this);
}
}
_fooBrandCheck.has(this);
}
}

View File

@ -0,0 +1,3 @@
{
"plugins": ["proposal-private-property-in-object", "syntax-class-properties"]
}

View File

@ -0,0 +1,7 @@
class Foo {
static get #foo() {}
test(other) {
return #foo in other;
}
}

View File

@ -0,0 +1,8 @@
class Foo {
static get #foo() {}
test(other) {
return Foo === other;
}
}

View File

@ -0,0 +1,7 @@
class Foo {
static #foo = 1;
test(other) {
return #foo in other;
}
}

View File

@ -0,0 +1,12 @@
var _temp;
var _fooBrandCheck = new WeakSet();
class Foo {
static #foo = (_temp = 1, _fooBrandCheck.add(this), _temp);
test(other) {
return _fooBrandCheck.has(other);
}
}

View File

@ -0,0 +1,7 @@
class Foo {
static #foo() {}
test(other) {
return #foo in other;
}
}

View File

@ -0,0 +1,8 @@
class Foo {
static #foo() {}
test(other) {
return Foo === other;
}
}

View File

@ -0,0 +1,9 @@
class A {
static #foo;
test() {
let A = function fn(A) {
return #foo in A;
};
}
}

View File

@ -0,0 +1,12 @@
var _fooBrandCheck = new WeakSet();
class A {
static #foo = void _fooBrandCheck.add(this);
test() {
let A = function fn(A) {
return _fooBrandCheck.has(A);
};
}
}

View File

@ -0,0 +1,3 @@
src
test
*.log

View File

@ -0,0 +1,19 @@
# @babel/plugin-syntax-class-static-block
> Allow parsing of class static blocks
See our website [@babel/plugin-syntax-class-static-block](https://babeljs.io/docs/en/babel-plugin-syntax-class-static-block) for more information.
## Install
Using npm:
```sh
npm install --save-dev @babel/plugin-syntax-class-static-block
```
or using yarn:
```sh
yarn add @babel/plugin-syntax-class-static-block --dev
```

View File

@ -0,0 +1,28 @@
{
"name": "@babel/plugin-syntax-private-property-in-object",
"version": "7.13.0",
"description": "Allow parsing of '#foo in obj' brand checks",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-plugin-syntax-private-property-in-object"
},
"homepage": "https://babel.dev/docs/en/next/babel-plugin-syntax-private-property-in-object",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "./lib/index.js",
"exports": {
".": "./lib/index.js"
},
"keywords": [
"babel-plugin"
],
"dependencies": {
"@babel/helper-plugin-utils": "workspace:^7.12.13"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
}

View File

@ -0,0 +1,13 @@
import { declare } from "@babel/helper-plugin-utils";
export default declare(api => {
api.assertVersion(7);
return {
name: "syntax-private-property-in-object",
manipulateOptions(opts, parserOpts) {
parserOpts.plugins.push("privateIn");
},
};
});

View File

@ -393,7 +393,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/helper-compilation-targets@workspace:^7.13.13, @babel/helper-compilation-targets@workspace:^7.13.16, @babel/helper-compilation-targets@workspace:^7.13.8, @babel/helper-compilation-targets@workspace:packages/babel-helper-compilation-targets":
"@babel/helper-compilation-targets@workspace:^7.12.17, @babel/helper-compilation-targets@workspace:^7.13.13, @babel/helper-compilation-targets@workspace:^7.13.16, @babel/helper-compilation-targets@workspace:^7.13.8, @babel/helper-compilation-targets@workspace:packages/babel-helper-compilation-targets":
version: 0.0.0-use.local
resolution: "@babel/helper-compilation-targets@workspace:packages/babel-helper-compilation-targets"
dependencies:
@ -1448,9 +1448,11 @@ __metadata:
resolution: "@babel/plugin-proposal-private-property-in-object@workspace:packages/babel-plugin-proposal-private-property-in-object"
dependencies:
"@babel/core": "workspace:*"
"@babel/helper-compilation-targets": "workspace:^7.12.17"
"@babel/helper-create-class-features-plugin": "workspace:^7.13.0"
"@babel/helper-plugin-test-runner": "workspace:*"
"@babel/helper-plugin-utils": "workspace:^7.13.0"
"@babel/plugin-syntax-private-property-in-object": "workspace:^7.13.0"
peerDependencies:
"@babel/core": ^7.0.0-0
languageName: unknown
@ -1825,6 +1827,16 @@ __metadata:
languageName: unknown
linkType: soft
"@babel/plugin-syntax-private-property-in-object@workspace:^7.13.0, @babel/plugin-syntax-private-property-in-object@workspace:packages/babel-plugin-syntax-private-property-in-object":
version: 0.0.0-use.local
resolution: "@babel/plugin-syntax-private-property-in-object@workspace:packages/babel-plugin-syntax-private-property-in-object"
dependencies:
"@babel/helper-plugin-utils": "workspace:^7.12.13"
peerDependencies:
"@babel/core": ^7.0.0-0
languageName: unknown
linkType: soft
"@babel/plugin-syntax-record-and-tuple@workspace:*, @babel/plugin-syntax-record-and-tuple@workspace:^7.12.1, @babel/plugin-syntax-record-and-tuple@workspace:packages/babel-plugin-syntax-record-and-tuple":
version: 0.0.0-use.local
resolution: "@babel/plugin-syntax-record-and-tuple@workspace:packages/babel-plugin-syntax-record-and-tuple"