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

This PR support transforms `#prop in object` in class-properties to cover https://www.npmjs.com/package/@babel/plugin-transform-private-property-in-object  plugin does
This commit is contained in:
Dunqing 2024-12-31 12:30:57 +00:00
parent 80c16526fb
commit 0592a8b43f
10 changed files with 150 additions and 109 deletions

View file

@ -163,6 +163,7 @@ pub enum Helper {
SuperPropSet,
ReadOnlyError,
WriteOnlyError,
CheckInRHS,
}
impl Helper {
@ -191,6 +192,7 @@ impl Helper {
Self::SuperPropSet => "superPropSet",
Self::ReadOnlyError => "readOnlyError",
Self::WriteOnlyError => "writeOnlyError",
Self::CheckInRHS => "checkInRHS",
}
}
}

View file

@ -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);
}
_ => {}
}
}

View file

@ -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};
@ -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.
@ -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,
)
}
}

View file

@ -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() {}

View file

@ -0,0 +1,6 @@
class Foo {
test(other) {
return babelHelpers.checkInRHS(other) === Foo;
}
}
function _get_foo() {}

View file

@ -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() {}

View file

@ -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() {}

View file

@ -0,0 +1,6 @@
class Foo {
test(other) {
return babelHelpers.checkInRHS(other) === Foo;
}
}
function _get_foo() {}

View file

@ -1,6 +1,6 @@
commit: 54a8389f
Passed: 661/1154
Passed: 686/1154
# All Passed:
* babel-plugin-transform-logical-assignment-operators
@ -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
@ -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
@ -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
@ -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

View file

@ -2,7 +2,7 @@ commit: 54a8389f
node: v22.12.0
Passed: 283 of 374 (75.67%)
Passed: 291 of 374 (77.81%)
Failures:
@ -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/@babel+runtime@7.26.0/node_modules/@babel/runtime/helpers/classPrivateFieldLooseBase.js:2:44)
@ -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)
@ -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/@vitest+expect@2.1.2/node_modules/@vitest/expect/dist/index.js:1438:21)
at Proxy.<anonymous> (./node_modules/.pnpm/@vitest+expect@2.1.2/node_modules/@vitest/expect/dist/index.js:923:17)
at Proxy.methodWrapper (./node_modules/.pnpm/chai@5.1.2/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