Skip to content

Commit 3e60843

Browse files
feat: Implement privateFieldsAsSymbols assumption for classes (#15435)
Co-authored-by: Nicolò Ribaudo <[email protected]>
1 parent 79e1452 commit 3e60843

File tree

119 files changed

+1931
-14
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

119 files changed

+1931
-14
lines changed

packages/babel-core/src/config/validation/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ const knownAssumptions = [
277277
"noIncompleteNsImportDetection",
278278
"noNewArrows",
279279
"objectRestNoSymbols",
280+
"privateFieldsAsSymbols",
280281
"privateFieldsAsProperties",
281282
"pureGetters",
282283
"setClassMethods",

packages/babel-helper-create-class-features-plugin/src/fields.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,22 @@ export function buildPrivateNamesMap(props: PropPath[]) {
5858
export function buildPrivateNamesNodes(
5959
privateNamesMap: PrivateNamesMap,
6060
privateFieldsAsProperties: boolean,
61+
privateFieldsAsSymbols: boolean,
6162
state: File,
6263
) {
6364
const initNodes: t.Statement[] = [];
6465

6566
for (const [name, value] of privateNamesMap) {
66-
// When the privateFieldsAsProperties assumption is enabled,
67-
// both static and instance fields are transpiled using a
68-
// secret non-enumerable property. Hence, we also need to generate that
69-
// key (using the classPrivateFieldLooseKey helper).
70-
// In spec mode, only instance fields need a "private name" initializer
71-
// because static fields are directly assigned to a variable in the
72-
// buildPrivateStaticFieldInitSpec function.
67+
// - When the privateFieldsAsProperties assumption is enabled,
68+
// both static and instance fields are transpiled using a
69+
// secret non-enumerable property. Hence, we also need to generate that
70+
// key (using the classPrivateFieldLooseKey helper).
71+
// - When the privateFieldsAsSymbols assumption is enabled,
72+
// both static and instance fields are transpiled using a
73+
// unique Symbol to define a non-enumerable property.
74+
// - In spec mode, only instance fields need a "private name" initializer
75+
// because static fields are directly assigned to a variable in the
76+
// buildPrivateStaticFieldInitSpec function.
7377
const { static: isStatic, method: isMethod, getId, setId } = value;
7478
const isAccessor = getId || setId;
7579
const id = t.cloneNode(value.id);
@@ -80,6 +84,8 @@ export function buildPrivateNamesNodes(
8084
init = t.callExpression(state.addHelper("classPrivateFieldLooseKey"), [
8185
t.stringLiteral(name),
8286
]);
87+
} else if (privateFieldsAsSymbols) {
88+
init = t.callExpression(t.identifier("Symbol"), [t.stringLiteral(name)]);
8389
} else if (!isStatic) {
8490
init = t.newExpression(
8591
t.identifier(!isMethod || isAccessor ? "WeakMap" : "WeakSet"),

packages/babel-helper-create-class-features-plugin/src/index.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,33 @@ export function createClassFeaturePlugin({
4848
inherits,
4949
}: Options): PluginObject {
5050
const setPublicClassFields = api.assumption("setPublicClassFields");
51+
const privateFieldsAsSymbols = api.assumption("privateFieldsAsSymbols");
5152
const privateFieldsAsProperties = api.assumption("privateFieldsAsProperties");
5253
const constantSuper = api.assumption("constantSuper");
5354
const noDocumentAll = api.assumption("noDocumentAll");
5455

56+
if (privateFieldsAsProperties && privateFieldsAsSymbols) {
57+
throw new Error(
58+
`Cannot enable both the "privateFieldsAsProperties" and ` +
59+
`"privateFieldsAsSymbols" assumptions as the same time.`,
60+
);
61+
}
62+
const privateFieldsAsSymbolsOrProperties =
63+
privateFieldsAsProperties || privateFieldsAsSymbols;
64+
5565
if (loose === true) {
56-
const explicit = [];
66+
type AssumptionName = Parameters<PluginAPI["assumption"]>[0];
67+
const explicit: `"${AssumptionName}"`[] = [];
5768

5869
if (setPublicClassFields !== undefined) {
5970
explicit.push(`"setPublicClassFields"`);
6071
}
6172
if (privateFieldsAsProperties !== undefined) {
6273
explicit.push(`"privateFieldsAsProperties"`);
6374
}
75+
if (privateFieldsAsSymbols !== undefined) {
76+
explicit.push(`"privateFieldsAsSymbols"`);
77+
}
6478
if (explicit.length !== 0) {
6579
console.warn(
6680
`[${name}]: You are using the "loose: true" option and you are` +
@@ -71,7 +85,7 @@ export function createClassFeaturePlugin({
7185
` following top-level option:\n` +
7286
`\t"assumptions": {\n` +
7387
`\t\t"setPublicClassFields": true,\n` +
74-
`\t\t"privateFieldsAsProperties": true\n` +
88+
`\t\t"privateFieldsAsSymbols": true\n` +
7589
`\t}`,
7690
);
7791
}
@@ -191,6 +205,7 @@ export function createClassFeaturePlugin({
191205
const privateNamesNodes = buildPrivateNamesNodes(
192206
privateNamesMap,
193207
privateFieldsAsProperties ?? loose,
208+
privateFieldsAsSymbols ?? false,
194209
file,
195210
);
196211

@@ -199,7 +214,8 @@ export function createClassFeaturePlugin({
199214
path,
200215
privateNamesMap,
201216
{
202-
privateFieldsAsProperties: privateFieldsAsProperties ?? loose,
217+
privateFieldsAsProperties:
218+
privateFieldsAsSymbolsOrProperties ?? loose,
203219
noDocumentAll,
204220
innerBinding,
205221
},
@@ -231,7 +247,7 @@ export function createClassFeaturePlugin({
231247
privateNamesMap,
232248
file,
233249
setPublicClassFields ?? loose,
234-
privateFieldsAsProperties ?? loose,
250+
privateFieldsAsSymbolsOrProperties ?? loose,
235251
constantSuper ?? loose,
236252
innerBinding,
237253
));
@@ -246,7 +262,7 @@ export function createClassFeaturePlugin({
246262
privateNamesMap,
247263
file,
248264
setPublicClassFields ?? loose,
249-
privateFieldsAsProperties ?? loose,
265+
privateFieldsAsSymbolsOrProperties ?? loose,
250266
constantSuper ?? loose,
251267
innerBinding,
252268
));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"plugins": ["proposal-class-properties"],
3+
"assumptions": {
4+
"privateFieldsAsProperties": true,
5+
"privateFieldsAsSymbols": true
6+
},
7+
"throws": "Cannot enable both the \"privateFieldsAsProperties\" and \"privateFieldsAsSymbols\" assumptions as the same time."
8+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[proposal-class-properties]: You are using the "loose: true" option and you are explicitly setting a value for the "setPublicClassFields" assumption. The "loose" option can cause incompatibilities with the other class features plugins, so it's recommended that you replace it with the following top-level option:
22
"assumptions": {
33
"setPublicClassFields": true,
4-
"privateFieldsAsProperties": true
4+
"privateFieldsAsSymbols": true
55
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[proposal-class-properties]: You are using the "loose: true" option and you are explicitly setting a value for the "setPublicClassFields" assumption. The "loose" option can cause incompatibilities with the other class features plugins, so it's recommended that you replace it with the following top-level option:
22
"assumptions": {
33
"setPublicClassFields": true,
4-
"privateFieldsAsProperties": true
4+
"privateFieldsAsSymbols": true
55
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
class Cl {
2+
#privateField = "top secret string";
3+
4+
constructor() {
5+
this.publicField = "not secret string";
6+
}
7+
8+
get #privateFieldValue() {
9+
return this.#privateField;
10+
}
11+
12+
set #privateFieldValue(newValue) {
13+
this.#privateField = newValue;
14+
}
15+
16+
publicGetPrivateField() {
17+
return this.#privateFieldValue;
18+
}
19+
20+
publicSetPrivateField(newValue) {
21+
this.#privateFieldValue = newValue;
22+
}
23+
}
24+
25+
const cl = new Cl();
26+
27+
expect(cl.publicGetPrivateField()).toEqual("top secret string");
28+
29+
cl.publicSetPrivateField("new secret string");
30+
expect(cl.publicGetPrivateField()).toEqual("new secret string");
31+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class Cl {
2+
#privateField = "top secret string";
3+
4+
constructor() {
5+
this.publicField = "not secret string";
6+
}
7+
8+
get #privateFieldValue() {
9+
return this.#privateField;
10+
}
11+
12+
set #privateFieldValue(newValue) {
13+
this.#privateField = newValue;
14+
}
15+
16+
publicGetPrivateField() {
17+
return this.#privateFieldValue;
18+
}
19+
20+
publicSetPrivateField(newValue) {
21+
this.#privateFieldValue = newValue;
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
var _privateField = /*#__PURE__*/Symbol("privateField");
2+
var _privateFieldValue = /*#__PURE__*/Symbol("privateFieldValue");
3+
class Cl {
4+
constructor() {
5+
Object.defineProperty(this, _privateFieldValue, {
6+
get: _get_privateFieldValue,
7+
set: _set_privateFieldValue
8+
});
9+
Object.defineProperty(this, _privateField, {
10+
writable: true,
11+
value: "top secret string"
12+
});
13+
this.publicField = "not secret string";
14+
}
15+
publicGetPrivateField() {
16+
return babelHelpers.classPrivateFieldLooseBase(this, _privateFieldValue)[_privateFieldValue];
17+
}
18+
publicSetPrivateField(newValue) {
19+
babelHelpers.classPrivateFieldLooseBase(this, _privateFieldValue)[_privateFieldValue] = newValue;
20+
}
21+
}
22+
function _get_privateFieldValue() {
23+
return babelHelpers.classPrivateFieldLooseBase(this, _privateField)[_privateField];
24+
}
25+
function _set_privateFieldValue(newValue) {
26+
babelHelpers.classPrivateFieldLooseBase(this, _privateField)[_privateField] = newValue;
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class Cl {
2+
#privateField = 0;
3+
4+
set #privateFieldValue(newValue) {
5+
this.#privateField = newValue;
6+
}
7+
8+
constructor() {
9+
expect(this.#privateFieldValue).toBeUndefined();
10+
}
11+
}
12+
13+
const cl = new Cl();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class Cl {
2+
#privateField = 0;
3+
4+
set #privateFieldValue(newValue) {
5+
this.#privateField = newValue;
6+
}
7+
8+
constructor() {
9+
this.publicField = this.#privateFieldValue;
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
var _privateField = /*#__PURE__*/Symbol("privateField");
2+
var _privateFieldValue = /*#__PURE__*/Symbol("privateFieldValue");
3+
class Cl {
4+
constructor() {
5+
Object.defineProperty(this, _privateFieldValue, {
6+
get: void 0,
7+
set: _set_privateFieldValue
8+
});
9+
Object.defineProperty(this, _privateField, {
10+
writable: true,
11+
value: 0
12+
});
13+
this.publicField = babelHelpers.classPrivateFieldLooseBase(this, _privateFieldValue)[_privateFieldValue];
14+
}
15+
}
16+
function _set_privateFieldValue(newValue) {
17+
babelHelpers.classPrivateFieldLooseBase(this, _privateField)[_privateField] = newValue;
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
let foo;
2+
class Cl {
3+
set #foo(v) { return 1 }
4+
test() {
5+
foo = this.#foo = 2;
6+
}
7+
}
8+
9+
new Cl().test();
10+
11+
expect(foo).toBe(2);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"plugins": ["proposal-private-methods", "proposal-class-properties"],
3+
"assumptions": {
4+
"privateFieldsAsSymbols": true
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class C {
2+
/* before get a */
3+
get #a() { return 42 };
4+
/* after get a */
5+
6+
/* before set a */
7+
set #a(v) {}
8+
/* after set a */
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
var _a = /*#__PURE__*/Symbol("a");
2+
class C {
3+
constructor() {
4+
/* before get a */
5+
Object.defineProperty(this, _a, {
6+
get: _get_a,
7+
set: _set_a
8+
});
9+
}
10+
/* after set a */
11+
}
12+
function _get_a() {
13+
return 42;
14+
}
15+
/* after get a */
16+
/* before set a */
17+
function _set_a(v) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class Cl {
2+
#privateField = 0;
3+
4+
get #privateFieldValue() {
5+
return this.#privateField;
6+
}
7+
8+
constructor() {
9+
expect(() => this.#privateFieldValue = 1).toThrow(TypeError);
10+
expect(() => ([this.#privateFieldValue] = [1])).toThrow(TypeError);
11+
}
12+
}
13+
14+
const cl = new Cl();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class Cl {
2+
#privateField = 0;
3+
4+
get #privateFieldValue() {
5+
return this.#privateField;
6+
}
7+
8+
constructor() {
9+
this.#privateFieldValue = 1;
10+
([this.#privateFieldValue] = [1]);
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
var _privateField = /*#__PURE__*/Symbol("privateField");
2+
var _privateFieldValue = /*#__PURE__*/Symbol("privateFieldValue");
3+
class Cl {
4+
constructor() {
5+
Object.defineProperty(this, _privateFieldValue, {
6+
get: _get_privateFieldValue,
7+
set: void 0
8+
});
9+
Object.defineProperty(this, _privateField, {
10+
writable: true,
11+
value: 0
12+
});
13+
babelHelpers.classPrivateFieldLooseBase(this, _privateFieldValue)[_privateFieldValue] = 1;
14+
[babelHelpers.classPrivateFieldLooseBase(this, _privateFieldValue)[_privateFieldValue]] = [1];
15+
}
16+
}
17+
function _get_privateFieldValue() {
18+
return babelHelpers.classPrivateFieldLooseBase(this, _privateField)[_privateField];
19+
}

0 commit comments

Comments
 (0)