Skip to content

Commit

Permalink
Implement noUninitializedPrivateFieldAccess assumption
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Feb 8, 2024
1 parent 9a9fde7 commit 1332940
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 17 deletions.
1 change: 1 addition & 0 deletions packages/babel-core/src/config/validation/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ const knownAssumptions = [
"noDocumentAll",
"noIncompleteNsImportDetection",
"noNewArrows",
"noUninitializedPrivateFieldAccess",
"objectRestNoSymbols",
"privateFieldsAsSymbols",
"privateFieldsAsProperties",
Expand Down
89 changes: 72 additions & 17 deletions packages/babel-helper-create-class-features-plugin/src/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,7 @@ export function privateNameVisitorFactory<S, V>(
// Traverses the outer portion of a class, without touching the class's inner
// scope, for private names.
const nestedVisitor = traverse.visitors.merge([
{
...visitor,
},
{ ...visitor },
environmentVisitor,
]);

Expand Down Expand Up @@ -216,6 +214,7 @@ interface PrivateNameState {
classRef: t.Identifier;
file: File;
noDocumentAll: boolean;
noUninitializedPrivateFieldAccess: boolean;
innerBinding?: t.Identifier;
}

Expand Down Expand Up @@ -353,6 +352,14 @@ function writeOnlyError(file: File, name: string) {
]);
}

function buildStaticPrivateFieldAccess<N extends t.Expression>(
expr: N,
noUninitializedPrivateFieldAccess: boolean,
) {
if (noUninitializedPrivateFieldAccess) return expr;
return t.memberExpression(expr, t.identifier("_"));
}

const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
{
memoise(member, count) {
Expand All @@ -378,7 +385,13 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
},

get(member) {
const { classRef, privateNamesMap, file, innerBinding } = this;
const {
classRef,
privateNamesMap,
file,
innerBinding,
noUninitializedPrivateFieldAccess,
} = this;
const { name } = (member.node.property as t.PrivateName).id;
const {
id,
Expand Down Expand Up @@ -416,16 +429,19 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =

if (!isMethod) {
if (skipCheck) {
return t.memberExpression(t.cloneNode(id), t.identifier("_"));
return buildStaticPrivateFieldAccess(
t.cloneNode(id),
noUninitializedPrivateFieldAccess,
);
}

return t.memberExpression(
return buildStaticPrivateFieldAccess(
t.callExpression(file.addHelper("assertClassBrand"), [
receiver,
t.cloneNode(classRef),
t.cloneNode(id),
]),
t.identifier("_"),
noUninitializedPrivateFieldAccess,
);
}

Expand Down Expand Up @@ -507,7 +523,12 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
},

set(member, value) {
const { classRef, privateNamesMap, file } = this;
const {
classRef,
privateNamesMap,
file,
noUninitializedPrivateFieldAccess,
} = this;
const { name } = (member.node.property as t.PrivateName).id;
const {
id,
Expand Down Expand Up @@ -566,7 +587,10 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
}
return t.assignmentExpression(
"=",
t.memberExpression(t.cloneNode(id), t.identifier("_")),
buildStaticPrivateFieldAccess(
t.cloneNode(id),
noUninitializedPrivateFieldAccess,
),
skipCheck
? value
: t.callExpression(file.addHelper("assertClassBrand"), [
Expand Down Expand Up @@ -607,7 +631,12 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
},

destructureSet(member) {
const { classRef, privateNamesMap, file } = this;
const {
classRef,
privateNamesMap,
file,
noUninitializedPrivateFieldAccess,
} = this;
const { name } = (member.node.property as t.PrivateName).id;
const {
id,
Expand Down Expand Up @@ -661,7 +690,19 @@ const privateNameHandlerSpec: Handler<PrivateNameState & Receiver> & Receiver =
}

if (isStatic && !isMethod) {
return this.get(member);
const getCall = this.get(member);
if (
!noUninitializedPrivateFieldAccess ||
!t.isCallExpression(getCall)
) {
return getCall;
}
const ref = getCall.arguments.pop();
getCall.arguments.push(template.expression.ast`(_) => ${ref} = _`);
return t.memberExpression(
t.callExpression(file.addHelper("toSetter"), [getCall]),
t.identifier("_"),
);
}

const setCall = this.set(member, t.identifier("_"));
Expand Down Expand Up @@ -773,10 +814,12 @@ export function transformPrivateNamesUsage(
privateNamesMap: PrivateNamesMap,
{
privateFieldsAsProperties,
noUninitializedPrivateFieldAccess,
noDocumentAll,
innerBinding,
}: {
privateFieldsAsProperties: boolean;
noUninitializedPrivateFieldAccess: boolean;
noDocumentAll: boolean;
innerBinding: t.Identifier;
},
Expand All @@ -795,6 +838,7 @@ export function transformPrivateNamesUsage(
file: state,
...handler,
noDocumentAll,
noUninitializedPrivateFieldAccess,
innerBinding,
});
body.traverse(privateInVisitor, {
Expand Down Expand Up @@ -868,14 +912,20 @@ function buildPrivateInstanceFieldInitSpec(
function buildPrivateStaticFieldInitSpec(
prop: NodePath<t.ClassPrivateProperty>,
privateNamesMap: PrivateNamesMap,
noUninitializedPrivateFieldAccess: boolean,
) {
const privateName = privateNamesMap.get(prop.node.key.id.name);
return inheritPropComments(
template.statement.ast`
var ${t.cloneNode(privateName.id)} = {

const value = noUninitializedPrivateFieldAccess
? prop.node.value
: template.expression.ast`{
_: ${prop.node.value || t.buildUndefinedNode()}
};
`,
}`;

return inheritPropComments(
t.variableDeclaration("var", [
t.variableDeclarator(t.cloneNode(privateName.id), value),
]),
prop,
);
}
Expand Down Expand Up @@ -1348,6 +1398,7 @@ export function buildFieldsInitNodes(
file: File,
setPublicClassFields: boolean,
privateFieldsAsProperties: boolean,
noUninitializedPrivateFieldAccess: boolean,
constantSuper: boolean,
innerBindingRef: t.Identifier | null,
) {
Expand Down Expand Up @@ -1453,7 +1504,11 @@ export function buildFieldsInitNodes(
);
} else {
staticNodes.push(
buildPrivateStaticFieldInitSpec(prop, privateNamesMap),
buildPrivateStaticFieldInitSpec(
prop,
privateNamesMap,
noUninitializedPrivateFieldAccess,
),
);
}
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ export function createClassFeaturePlugin({
const setPublicClassFields = api.assumption("setPublicClassFields");
const privateFieldsAsSymbols = api.assumption("privateFieldsAsSymbols");
const privateFieldsAsProperties = api.assumption("privateFieldsAsProperties");
const noUninitializedPrivateFieldAccess =
api.assumption("noUninitializedPrivateFieldAccess") ?? false;
const constantSuper = api.assumption("constantSuper");
const noDocumentAll = api.assumption("noDocumentAll");

Expand Down Expand Up @@ -254,6 +256,7 @@ export function createClassFeaturePlugin({
{
privateFieldsAsProperties:
privateFieldsAsSymbolsOrProperties ?? loose,
noUninitializedPrivateFieldAccess,
noDocumentAll,
innerBinding,
},
Expand Down Expand Up @@ -294,6 +297,7 @@ export function createClassFeaturePlugin({
file,
setPublicClassFields ?? loose,
privateFieldsAsSymbolsOrProperties ?? loose,
noUninitializedPrivateFieldAccess,
constantSuper ?? loose,
innerBinding,
));
Expand All @@ -315,6 +319,7 @@ export function createClassFeaturePlugin({
file,
setPublicClassFields ?? loose,
privateFieldsAsSymbolsOrProperties ?? loose,
noUninitializedPrivateFieldAccess,
constantSuper ?? loose,
innerBinding,
));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"plugins": ["transform-class-properties"],
"assumptions": {
"noUninitializedPrivateFieldAccess": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class A {
static #x = 0;

static dynamicCheck() {
expect(this.#x).toBe(0);
expect(this.#x = 1).toBe(1);
expect(this.#x).toBe(1);
[this.#x] = [2];
expect(this.#x).toBe(2);
}

static noCheck() {
expect(A.#x).toBe(2);
expect(A.#x = 3).toBe(3);
[A.#x] = [4];
expect(A.#x).toBe(4);
}
}

A.dynamicCheck();
A.noCheck();


Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class A {
static #x = 2;

static dynamicCheck() {
this.#x;
this.#x = 2;
[this.#x] = [];
}

static noCheck() {
A.#x;
A.#x = 2;
[A.#x] = [];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class A {
static dynamicCheck() {
babelHelpers.assertClassBrand(this, A, _x);
_x = babelHelpers.assertClassBrand(this, A, 2);
[babelHelpers.toSetter(babelHelpers.assertClassBrand(this, A, _ => _x = _))._] = [];
}
static noCheck() {
_x;
_x = 2;
[_x] = [];
}
}
var _x = 2;

0 comments on commit 1332940

Please sign in to comment.