Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(transformer/class-properties): transform private in expression #8202

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/oxc_transformer/src/common/helper_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ pub enum Helper {
SuperPropSet,
ReadOnlyError,
WriteOnlyError,
CheckInRHS,
}

impl Helper {
Expand Down Expand Up @@ -191,6 +192,7 @@ impl Helper {
Self::SuperPropSet => "superPropSet",
Self::ReadOnlyError => "readOnlyError",
Self::WriteOnlyError => "writeOnlyError",
Self::CheckInRHS => "checkInRHS",
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/oxc_transformer/src/es2022/class_properties/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ impl<'a, 'ctx> Traverse<'a> for ClassProperties<'a, 'ctx> {
Expression::TaggedTemplateExpression(_) => {
self.transform_tagged_template_expression(expr, ctx);
}
// "#prop in object"
Expression::PrivateInExpression(_) => {
self.transform_private_in_expression(expr, ctx);
}
_ => {}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use std::mem;

use oxc_allocator::String as ArenaString;
use oxc_allocator::{Box as ArenaBox, String as ArenaString};
use oxc_ast::{ast::*, NONE};
use oxc_span::SPAN;
use oxc_syntax::{reference::ReferenceId, symbol::SymbolId};
Expand Down Expand Up @@ -1820,6 +1820,63 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
*target = AssignmentTarget::from(replacement.into_member_expression());
}

/// Transform private field in expression.
///
/// * Static
/// `#prop in object` -> `_checkInRHS(object) === Class`
///
/// * Instance prop
/// `#prop in object` -> `_prop.has(_checkInRHS(object))`
///
/// * Instance method
/// `#method in object` -> `_Class_brand.has(_checkInRHS(object))`
///
// `#[inline]` so that compiler sees that `expr` is an `Expression::PrivateFieldExpression`
#[inline]
pub(super) fn transform_private_in_expression(
&mut self,
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let Expression::PrivateInExpression(private_in) = ctx.ast.move_expression(expr) else {
unreachable!();
};

*expr = self.transform_private_in_expression_impl(private_in, ctx);
}

fn transform_private_in_expression_impl(
&mut self,
private_field: ArenaBox<'a, PrivateInExpression<'a>>,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let PrivateInExpression { left, right, span, .. } = private_field.unbox();

let ResolvedPrivateProp { class_bindings, prop_binding, is_method, is_static, .. } =
self.classes_stack.find_private_prop(&left);

if is_static {
let class_binding = class_bindings.get_or_init_static_binding(ctx);
let class_ident = class_binding.create_read_expression(ctx);
let left = self.create_check_in_rhs(right, SPAN, ctx);
return ctx.ast.expression_binary(
span,
left,
BinaryOperator::StrictEquality,
class_ident,
);
}

let callee = if is_method {
class_bindings.brand().create_read_expression(ctx)
} else {
prop_binding.create_read_expression(ctx)
};
let callee = create_member_callee(callee, "has", ctx);
let argument = self.create_check_in_rhs(right, SPAN, ctx);
ctx.ast.expression_call(span, callee, NONE, ctx.ast.vec1(Argument::from(argument)), false)
}

/// Duplicate object to be used in get/set pair.
///
/// If `object` may have side effects, create a temp var `_object` and assign to it.
Expand Down Expand Up @@ -2151,4 +2208,19 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
let expressions = ctx.ast.vec_from_array([object, error]);
ctx.ast.expression_sequence(span, expressions)
}

/// _checkInRHS(object)
fn create_check_in_rhs(
&self,
object: Expression<'a>,
span: Span,
ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
self.ctx.helper_call_expr(
Helper::CheckInRHS,
span,
ctx.ast.vec1(Argument::from(object)),
ctx,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
var _Foo_brand = new WeakSet();
class Foo {
constructor() {
babelHelpers.classPrivateMethodInitSpec(this, _Foo_brand);
}
test(other) {
return _Foo_brand.has(babelHelpers.checkInRHS(other));
}
}
function _get_foo() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Foo {
test(other) {
return babelHelpers.checkInRHS(other) === Foo;
}
}
function _get_foo() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
var _Foo_brand = new WeakSet();
class Foo {
constructor() {
babelHelpers.classPrivateMethodInitSpec(this, _Foo_brand);
}
test(other) {
return _Foo_brand.has(babelHelpers.checkInRHS(other));
}
}
function _get_foo() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
var _F_brand = new WeakSet();
var _x = new WeakMap();
var _y = new WeakMap();
class F {
constructor() {
babelHelpers.classPrivateMethodInitSpec(this, _F_brand);
babelHelpers.classPrivateFieldInitSpec(this, _x, 0);
babelHelpers.classPrivateFieldInitSpec(this, _y, (() => {
throw "error";
})());
}
m() {
_F_brand.has(babelHelpers.checkInRHS(this));
_x.has(babelHelpers.checkInRHS(this));
_y.has(babelHelpers.checkInRHS(this));
_F_brand.has(babelHelpers.checkInRHS(this));
}
}
function _get_w() {}
function _z() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Foo {
test(other) {
return babelHelpers.checkInRHS(other) === Foo;
}
}
function _get_foo() {}
79 changes: 2 additions & 77 deletions tasks/transform_conformance/snapshots/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
commit: 54a8389f

Passed: 661/1154
Passed: 686/1154

# All Passed:
* babel-plugin-transform-logical-assignment-operators
Expand Down Expand Up @@ -811,7 +811,7 @@ x Output mismatch
x Output mismatch


# babel-plugin-transform-private-property-in-object (0/59)
# babel-plugin-transform-private-property-in-object (25/59)
* assumption-privateFieldsAsProperties/accessor/input.js
x Output mismatch

Expand Down Expand Up @@ -872,36 +872,6 @@ x Output mismatch
* assumption-privateFieldsAsSymbols/static-method/input.js
x Output mismatch

* private/accessor/input.js
x Output mismatch

* private/field/input.js
x Output mismatch

* private/method/input.js
x Output mismatch

* private/native-classes/input.js
x Output mismatch

* private/nested-class/input.js
x Output mismatch

* private/nested-class-other-redeclared/input.js
x Output mismatch

* private/nested-class-redeclared/input.js
x Output mismatch

* private/static-accessor/input.js
x Output mismatch

* private/static-field/input.js
x Output mismatch

* private/static-method/input.js
x Output mismatch

* private/static-shadow/input.js
x Output mismatch

Expand All @@ -914,9 +884,6 @@ x Output mismatch
* private-loose/method/input.js
x Output mismatch

* private-loose/native-classes/input.js
x Output mismatch

* private-loose/nested-class/input.js
x Output mismatch

Expand All @@ -938,51 +905,9 @@ x Output mismatch
* private-loose/static-shadow/input.js
x Output mismatch

* to-native-fields/accessor/input.js
x Output mismatch

* to-native-fields/class-expression-in-default-param/input.js
x Output mismatch

* to-native-fields/class-expression-instance/input.js
x Output mismatch

* to-native-fields/class-expression-static/input.js
x Output mismatch

* to-native-fields/field/input.js
x Output mismatch

* to-native-fields/half-constructed-instance/input.js
x Output mismatch

* to-native-fields/half-constructed-static/input.js
x Output mismatch

* to-native-fields/method/input.js
x Output mismatch

* to-native-fields/multiple-checks/input.js
x Output mismatch

* to-native-fields/nested-class/input.js
x Output mismatch

* to-native-fields/nested-class-other-redeclared/input.js
x Output mismatch

* to-native-fields/nested-class-redeclared/input.js
x Output mismatch

* to-native-fields/static-accessor/input.js
x Output mismatch

* to-native-fields/static-field/input.js
x Output mismatch

* to-native-fields/static-method/input.js
x Output mismatch

* to-native-fields/static-shadow/input.js
x Output mismatch

Expand Down
48 changes: 17 additions & 31 deletions tasks/transform_conformance/snapshots/babel_exec.snap.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ commit: 54a8389f

node: v22.12.0

Passed: 283 of 374 (75.67%)
Passed: 291 of 374 (77.81%)

Failures:

Expand Down Expand Up @@ -78,9 +78,6 @@ AssertionError: expected '_Class' to be 'Foo' // Object.is equality
AssertionError: expected '_Class' to be 'Foo' // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-properties-test-fixtures-public-static-infer-name-exec.test.js:9:19

./fixtures/babel/babel-plugin-transform-class-static-block-test-fixtures-integration-loose-private-in-exec.test.js
Private field '#bar' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-class-static-block-test-fixtures-integration-loose-private-methods-access-exec.test.js
TypeError: attempted to use private field on non-instance
at _classPrivateFieldBase (./node_modules/.pnpm/@[email protected]/node_modules/@babel/runtime/helpers/classPrivateFieldLooseBase.js:2:44)
Expand All @@ -91,9 +88,6 @@ TypeError: attempted to use private field on non-instance
AssertionError: expected [Function Base] to be undefined // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-class-static-block-test-fixtures-integration-new-target-exec.test.js:10:29

./fixtures/babel/babel-plugin-transform-class-static-block-test-fixtures-integration-private-in-exec.test.js
Private field '#bar' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-optional-chaining-test-fixtures-assumption-noDocumentAll-parenthesized-expression-member-call-exec.test.js
TypeError: Cannot read properties of undefined (reading 'x')
at m (./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-optional-chaining-test-fixtures-assumption-noDocumentAll-parenthesized-expression-member-call-exec.test.js:10:16)
Expand Down Expand Up @@ -415,40 +409,32 @@ TypeError: "#privateStaticFieldValue" is write-only
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-methods-test-fixtures-static-accessors-privateFieldsAsSymbols-get-only-setter-exec.test.js:14:12

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-assumption-privateFieldsAsProperties-method-exec.test.js
Private field '#foo' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-assumption-privateFieldsAsProperties-rhs-not-object-exec.test.js
Private field '#p' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-assumption-privateFieldsAsSymbols-method-exec.test.js
Private field '#foo' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-assumption-privateFieldsAsSymbols-rhs-not-object-exec.test.js
Private field '#p' must be declared in an enclosing class
ReferenceError: _Foo_brand is not defined
at new Foo (./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-assumption-privateFieldsAsProperties-method-exec.test.js:8:38)
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-assumption-privateFieldsAsProperties-method-exec.test.js:19:13

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-loose-rhs-not-object-exec.test.js
Private field '#p' must be declared in an enclosing class
AssertionError: expected [Function] to throw error including 'right-hand side of \'in\' should be a…' but got '_Class_brand is not defined'
at Proxy.<anonymous> (./node_modules/.pnpm/@[email protected]/node_modules/@vitest/expect/dist/index.js:1438:21)
at Proxy.<anonymous> (./node_modules/.pnpm/@[email protected]/node_modules/@vitest/expect/dist/index.js:923:17)
at Proxy.methodWrapper (./node_modules/.pnpm/[email protected]/node_modules/chai/chai.js:1610:25)
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-loose-rhs-not-object-exec.test.js:176:5

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-loose-static-shadow-exec.test.js
Private field '#x' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-rhs-not-object-exec.test.js
Private field '#p' must be declared in an enclosing class
AssertionError: expected 2 to be 5 // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-loose-static-shadow-exec.test.js:18:25

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-static-shadow-exec.test.js
Private field '#x' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-to-native-fields-half-constructed-instance-exec.test.js
Private field '#w' must be declared in an enclosing class
AssertionError: expected 2 to be 5 // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-private-static-shadow-exec.test.js:18:25

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-to-native-fields-half-constructed-static-exec.test.js
Private field '#w' must be declared in an enclosing class

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-to-native-fields-rhs-not-object-exec.test.js
Private field '#p' must be declared in an enclosing class
AssertionError: expected true to be false // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-to-native-fields-half-constructed-static-exec.test.js:29:15

./fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-to-native-fields-static-shadow-exec.test.js
Private field '#x' must be declared in an enclosing class
AssertionError: expected 2 to be 5 // Object.is equality
at ./tasks/transform_conformance/fixtures/babel/babel-plugin-transform-private-property-in-object-test-fixtures-to-native-fields-static-shadow-exec.test.js:18:25

./fixtures/babel/babel-preset-env-test-fixtures-plugins-integration-issue-15170-exec.test.js
AssertionError: expected [Function] to not throw an error but 'ReferenceError: x is not defined' was thrown
Expand Down
Loading