From 4e9e838838e6b2da45f074feae2b5824297f4375 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Sat, 21 Sep 2024 07:48:18 +0000 Subject: [PATCH] fix(transformer): fix arrow function transform (#5933) Fix arrow function transform's treatment of `this` within classes. --- .../src/es2015/arrow_functions.rs | 58 +++++++++++++++---- crates/oxc_transformer/src/es2015/mod.rs | 12 ---- crates/oxc_transformer/src/lib.rs | 5 -- .../snapshots/oxc.snap.md | 2 +- .../arrow-in-class-property-key/input.js | 11 ++++ .../arrow-in-class-property-key/output.js | 14 +++++ .../fixtures/this-in-class-extends/input.js | 9 +++ .../fixtures/this-in-class-extends/output.js | 10 ++++ .../this-in-class-method-key/input.js | 13 +++++ .../this-in-class-method-key/output.js | 14 +++++ .../this-in-class-property-key/input.js | 13 +++++ .../this-in-class-property-key/output.js | 14 +++++ .../fixtures/this-in-class-property/input.js | 9 +++ .../fixtures/this-in-class-property/output.js | 9 +++ .../this-in-class-static-block/input.js | 17 ++++++ .../this-in-class-static-block/output.js | 17 ++++++ 16 files changed, 199 insertions(+), 28 deletions(-) create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-property-key/input.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-property-key/output.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-extends/input.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-extends/output.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-method-key/input.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-method-key/output.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property-key/input.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property-key/output.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property/input.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property/output.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-static-block/input.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-static-block/output.js diff --git a/crates/oxc_transformer/src/es2015/arrow_functions.rs b/crates/oxc_transformer/src/es2015/arrow_functions.rs index 1311dcf0e774d..18f61c87a8a04 100644 --- a/crates/oxc_transformer/src/es2015/arrow_functions.rs +++ b/crates/oxc_transformer/src/es2015/arrow_functions.rs @@ -74,7 +74,7 @@ use oxc_allocator::Vec; use oxc_ast::{ast::*, NONE}; use oxc_span::SPAN; use oxc_syntax::{scope::ScopeFlags, symbol::SymbolFlags}; -use oxc_traverse::{Traverse, TraverseCtx}; +use oxc_traverse::{Ancestor, Traverse, TraverseCtx}; use serde::Deserialize; use crate::{context::Ctx, helpers::bindings::BoundIdentifier}; @@ -174,17 +174,9 @@ impl<'a> Traverse<'a> for ArrowFunctions<'a> { self.inside_arrow_function_stack.pop().unwrap(); } - fn enter_class(&mut self, _class: &mut Class<'a>, _ctx: &mut TraverseCtx<'a>) { - self.inside_arrow_function_stack.push(false); - } - - fn exit_class(&mut self, _class: &mut Class<'a>, _ctx: &mut TraverseCtx<'a>) { - self.inside_arrow_function_stack.pop().unwrap(); - } - fn enter_static_block(&mut self, _block: &mut StaticBlock<'a>, _ctx: &mut TraverseCtx<'a>) { self.this_var_stack.push(None); - // No need to push to `inside_arrow_function_stack` because `enter_class` already pushed `false` + self.inside_arrow_function_stack.push(false); } fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, _ctx: &mut TraverseCtx<'a>) { @@ -192,6 +184,8 @@ impl<'a> Traverse<'a> for ArrowFunctions<'a> { if let Some(this_var) = this_var { self.insert_this_var_statement_at_the_top_of_statements(&mut block.body, &this_var); } + + self.inside_arrow_function_stack.pop().unwrap(); } fn enter_jsx_element_name( @@ -225,6 +219,45 @@ impl<'a> Traverse<'a> for ArrowFunctions<'a> { } fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + // `this` inside a class resolves to `this` *outside* the class in: + // * `extends` clause + // * Computed method key + // * Computed property key + // + // ```js + // // All these `this` refer to global `this` + // class C extends this { + // [this] = 123; + // static [this] = 123; + // [this]() {} + // static [this]() {} + // } + // ``` + // + // `this` resolves to the class / class instance (i.e. `this` defined *within* the class) in: + // * Class method bodies + // * Class property bodies + // * Class static blocks + // + // ```js + // // All these `this` refer to `this` defined within the class + // class C { + // a = this; + // static b = this; + // #c = this; + // d() { this } + // static e() { this } + // #f() { this } + // static { this } + // } + // ``` + // + // Class method bodies are caught by `enter_function`, static blocks caught by `enter_static_block`. + // Handle property bodies here. + if matches!(ctx.parent(), Ancestor::PropertyDefinitionValue(_)) { + self.inside_arrow_function_stack.push(false); + } + if let Expression::ThisExpression(this_expr) = expr { if !self.is_inside_arrow_function() { return; @@ -245,6 +278,11 @@ impl<'a> Traverse<'a> for ArrowFunctions<'a> { *expr = self.transform_arrow_function_expression(arrow_function_expr.unbox(), ctx); } + + // See comment in `enter_expression` + if matches!(ctx.parent(), Ancestor::PropertyDefinitionValue(_)) { + self.inside_arrow_function_stack.pop().unwrap(); + } } } diff --git a/crates/oxc_transformer/src/es2015/mod.rs b/crates/oxc_transformer/src/es2015/mod.rs index 2d49e79924e8e..d95ee15c95fd4 100644 --- a/crates/oxc_transformer/src/es2015/mod.rs +++ b/crates/oxc_transformer/src/es2015/mod.rs @@ -83,18 +83,6 @@ impl<'a> Traverse<'a> for ES2015<'a> { } } - fn enter_class(&mut self, class: &mut Class<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.arrow_function.is_some() { - self.arrow_functions.enter_class(class, ctx); - } - } - - fn exit_class(&mut self, class: &mut Class<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.arrow_function.is_some() { - self.arrow_functions.exit_class(class, ctx); - } - } - fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { if self.options.arrow_function.is_some() { self.arrow_functions.enter_static_block(block, ctx); diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 13c158d448beb..95cdbe85e9c95 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -156,11 +156,6 @@ impl<'a> Traverse<'a> for Transformer<'a> { fn enter_class(&mut self, class: &mut Class<'a>, ctx: &mut TraverseCtx<'a>) { self.x0_typescript.enter_class(class, ctx); - self.x3_es2015.enter_class(class, ctx); - } - - fn exit_class(&mut self, class: &mut Class<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_es2015.exit_class(class, ctx); } fn enter_class_body(&mut self, body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) { diff --git a/tasks/transform_conformance/snapshots/oxc.snap.md b/tasks/transform_conformance/snapshots/oxc.snap.md index 79fe2ac71016f..b1b312c60980d 100644 --- a/tasks/transform_conformance/snapshots/oxc.snap.md +++ b/tasks/transform_conformance/snapshots/oxc.snap.md @@ -1,6 +1,6 @@ commit: 3bcfee23 -Passed: 49/58 +Passed: 55/64 # All Passed: * babel-plugin-transform-nullish-coalescing-operator diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-property-key/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-property-key/input.js new file mode 100644 index 0000000000000..d1be2ab75ca1b --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-property-key/input.js @@ -0,0 +1,11 @@ +let f; + +class C { + [f = () => this] = 1; +} + +function outer() { + class C { + [f = () => this] = 1; + } +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-property-key/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-property-key/output.js new file mode 100644 index 0000000000000..5ebf48cbda07c --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-property-key/output.js @@ -0,0 +1,14 @@ +var _this = this; + +let f; + +class C { + [f = function() { return _this; }] = 1; +} + +function outer() { + var _this2 = this; + class C { + [f = function() { return _this2; }] = 1; + } +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-extends/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-extends/input.js new file mode 100644 index 0000000000000..9f0b1e79bb2dd --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-extends/input.js @@ -0,0 +1,9 @@ +function outer() { + return () => { + class C extends this {} + }; +} + +function outer2() { + class C extends this {} +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-extends/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-extends/output.js new file mode 100644 index 0000000000000..811111b4a0e0d --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-extends/output.js @@ -0,0 +1,10 @@ +function outer() { + var _this = this; + return function() { + class C extends _this {} + }; +} + +function outer2() { + class C extends this {} +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-method-key/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-method-key/input.js new file mode 100644 index 0000000000000..f80a4082dbfad --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-method-key/input.js @@ -0,0 +1,13 @@ +function outer() { + return () => { + class C { + [this]() {} + } + }; +} + +function outer2() { + class C { + [this]() {} + } +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-method-key/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-method-key/output.js new file mode 100644 index 0000000000000..d1c0e1f090003 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-method-key/output.js @@ -0,0 +1,14 @@ +function outer() { + var _this = this; + return function() { + class C { + [_this]() {} + } + }; +} + +function outer2() { + class C { + [this]() {} + } +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property-key/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property-key/input.js new file mode 100644 index 0000000000000..ea6f556228224 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property-key/input.js @@ -0,0 +1,13 @@ +function outer() { + return () => { + class C { + [this] = 1; + } + }; +} + +function outer2() { + class C { + [this] = 1; + } +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property-key/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property-key/output.js new file mode 100644 index 0000000000000..f8bd5c39c220b --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property-key/output.js @@ -0,0 +1,14 @@ +function outer() { + var _this = this; + return function() { + class C { + [_this] = 1; + } + }; +} + +function outer2() { + class C { + [this] = 1; + } +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property/input.js new file mode 100644 index 0000000000000..fad3dc976d122 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property/input.js @@ -0,0 +1,9 @@ +class C { + x = this; +} + +f = () => { + class C { + x = this; + } +}; diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property/output.js new file mode 100644 index 0000000000000..328e13db05d20 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-property/output.js @@ -0,0 +1,9 @@ +class C { + x = this; +} + +f = function() { + class C { + x = this; + } +}; diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-static-block/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-static-block/input.js new file mode 100644 index 0000000000000..a36e16f91393e --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-static-block/input.js @@ -0,0 +1,17 @@ +function outer() { + return () => { + class C { + static { + t = this; + } + } + }; +} + +function outer2() { + class C { + static { + t = this; + } + } +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-static-block/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-static-block/output.js new file mode 100644 index 0000000000000..0ed2b8e31b699 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/this-in-class-static-block/output.js @@ -0,0 +1,17 @@ +function outer() { + return function() { + class C { + static { + t = this; + } + } + }; +} + +function outer2() { + class C { + static { + t = this; + } + } +}