diff --git a/crates/oxc_transformer/src/common/var_declarations.rs b/crates/oxc_transformer/src/common/var_declarations.rs index 7701c15a5378ce..66fb24af9e44cb 100644 --- a/crates/oxc_transformer/src/common/var_declarations.rs +++ b/crates/oxc_transformer/src/common/var_declarations.rs @@ -98,7 +98,6 @@ impl<'a> VarDeclarationsStore<'a> { /// Add a `let` declaration to be inserted at top of current enclosing statement block, /// given a `BoundIdentifier`. - #[expect(dead_code)] pub fn insert_let( &self, binding: &BoundIdentifier<'a>, diff --git a/crates/oxc_transformer/src/es2022/class_properties.rs b/crates/oxc_transformer/src/es2022/class_properties.rs index 5720ee0ef01ba6..33a7b1c505c658 100644 --- a/crates/oxc_transformer/src/es2022/class_properties.rs +++ b/crates/oxc_transformer/src/es2022/class_properties.rs @@ -152,6 +152,8 @@ impl<'a, 'ctx> Traverse<'a> for ClassProperties<'a, 'ctx> { } false } else { + // TODO: If a method with computed key which is before last property with computed key, + // convert method key to temp var to preserve evaluations order for keys true } }); @@ -195,37 +197,46 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { // Convert to assignment or `_defineProperty` call, depending on `loose` option let init_expr = if self.set_public_class_fields { // `this.foo = value` - let key = match &prop.key { - PropertyKey::StaticIdentifier(ident) => ident.as_ref().clone(), - _ => { + let this = ctx.ast.expression_this(SPAN); + let left = match &mut prop.key { + PropertyKey::StaticIdentifier(ident) => { + ctx.ast.member_expression_static(SPAN, this, ident.as_ref().clone(), false) + } + PropertyKey::PrivateIdentifier(_) => { // TODO: Handle private properties - // TODO: Handle computed property key - ctx.ast.identifier_name(SPAN, Atom::from("oops")) + ctx.ast.member_expression_static( + SPAN, + this, + ctx.ast.identifier_name(SPAN, Atom::from("oops")), + false, + ) + } + key @ match_expression!(PropertyKey) => { + let key = self.create_computed_key_temp_var(key.to_expression_mut(), ctx); + ctx.ast.member_expression_computed(SPAN, this, key, false) } }; + // TODO: Should this have span of the original `PropertyDefinition`? ctx.ast.expression_assignment( SPAN, AssignmentOperator::Assign, - AssignmentTarget::from(ctx.ast.member_expression_static( - SPAN, - ctx.ast.expression_this(SPAN), - key, - false, - )), + AssignmentTarget::from(left), value, ) } else { // `_defineProperty(this, "foo", value)` - let key = match &prop.key { + let key = match &mut prop.key { PropertyKey::StaticIdentifier(ident) => { ctx.ast.expression_string_literal(ident.span, ident.name.clone()) } - _ => { + PropertyKey::PrivateIdentifier(_) => { // TODO: Handle private properties - // TODO: Handle computed property key ctx.ast.expression_string_literal(SPAN, Atom::from("oops")) } + key @ match_expression!(PropertyKey) => { + self.create_computed_key_temp_var(key.to_expression_mut(), ctx) + } }; let args = ctx.ast.vec_from_iter( [ctx.ast.expression_this(SPAN), key, value].into_iter().map(Argument::from), @@ -237,6 +248,36 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> { instance_inits.push(init_expr); } + /// Convert computed property/method key to a temp var. + /// + /// Transformation is: + /// `class C { [x()] = 2; }` -> `let _x; _x = x(); class C { constructor() { this[_x] = 2; } }` + /// This function creates the `let _x;` and `_x = x();` statements and returns `_x`. + /// + /// TODO: For class expressions, need to instead insert assignments before the expression. + /// `let C = class C { [x()] = 2; };` + /// -> `let _x; let C = (_x = x(), class C { constructor() { this[_x] = 2; } });` + fn create_computed_key_temp_var( + &mut self, + key: &mut Expression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Expression<'a> { + let key = ctx.ast.move_expression(key); + + // TODO: Bound vars and literals do not need temp vars + // e.g. `let x = 'x'; class C { [x] = 1; }` or `class C { ['x'] = 1; }` + + let class_scope_id = ctx.current_scope_id(); + let parent_scope_id = ctx.scopes().get_parent_id(class_scope_id).unwrap(); + // TODO: `_x = ...` should be a separate statement/expression, not initialized in the `let` statement. + // TODO: Handle if is a class expression defined in a function's params. + let binding = + ctx.generate_uid_based_on_node(&key, parent_scope_id, SymbolFlags::BlockScopedVariable); + self.ctx.var_declarations.insert_let(&binding, Some(key), ctx); + + binding.create_read_expression(ctx) + } + /// Convert static property to initialization expression. #[expect(clippy::unused_self)] fn convert_static_property( diff --git a/tasks/transform_conformance/snapshots/babel_exec.snap.md b/tasks/transform_conformance/snapshots/babel_exec.snap.md index e7043b25891a8f..2e9a8d5be7f92d 100644 --- a/tasks/transform_conformance/snapshots/babel_exec.snap.md +++ b/tasks/transform_conformance/snapshots/babel_exec.snap.md @@ -1,6 +1,6 @@ commit: d20b314c -Passed: 46/225 +Passed: 49/225 # All Passed: * babel-plugin-transform-class-static-block @@ -24,7 +24,7 @@ exec failed exec failed -# babel-plugin-transform-class-properties (1/126) +# babel-plugin-transform-class-properties (4/126) * assumption-noDocumentAll/optional-chain-before-member-call/exec.js exec failed @@ -37,9 +37,6 @@ exec failed * assumption-setPublicClassFields/computed-initialization-order/exec.js exec failed -* assumption-setPublicClassFields/instance-computed/exec.js -exec failed - * assumption-setPublicClassFields/static/exec.js exec failed @@ -58,9 +55,6 @@ exec failed * assumption-setPublicClassFields/static-undefined/exec.js exec failed -* class-name-tdz/general/exec.js -exec failed - * class-name-tdz/static-edgest-case/exec.js exec failed @@ -265,6 +259,9 @@ exec failed * private-loose/nested-class-computed-redeclared/exec.js exec failed +* private-loose/nested-class-extends-computed/exec.js +exec failed + * private-loose/nested-class-extends-computed-redeclared/exec.js exec failed @@ -370,9 +367,6 @@ exec failed * public-loose/computed-initialization-order/exec.js exec failed -* public-loose/instance-computed/exec.js -exec failed - * public-loose/static/exec.js exec failed @@ -391,9 +385,6 @@ exec failed * public-loose/static-undefined/exec.js exec failed -* regression/15098/exec.js -exec failed - * regression/7371/exec.js exec failed