Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow compiling
#foo in obj
without compiling private fields (#13172)
- Loading branch information
1 parent
d7d910a
commit 6e9c515
Showing
45 changed files
with
712 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
161 changes: 148 additions & 13 deletions
161
packages/babel-plugin-proposal-private-property-in-object/src/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
|
||
// 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. | ||
|
||
const classWeakSets = new WeakMap(); | ||
const fieldsWeakSets = new WeakMap(); | ||
|
||
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, | ||
); | ||
|
||
return createClassFeaturePlugin({ | ||
name: "proposal-class-properties", | ||
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. | ||
|
||
api, | ||
feature: FEATURES.privateIn, | ||
loose: options.loose, | ||
const id = getWeakSetId( | ||
fieldsWeakSets, | ||
outerClass, | ||
privateElement, | ||
privateElement.node.key.id.name, | ||
injectToFieldInit, | ||
); | ||
|
||
manipulateOptions(opts, parserOpts) { | ||
parserOpts.plugins.push("privateIn"); | ||
path.replaceWith( | ||
template.expression.ast`${id}.has(${path.node.right})`, | ||
); | ||
} | ||
}, | ||
}, | ||
}); | ||
}; | ||
}); |
7 changes: 7 additions & 0 deletions
7
...ugin-proposal-private-property-in-object/test/fixtures/to-native-fields/accessor/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
class Foo { | ||
get #foo() {} | ||
|
||
test(other) { | ||
return #foo in other; | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
...gin-proposal-private-property-in-object/test/fixtures/to-native-fields/accessor/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
var _FooBrandCheck = new WeakSet(); | ||
|
||
class Foo { | ||
constructor() { | ||
_FooBrandCheck.add(this); | ||
} | ||
|
||
get #foo() {} | ||
|
||
test(other) { | ||
return _FooBrandCheck.has(other); | ||
} | ||
|
||
} |
4 changes: 4 additions & 0 deletions
4
...perty-in-object/test/fixtures/to-native-fields/class-expression-in-default-param/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
(x = class { | ||
#foo; | ||
test(other) { return #foo in other } | ||
}) => {} |
12 changes: 12 additions & 0 deletions
12
...erty-in-object/test/fixtures/to-native-fields/class-expression-in-default-param/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
(x = (() => { | ||
var _fooBrandCheck; | ||
|
||
return _fooBrandCheck = new WeakSet(), class { | ||
#foo = void _fooBrandCheck.add(this); | ||
|
||
test(other) { | ||
return _fooBrandCheck.has(other); | ||
} | ||
|
||
}; | ||
})()) => {}; |
9 changes: 9 additions & 0 deletions
9
...vate-property-in-object/test/fixtures/to-native-fields/class-expression-instance/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
function fn() { | ||
return new class { | ||
#priv; | ||
|
||
method(obj) { | ||
return #priv in obj; | ||
} | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
...ate-property-in-object/test/fixtures/to-native-fields/class-expression-instance/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
|
||
})(); | ||
} |
9 changes: 9 additions & 0 deletions
9
...rivate-property-in-object/test/fixtures/to-native-fields/class-expression-static/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
function fn() { | ||
return new class { | ||
static #priv; | ||
|
||
method(obj) { | ||
return #priv in obj; | ||
} | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
...ivate-property-in-object/test/fixtures/to-native-fields/class-expression-static/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
|
||
})(); | ||
} |
7 changes: 7 additions & 0 deletions
7
...-plugin-proposal-private-property-in-object/test/fixtures/to-native-fields/field/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
class Foo { | ||
#foo = 1; | ||
|
||
test(other) { | ||
return #foo in other; | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
...plugin-proposal-private-property-in-object/test/fixtures/to-native-fields/field/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
|
||
} |
28 changes: 28 additions & 0 deletions
28
...ivate-property-in-object/test/fixtures/to-native-fields/half-constructed-instance/exec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); |
14 changes: 14 additions & 0 deletions
14
...vate-property-in-object/test/fixtures/to-native-fields/half-constructed-instance/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() {} | ||
} |
3 changes: 3 additions & 0 deletions
3
...-property-in-object/test/fixtures/to-native-fields/half-constructed-instance/options.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"minNodeVersion": "14.0.0" | ||
} |
29 changes: 29 additions & 0 deletions
29
...ate-property-in-object/test/fixtures/to-native-fields/half-constructed-instance/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() {} | ||
|
||
} |
Oops, something went wrong.