From 49ee1dcff22154e01bed78d1fe21b70da68598b4 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Wed, 18 Sep 2024 09:46:57 +0000 Subject: [PATCH] fix(transformer): arrow function transform handle `this` in arrow function in class static block (#5848) Class static blocks also hold a `this` binding. --- .../src/es2015/arrow_functions.rs | 24 ++++++++++++++++--- crates/oxc_transformer/src/es2015/mod.rs | 12 ++++++++++ crates/oxc_transformer/src/lib.rs | 8 +++++++ tasks/transform_conformance/oxc.snap.md | 2 +- .../arrow-in-class-static-block/input.js | 6 +++++ .../arrow-in-class-static-block/output.js | 9 +++++++ 6 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-static-block/input.js create mode 100644 tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-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 484325812..12b0f56b9 100644 --- a/crates/oxc_transformer/src/es2015/arrow_functions.rs +++ b/crates/oxc_transformer/src/es2015/arrow_functions.rs @@ -99,6 +99,9 @@ impl<'a> ArrowFunctions<'a> { } impl<'a> Traverse<'a> for ArrowFunctions<'a> { + // Note: No visitors for `TSModuleBlock` because `this` is not legal in TS module blocks. + // + /// Insert `var _this = this;` for the global scope. fn exit_program(&mut self, program: &mut Program<'a>, _ctx: &mut TraverseCtx<'a>) { debug_assert!(self.inside_arrow_function_stack.len() == 1); @@ -168,6 +171,18 @@ impl<'a> Traverse<'a> for ArrowFunctions<'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` + } + + fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, _ctx: &mut TraverseCtx<'a>) { + let this_var = self.this_var_stack.pop().unwrap(); + if let Some(this_var) = this_var { + self.insert_this_var_statement_at_the_top_of_statements(&mut block.body, &this_var); + } + } + fn enter_jsx_element_name( &mut self, element_name: &mut JSXElementName<'a>, @@ -255,13 +270,16 @@ impl<'a> ArrowFunctions<'a> { let target_scope_id = ctx .scopes() .ancestors(ctx.current_scope_id()) + // We're inside arrow function, so parent scope can't be what we're looking for. + // It's either the arrow function, or a block nested within arrow function. .skip(1) .find(|&scope_id| { let scope_flags = ctx.scopes().get_flags(scope_id); - // Function but not arrow function - scope_flags & (ScopeFlags::Function | ScopeFlags::Arrow) == ScopeFlags::Function + scope_flags.intersects( + ScopeFlags::Function | ScopeFlags::Top | ScopeFlags::ClassStaticBlock, + ) && !scope_flags.contains(ScopeFlags::Arrow) }) - .unwrap_or(ctx.scopes().root_scope_id()); + .unwrap(); this_var.replace(BoundIdentifier::new_uid( "this", diff --git a/crates/oxc_transformer/src/es2015/mod.rs b/crates/oxc_transformer/src/es2015/mod.rs index 6500930fa..e1a007ded 100644 --- a/crates/oxc_transformer/src/es2015/mod.rs +++ b/crates/oxc_transformer/src/es2015/mod.rs @@ -95,6 +95,18 @@ impl<'a> Traverse<'a> for ES2015<'a> { } } + 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); + } + } + + fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { + if self.options.arrow_function.is_some() { + self.arrow_functions.exit_static_block(block, ctx); + } + } + fn enter_variable_declarator( &mut self, node: &mut VariableDeclarator<'a>, diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 39f2ca914..bcde55095 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -167,6 +167,14 @@ impl<'a> Traverse<'a> for Transformer<'a> { self.x0_typescript.enter_class_body(body, ctx); } + fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { + self.x3_es2015.enter_static_block(block, ctx); + } + + fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { + self.x3_es2015.exit_static_block(block, ctx); + } + fn enter_ts_module_declaration( &mut self, decl: &mut TSModuleDeclaration<'a>, diff --git a/tasks/transform_conformance/oxc.snap.md b/tasks/transform_conformance/oxc.snap.md index c1c31d7db..afe94fbac 100644 --- a/tasks/transform_conformance/oxc.snap.md +++ b/tasks/transform_conformance/oxc.snap.md @@ -1,6 +1,6 @@ commit: 3bcfee23 -Passed: 43/53 +Passed: 44/54 # 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-static-block/input.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-static-block/input.js new file mode 100644 index 000000000..b97bacf16 --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-static-block/input.js @@ -0,0 +1,6 @@ +let f; +class C { + static { + f = () => this; + } +} diff --git a/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-static-block/output.js b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-static-block/output.js new file mode 100644 index 000000000..d6799b25c --- /dev/null +++ b/tasks/transform_conformance/tests/babel-plugin-transform-arrow-functions/test/fixtures/arrow-in-class-static-block/output.js @@ -0,0 +1,9 @@ +let f; +class C { + static { + var _this = this; + f = function() { + return _this; + }; + } +}