diff --git a/crates/oxc_transformer/src/common/arrow_function_to_expression.rs b/crates/oxc_transformer/src/common/arrow_function_to_expression.rs new file mode 100644 index 00000000000000..1c4a02b3f43c86 --- /dev/null +++ b/crates/oxc_transformer/src/common/arrow_function_to_expression.rs @@ -0,0 +1,400 @@ +//! Arrow function to expression transformation. + +use std::cell::RefCell; + +use oxc_allocator::Vec as ArenaVec; +use oxc_ast::ast::*; +use oxc_data_structures::stack::NonEmptyStack; +use oxc_span::SPAN; +use oxc_syntax::{ + scope::{ScopeFlags, ScopeId}, + symbol::SymbolFlags, +}; +use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx}; + +use crate::context::TransformCtx; + +/// Used to store scope_id which used to create the bound identifier, +/// or the bound identifier itself which is created in the scope. +enum ThisVar<'a> { + ScopeId(ScopeId), + BoundIdentifier(BoundIdentifier<'a>), +} + +impl<'a> ThisVar<'a> { + pub fn to_bound_identifier(&self) -> Option<&BoundIdentifier<'a>> { + if let ThisVar::BoundIdentifier(ident) = self { + Some(ident) + } else { + None + } + } +} + +pub struct ArrowFunctionToExpression<'a, 'ctx> { + this_var_stack: NonEmptyStack>, + ctx: &'ctx TransformCtx<'a>, +} + +impl<'a, 'ctx> ArrowFunctionToExpression<'a, 'ctx> { + pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self { + // `NonEmptyStack` is created with PROGRAM_SCOPE_ID as the first scope. + const PROGRAM_SCOPE_ID: ScopeId = ScopeId::new(0); + Self { this_var_stack: NonEmptyStack::new(ThisVar::ScopeId(PROGRAM_SCOPE_ID)), ctx } + } +} + +impl<'a, 'ctx> Traverse<'a> for ArrowFunctionToExpression<'a, 'ctx> { + // 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>) { + if self.is_disabled() { + return; + } + + if let Some(this_var) = self.this_var_stack.last().to_bound_identifier() { + self.insert_this_var_statement_at_the_top_of_statements( + &mut program.body, + this_var, + ctx, + ); + } + debug_assert!(self.this_var_stack.len() == 1); + } + + fn enter_function(&mut self, func: &mut Function<'a>, _ctx: &mut TraverseCtx<'a>) { + if self.is_disabled() { + return; + } + + self.this_var_stack.push(ThisVar::ScopeId(func.scope_id.get().unwrap())); + } + + /// ```ts + /// function a(){ + /// return () => console.log(this); + /// } + /// // to + /// function a(){ + /// var _this = this; + /// return function() { return console.log(_this); }; + /// } + /// ``` + /// Insert the var _this = this; statement outside the arrow function + fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { + if self.is_disabled() { + return; + } + + if let Some(this_var) = self.this_var_stack.pop().to_bound_identifier() { + let Some(body) = &mut func.body else { unreachable!() }; + + self.insert_this_var_statement_at_the_top_of_statements( + &mut body.statements, + this_var, + ctx, + ); + } + } + + fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, _ctx: &mut TraverseCtx<'a>) { + if self.is_disabled() { + return; + } + + self.this_var_stack.push(ThisVar::ScopeId(block.scope_id.get().unwrap())); + } + + fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { + if self.is_disabled() { + return; + } + + if let Some(this_var) = self.this_var_stack.pop().to_bound_identifier() { + self.insert_this_var_statement_at_the_top_of_statements(&mut block.body, this_var, ctx); + } + } + + fn enter_jsx_element_name( + &mut self, + element_name: &mut JSXElementName<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + if self.is_disabled() { + return; + } + + if let JSXElementName::ThisExpression(this) = element_name { + if let Some(ident) = self.get_this_identifier(this.span, ctx) { + *element_name = ctx.ast.jsx_element_name_from_identifier_reference(ident); + } + }; + } + + fn enter_jsx_member_expression_object( + &mut self, + object: &mut JSXMemberExpressionObject<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + if self.is_disabled() { + return; + } + + if let JSXMemberExpressionObject::ThisExpression(this) = object { + if let Some(ident) = self.get_this_identifier(this.span, ctx) { + *object = ctx.ast.jsx_member_expression_object_from_identifier_reference(ident); + } + } + } + + fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + if self.is_disabled() { + return; + } + + if let Expression::ThisExpression(this) = expr { + if let Some(ident) = self.get_this_identifier(this.span, ctx) { + *expr = ctx.ast.expression_from_identifier_reference(ident); + } + } + } + + fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + if self.is_disabled() { + return; + } + + if let Expression::ArrowFunctionExpression(arrow_function_expr) = expr { + // Only transform async arrow functions if we are in async arrow function mode. + if self.is_async_arrow_function() && !arrow_function_expr.r#async { + return; + } + + let Expression::ArrowFunctionExpression(arrow_function_expr) = + ctx.ast.move_expression(expr) + else { + unreachable!() + }; + + *expr = self.transform_arrow_function_expression(arrow_function_expr.unbox(), ctx); + } + } +} + +impl<'a, 'ctx> ArrowFunctionToExpression<'a, 'ctx> { + fn is_disabled(&self) -> bool { + *self.ctx.arrow_function_to_expression.mode.borrow() + == ArrowFunctionsToExpressionMode::Disabled + } + + fn is_async_arrow_function(&self) -> bool { + *self.ctx.arrow_function_to_expression.mode.borrow() + == ArrowFunctionsToExpressionMode::AsyncArrowFunction + } + + fn get_this_identifier( + &mut self, + span: Span, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + // If the `this` is not used in an arrow function, we don't need to transform it. + if !self.is_in_arrow_function_scope(ctx) { + return None; + } + + // TODO(improve-on-babel): We create a new UID for every scope. This is pointless, as only one + // `this` can be in scope at a time. We could create a single `_this` UID and reuse it in each + // scope. But this does not match output for some of Babel's test cases. + // + let this_var = self.this_var_stack.last_mut(); + let this_var = match this_var { + // If it's a scope_id, create a new identifier in the scope + ThisVar::ScopeId(scope_id) => { + *this_var = ThisVar::BoundIdentifier(ctx.generate_uid( + "this", + *scope_id, + SymbolFlags::FunctionScopedVariable, + )); + let ThisVar::BoundIdentifier(ident) = this_var else { unreachable!() }; + ident + } + ThisVar::BoundIdentifier(ident) => ident, + }; + Some(this_var.create_spanned_read_reference(span, ctx)) + } + + /// Check if we are in an arrow function. + fn is_in_arrow_function_scope(&self, ctx: &mut TraverseCtx<'a>) -> bool { + // Early exit if we are in an arrow function + if ctx.current_scope_flags().contains(ScopeFlags::Arrow) { + return true; + } + + // `this` inside a class resolves to `this` *outside* the class in: + // * `extends` clause + // * Computed method key + // * Computed property key + // * Computed accessor property key (but `this` in this position is not legal TS) + // + // ```js + // // All these `this` refer to global `this` + // class C extends this { + // [this] = 123; + // static [this] = 123; + // [this]() {} + // static [this]() {} + // accessor [this] = 123; + // static accessor [this] = 123; + // } + // ``` + // + // `this` resolves to the class / class instance (i.e. `this` defined *within* the class) in: + // * Method body + // * Method param + // * Property value + // * Static block + // + // ```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 } + // g(x = this) {} + // accessor h = this; + // static accessor i = this; + // static { this } + // } + // ``` + // + // So in this loop, we only exit when we encounter one of the above. + for ancestor in ctx.ancestors() { + match ancestor { + // Top level + Ancestor::ProgramBody(_) + // Class property body + | Ancestor::PropertyDefinitionValue(_) + // Class accessor property body + | Ancestor::AccessorPropertyValue(_) + // Function (includes class method body) + | Ancestor::FunctionParams(_) + // Class static block + | Ancestor::StaticBlockBody(_) => return false, + // Arrow function + Ancestor::ArrowFunctionExpressionParams(_) | Ancestor::ArrowFunctionExpressionBody(_) => { + return true + } + Ancestor::FunctionBody(a) => { + if self.is_async_arrow_function() && *a.r#async() { + continue; + } + return false + } + Ancestor::MethodDefinitionValue(_) => { + return true; + } + _ => { + } + } + } + unreachable!(); + } + + #[expect(clippy::unused_self)] + fn transform_arrow_function_expression( + &mut self, + arrow_function_expr: ArrowFunctionExpression<'a>, + ctx: &mut TraverseCtx<'a>, + ) -> Expression<'a> { + let mut body = arrow_function_expr.body; + + if arrow_function_expr.expression { + assert!(body.statements.len() == 1); + let stmt = body.statements.pop().unwrap(); + let Statement::ExpressionStatement(stmt) = stmt else { unreachable!() }; + let stmt = stmt.unbox(); + let return_statement = ctx.ast.statement_return(stmt.span, Some(stmt.expression)); + body.statements.push(return_statement); + } + + let scope_id = arrow_function_expr.scope_id.get().unwrap(); + let flags = ctx.scopes_mut().get_flags_mut(scope_id); + *flags &= !ScopeFlags::Arrow; + + let new_function = ctx.ast.alloc_function_with_scope_id( + FunctionType::FunctionExpression, + arrow_function_expr.span, + None, + false, + arrow_function_expr.r#async, + false, + arrow_function_expr.type_parameters, + None::>, + arrow_function_expr.params, + arrow_function_expr.return_type, + Some(body), + scope_id, + ); + Expression::FunctionExpression(new_function) + } + + /// Insert `var _this = this;` at the top of the statements. + #[expect(clippy::unused_self)] + fn insert_this_var_statement_at_the_top_of_statements( + &self, + statements: &mut ArenaVec<'a, Statement<'a>>, + this_var: &BoundIdentifier<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + let variable_declarator = ctx.ast.variable_declarator( + SPAN, + VariableDeclarationKind::Var, + this_var.create_binding_pattern(ctx), + Some(ctx.ast.expression_this(SPAN)), + false, + ); + + let stmt = ctx.ast.alloc_variable_declaration( + SPAN, + VariableDeclarationKind::Var, + ctx.ast.vec1(variable_declarator), + false, + ); + + let stmt = Statement::VariableDeclaration(stmt); + + statements.insert(0, stmt); + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ArrowFunctionsToExpressionMode { + Disabled, + ArrowFunction, + AsyncArrowFunction, +} + +pub struct ArrowFunctionToExpressionStore { + mode: RefCell, +} + +impl ArrowFunctionToExpressionStore { + pub fn new() -> Self { + Self { mode: RefCell::new(ArrowFunctionsToExpressionMode::Disabled) } + } + + pub fn enable_arrow_function(&self) { + self.mode.replace(ArrowFunctionsToExpressionMode::ArrowFunction); + } + + pub fn enable_async_arrow_function(&self) { + if *self.mode.borrow() != ArrowFunctionsToExpressionMode::ArrowFunction { + self.mode.replace(ArrowFunctionsToExpressionMode::AsyncArrowFunction); + } + } +} diff --git a/crates/oxc_transformer/src/common/mod.rs b/crates/oxc_transformer/src/common/mod.rs index 56d10c6bed5001..334a196ac91a6c 100644 --- a/crates/oxc_transformer/src/common/mod.rs +++ b/crates/oxc_transformer/src/common/mod.rs @@ -6,12 +6,14 @@ use oxc_traverse::{Traverse, TraverseCtx}; use crate::TransformCtx; +pub mod arrow_function_to_expression; pub mod helper_loader; pub mod module_imports; pub mod statement_injector; pub mod top_level_statements; pub mod var_declarations; +use arrow_function_to_expression::ArrowFunctionToExpression; use module_imports::ModuleImports; use statement_injector::StatementInjector; use top_level_statements::TopLevelStatements; @@ -22,6 +24,7 @@ pub struct Common<'a, 'ctx> { var_declarations: VarDeclarations<'a, 'ctx>, statement_injector: StatementInjector<'a, 'ctx>, top_level_statements: TopLevelStatements<'a, 'ctx>, + arrow_function_to_expression: ArrowFunctionToExpression<'a, 'ctx>, } impl<'a, 'ctx> Common<'a, 'ctx> { @@ -31,6 +34,7 @@ impl<'a, 'ctx> Common<'a, 'ctx> { var_declarations: VarDeclarations::new(ctx), statement_injector: StatementInjector::new(ctx), top_level_statements: TopLevelStatements::new(ctx), + arrow_function_to_expression: ArrowFunctionToExpression::new(ctx), } } } @@ -38,6 +42,7 @@ impl<'a, 'ctx> Common<'a, 'ctx> { impl<'a, 'ctx> Traverse<'a> for Common<'a, 'ctx> { fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { self.module_imports.exit_program(program, ctx); + self.arrow_function_to_expression.exit_program(program, ctx); self.var_declarations.exit_program(program, ctx); self.top_level_statements.exit_program(program, ctx); } @@ -58,4 +63,52 @@ impl<'a, 'ctx> Traverse<'a> for Common<'a, 'ctx> { self.var_declarations.exit_statements(stmts, ctx); self.statement_injector.exit_statements(stmts, ctx); } + + fn enter_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { + self.arrow_function_to_expression.enter_function(func, ctx); + } + + fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { + self.arrow_function_to_expression.exit_function(func, ctx); + } + + fn exit_method_definition( + &mut self, + node: &mut MethodDefinition<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.arrow_function_to_expression.exit_method_definition(node, ctx); + } + + fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { + self.arrow_function_to_expression.enter_static_block(block, ctx); + } + + fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { + self.arrow_function_to_expression.exit_static_block(block, ctx); + } + + fn enter_jsx_element_name( + &mut self, + element_name: &mut JSXElementName<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.arrow_function_to_expression.enter_jsx_element_name(element_name, ctx); + } + + fn enter_jsx_member_expression_object( + &mut self, + object: &mut JSXMemberExpressionObject<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.arrow_function_to_expression.enter_jsx_member_expression_object(object, ctx); + } + + fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + self.arrow_function_to_expression.enter_expression(expr, ctx); + } + + fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + self.arrow_function_to_expression.exit_expression(expr, ctx); + } } diff --git a/crates/oxc_transformer/src/context.rs b/crates/oxc_transformer/src/context.rs index 5c3c2cb63ed72b..a579e628e0ca32 100644 --- a/crates/oxc_transformer/src/context.rs +++ b/crates/oxc_transformer/src/context.rs @@ -9,6 +9,7 @@ use oxc_span::SourceType; use crate::{ common::{ + arrow_function_to_expression::ArrowFunctionToExpressionStore, helper_loader::HelperLoaderStore, module_imports::ModuleImportsStore, statement_injector::StatementInjectorStore, top_level_statements::TopLevelStatementsStore, var_declarations::VarDeclarationsStore, @@ -40,6 +41,8 @@ pub struct TransformCtx<'a> { pub statement_injector: StatementInjectorStore<'a>, /// Manage inserting statements at top of program globally pub top_level_statements: TopLevelStatementsStore<'a>, + /// Manage converting arrow functions to expressions + pub arrow_function_to_expression: ArrowFunctionToExpressionStore, } impl<'a> TransformCtx<'a> { @@ -63,6 +66,7 @@ impl<'a> TransformCtx<'a> { var_declarations: VarDeclarationsStore::new(), statement_injector: StatementInjectorStore::new(), top_level_statements: TopLevelStatementsStore::new(), + arrow_function_to_expression: ArrowFunctionToExpressionStore::new(), } } diff --git a/crates/oxc_transformer/src/es2015/arrow_functions.rs b/crates/oxc_transformer/src/es2015/arrow_functions.rs index d590b08a484e4f..28d00fa1c83844 100644 --- a/crates/oxc_transformer/src/es2015/arrow_functions.rs +++ b/crates/oxc_transformer/src/es2015/arrow_functions.rs @@ -148,304 +148,18 @@ pub struct ArrowFunctionsOptions { pub spec: bool, } -/// Used to store scope_id which used to create the bound identifier, -/// or the bound identifier itself which is created in the scope. -enum ThisVar<'a> { - ScopeId(ScopeId), - BoundIdentifier(BoundIdentifier<'a>), -} - -impl<'a> ThisVar<'a> { - pub fn to_bound_identifier(&self) -> Option<&BoundIdentifier<'a>> { - if let ThisVar::BoundIdentifier(ident) = self { - Some(ident) - } else { - None - } - } -} - pub struct ArrowFunctions<'a, 'ctx> { - _options: ArrowFunctionsOptions, - this_var_stack: NonEmptyStack>, ctx: &'ctx TransformCtx<'a>, } impl<'a, 'ctx> ArrowFunctions<'a, 'ctx> { pub fn new(options: ArrowFunctionsOptions, ctx: &'ctx TransformCtx<'a>) -> Self { - // `NonEmptyStack` is created with PROGRAM_SCOPE_ID as the first scope. - const PROGRAM_SCOPE_ID: ScopeId = ScopeId::new(0); - Self { - _options: options, - this_var_stack: NonEmptyStack::new(ThisVar::ScopeId(PROGRAM_SCOPE_ID)), - ctx, - } + Self { ctx } } } impl<'a, 'ctx> Traverse<'a> for ArrowFunctions<'a, 'ctx> { - // 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>) { - if let Some(this_var) = self.this_var_stack.last().to_bound_identifier() { - self.insert_this_var_statement_at_the_top_of_statements( - &mut program.body, - this_var, - ctx, - ); - } - debug_assert!(self.this_var_stack.len() == 1); - } - - fn enter_function(&mut self, func: &mut Function<'a>, _ctx: &mut TraverseCtx<'a>) { - self.this_var_stack.push(ThisVar::ScopeId(func.scope_id.get().unwrap())); - } - - /// ```ts - /// function a(){ - /// return () => console.log(this); - /// } - /// // to - /// function a(){ - /// var _this = this; - /// return function() { return console.log(_this); }; - /// } - /// ``` - /// Insert the var _this = this; statement outside the arrow function - fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { - if let Some(this_var) = self.this_var_stack.pop().to_bound_identifier() { - let Some(body) = &mut func.body else { unreachable!() }; - - self.insert_this_var_statement_at_the_top_of_statements( - &mut body.statements, - this_var, - ctx, - ); - } - } - - fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, _ctx: &mut TraverseCtx<'a>) { - self.this_var_stack.push(ThisVar::ScopeId(block.scope_id.get().unwrap())); - } - - fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { - if let Some(this_var) = self.this_var_stack.pop().to_bound_identifier() { - self.insert_this_var_statement_at_the_top_of_statements(&mut block.body, this_var, ctx); - } - } - - fn enter_jsx_element_name( - &mut self, - element_name: &mut JSXElementName<'a>, - ctx: &mut TraverseCtx<'a>, - ) { - if let JSXElementName::ThisExpression(this) = element_name { - if let Some(ident) = self.get_this_identifier(this.span, ctx) { - *element_name = ctx.ast.jsx_element_name_from_identifier_reference(ident); - } - }; - } - - fn enter_jsx_member_expression_object( - &mut self, - object: &mut JSXMemberExpressionObject<'a>, - ctx: &mut TraverseCtx<'a>, - ) { - if let JSXMemberExpressionObject::ThisExpression(this) = object { - if let Some(ident) = self.get_this_identifier(this.span, ctx) { - *object = ctx.ast.jsx_member_expression_object_from_identifier_reference(ident); - } - } - } - - fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - if let Expression::ThisExpression(this) = expr { - if let Some(ident) = self.get_this_identifier(this.span, ctx) { - *expr = ctx.ast.expression_from_identifier_reference(ident); - } - } - } - - fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - if let Expression::ArrowFunctionExpression(_) = expr { - let Expression::ArrowFunctionExpression(arrow_function_expr) = - ctx.ast.move_expression(expr) - else { - unreachable!() - }; - - *expr = self.transform_arrow_function_expression(arrow_function_expr.unbox(), ctx); - } - } -} - -impl<'a, 'ctx> ArrowFunctions<'a, 'ctx> { - fn get_this_identifier( - &mut self, - span: Span, - ctx: &mut TraverseCtx<'a>, - ) -> Option> { - // If the `this` is not used in an arrow function, we don't need to transform it. - if !Self::is_in_arrow_function_scope(ctx) { - return None; - } - - // TODO(improve-on-babel): We create a new UID for every scope. This is pointless, as only one - // `this` can be in scope at a time. We could create a single `_this` UID and reuse it in each - // scope. But this does not match output for some of Babel's test cases. - // - let this_var = self.this_var_stack.last_mut(); - let this_var = match this_var { - // If it's a scope_id, create a new identifier in the scope - ThisVar::ScopeId(scope_id) => { - *this_var = ThisVar::BoundIdentifier(ctx.generate_uid( - "this", - *scope_id, - SymbolFlags::FunctionScopedVariable, - )); - let ThisVar::BoundIdentifier(ident) = this_var else { unreachable!() }; - ident - } - ThisVar::BoundIdentifier(ident) => ident, - }; - Some(this_var.create_spanned_read_reference(span, ctx)) - } - - /// Check if we are in an arrow function. - fn is_in_arrow_function_scope(ctx: &mut TraverseCtx<'a>) -> bool { - // Early exit if we are in an arrow function - if ctx.current_scope_flags().contains(ScopeFlags::Arrow) { - return true; - } - // `this` inside a class resolves to `this` *outside* the class in: - // * `extends` clause - // * Computed method key - // * Computed property key - // * Computed accessor property key (but `this` in this position is not legal TS) - // - // ```js - // // All these `this` refer to global `this` - // class C extends this { - // [this] = 123; - // static [this] = 123; - // [this]() {} - // static [this]() {} - // accessor [this] = 123; - // static accessor [this] = 123; - // } - // ``` - // - // `this` resolves to the class / class instance (i.e. `this` defined *within* the class) in: - // * Method body - // * Method param - // * Property value - // * Static block - // - // ```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 } - // g(x = this) {} - // accessor h = this; - // static accessor i = this; - // static { this } - // } - // ``` - // - // So in this loop, we only exit when we encounter one of the above. - for ancestor in ctx.ancestors() { - match ancestor { - // Top level - Ancestor::ProgramBody(_) - // Function (includes class method body) - | Ancestor::FunctionParams(_) - | Ancestor::FunctionBody(_) - // Class property body - | Ancestor::PropertyDefinitionValue(_) - // Class accessor property body - | Ancestor::AccessorPropertyValue(_) - // Class static block - | Ancestor::StaticBlockBody(_) => return false, - // Arrow function - Ancestor::ArrowFunctionExpressionParams(_) | Ancestor::ArrowFunctionExpressionBody(_) => { - return true - } - _ => {} - } - } - unreachable!(); - } - - #[expect(clippy::unused_self)] - fn transform_arrow_function_expression( - &mut self, - arrow_function_expr: ArrowFunctionExpression<'a>, - ctx: &mut TraverseCtx<'a>, - ) -> Expression<'a> { - let mut body = arrow_function_expr.body; - - if arrow_function_expr.expression { - assert!(body.statements.len() == 1); - let stmt = body.statements.pop().unwrap(); - let Statement::ExpressionStatement(stmt) = stmt else { unreachable!() }; - let stmt = stmt.unbox(); - let return_statement = ctx.ast.statement_return(stmt.span, Some(stmt.expression)); - body.statements.push(return_statement); - } - - let scope_id = arrow_function_expr.scope_id.get().unwrap(); - let flags = ctx.scopes_mut().get_flags_mut(scope_id); - *flags &= !ScopeFlags::Arrow; - - let new_function = ctx.ast.alloc_function_with_scope_id( - FunctionType::FunctionExpression, - arrow_function_expr.span, - None, - false, - arrow_function_expr.r#async, - false, - arrow_function_expr.type_parameters, - None::>, - arrow_function_expr.params, - arrow_function_expr.return_type, - Some(body), - scope_id, - ); - Expression::FunctionExpression(new_function) - } - - /// Insert `var _this = this;` at the top of the statements. - #[expect(clippy::unused_self)] - fn insert_this_var_statement_at_the_top_of_statements( - &self, - statements: &mut ArenaVec<'a, Statement<'a>>, - this_var: &BoundIdentifier<'a>, - ctx: &mut TraverseCtx<'a>, - ) { - let variable_declarator = ctx.ast.variable_declarator( - SPAN, - VariableDeclarationKind::Var, - this_var.create_binding_pattern(ctx), - Some(ctx.ast.expression_this(SPAN)), - false, - ); - - let stmt = ctx.ast.alloc_variable_declaration( - SPAN, - VariableDeclarationKind::Var, - ctx.ast.vec1(variable_declarator), - false, - ); - - let stmt = Statement::VariableDeclaration(stmt); - - statements.insert(0, stmt); + fn enter_program(&mut self, _program: &mut Program<'a>, _ctx: &mut TraverseCtx<'a>) { + self.ctx.arrow_function_to_expression.enable_arrow_function(); } } diff --git a/crates/oxc_transformer/src/es2015/mod.rs b/crates/oxc_transformer/src/es2015/mod.rs index b02dd5bae6c2da..ea1368bd093a41 100644 --- a/crates/oxc_transformer/src/es2015/mod.rs +++ b/crates/oxc_transformer/src/es2015/mod.rs @@ -29,61 +29,9 @@ impl<'a, 'ctx> ES2015<'a, 'ctx> { } impl<'a, 'ctx> Traverse<'a> for ES2015<'a, 'ctx> { - fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + fn enter_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { if self.options.arrow_function.is_some() { - self.arrow_functions.exit_program(program, ctx); - } - } - - fn enter_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.arrow_function.is_some() { - self.arrow_functions.enter_function(func, ctx); - } - } - - fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.arrow_function.is_some() { - self.arrow_functions.exit_function(func, ctx); - } - } - - fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.arrow_function.is_some() { - self.arrow_functions.enter_expression(expr, ctx); - } - } - - fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.arrow_function.is_some() { - self.arrow_functions.exit_expression(expr, 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); - } - } - - 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_jsx_element_name(&mut self, node: &mut JSXElementName<'a>, ctx: &mut TraverseCtx<'a>) { - if self.options.arrow_function.is_some() { - self.arrow_functions.enter_jsx_element_name(node, ctx); - } - } - - fn enter_jsx_member_expression_object( - &mut self, - node: &mut JSXMemberExpressionObject<'a>, - ctx: &mut TraverseCtx<'a>, - ) { - if self.options.arrow_function.is_some() { - self.arrow_functions.enter_jsx_member_expression_object(node, ctx); + self.arrow_functions.enter_program(program, ctx); } } } diff --git a/crates/oxc_transformer/src/es2017/async_to_generator.rs b/crates/oxc_transformer/src/es2017/async_to_generator.rs index 514dbab1b0ea21..865c18091fafbb 100644 --- a/crates/oxc_transformer/src/es2017/async_to_generator.rs +++ b/crates/oxc_transformer/src/es2017/async_to_generator.rs @@ -73,6 +73,10 @@ impl<'a, 'ctx> AsyncToGenerator<'a, 'ctx> { } impl<'a, 'ctx> Traverse<'a> for AsyncToGenerator<'a, 'ctx> { + fn enter_program(&mut self, _program: &mut Program<'a>, _ctx: &mut TraverseCtx<'a>) { + self.ctx.arrow_function_to_expression.enable_async_arrow_function(); + } + fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { let new_expr = match expr { Expression::AwaitExpression(await_expr) => { @@ -130,14 +134,9 @@ impl<'a, 'ctx> Traverse<'a> for AsyncToGenerator<'a, 'ctx> { } } - fn exit_method_definition( - &mut self, - node: &mut MethodDefinition<'a>, - ctx: &mut TraverseCtx<'a>, - ) { - let function = &mut node.value; - if function.r#async && !function.generator && !function.is_typescript_syntax() { - self.executor.transform_function_for_method_definition(function, ctx); + fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { + if func.r#async && ctx.parent().is_method_definition() { + self.executor.transform_function_for_method_definition(func, ctx); } } } diff --git a/crates/oxc_transformer/src/es2017/mod.rs b/crates/oxc_transformer/src/es2017/mod.rs index 10fdc26adcf032..1779f85247d768 100644 --- a/crates/oxc_transformer/src/es2017/mod.rs +++ b/crates/oxc_transformer/src/es2017/mod.rs @@ -1,7 +1,7 @@ mod async_to_generator; mod options; -use oxc_ast::ast::{Expression, Statement}; +use oxc_ast::ast::{Expression, Function, Program, Statement}; use oxc_traverse::{Traverse, TraverseCtx}; use crate::{es2017::async_to_generator::AsyncToGenerator, TransformCtx}; @@ -24,19 +24,21 @@ impl<'a, 'ctx> ES2017<'a, 'ctx> { } impl<'a, 'ctx> Traverse<'a> for ES2017<'a, 'ctx> { + fn enter_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + if self.options.async_to_generator { + self.async_to_generator.enter_program(program, ctx); + } + } + fn exit_expression(&mut self, node: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { if self.options.async_to_generator { self.async_to_generator.exit_expression(node, ctx); } } - fn exit_method_definition( - &mut self, - node: &mut oxc_ast::ast::MethodDefinition<'a>, - ctx: &mut TraverseCtx<'a>, - ) { + fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { if self.options.async_to_generator { - self.async_to_generator.exit_method_definition(node, ctx); + self.async_to_generator.exit_function(func, ctx); } } diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 465c159ea11d06..a5a93cc78f0c24 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -138,6 +138,8 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { typescript.enter_program(program, ctx); } self.x1_jsx.enter_program(program, ctx); + self.x2_es2017.enter_program(program, ctx); + self.x3_es2015.enter_program(program, ctx); } fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { @@ -145,7 +147,6 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { if let Some(typescript) = self.x0_typescript.as_mut() { typescript.exit_program(program, ctx); } - self.x3_es2015.exit_program(program, ctx); self.common.exit_program(program, ctx); } @@ -198,11 +199,11 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { } fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_es2015.enter_static_block(block, ctx); + self.common.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); + self.common.exit_static_block(block, ctx); } fn enter_ts_module_declaration( @@ -220,19 +221,19 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { if let Some(typescript) = self.x0_typescript.as_mut() { typescript.enter_expression(expr, ctx); } + self.common.enter_expression(expr, ctx); self.x2_es2021.enter_expression(expr, ctx); self.x2_es2020.enter_expression(expr, ctx); self.x2_es2018.enter_expression(expr, ctx); self.x2_es2016.enter_expression(expr, ctx); - self.x3_es2015.enter_expression(expr, ctx); self.x4_regexp.enter_expression(expr, ctx); } fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + self.common.exit_expression(expr, ctx); self.x1_jsx.exit_expression(expr, ctx); self.x2_es2018.exit_expression(expr, ctx); self.x2_es2017.exit_expression(expr, ctx); - self.x3_es2015.exit_expression(expr, ctx); } fn enter_simple_assignment_target( @@ -267,7 +268,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { fn enter_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { self.x2_es2018.enter_function(func, ctx); - self.x3_es2015.enter_function(func, ctx); + self.common.enter_function(func, ctx); } fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { @@ -276,7 +277,8 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { } self.x1_jsx.exit_function(func, ctx); self.x2_es2018.exit_function(func, ctx); - self.x3_es2015.exit_function(func, ctx); + self.x2_es2017.exit_function(func, ctx); + self.common.exit_function(func, ctx); } fn enter_jsx_element(&mut self, node: &mut JSXElement<'a>, ctx: &mut TraverseCtx<'a>) { @@ -286,7 +288,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { } fn enter_jsx_element_name(&mut self, node: &mut JSXElementName<'a>, ctx: &mut TraverseCtx<'a>) { - self.x3_es2015.enter_jsx_element_name(node, ctx); + self.common.enter_jsx_element_name(node, ctx); } fn enter_jsx_member_expression_object( @@ -294,7 +296,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { node: &mut JSXMemberExpressionObject<'a>, ctx: &mut TraverseCtx<'a>, ) { - self.x3_es2015.enter_jsx_member_expression_object(node, ctx); + self.common.enter_jsx_member_expression_object(node, ctx); } fn enter_jsx_fragment(&mut self, node: &mut JSXFragment<'a>, ctx: &mut TraverseCtx<'a>) { @@ -333,7 +335,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { typescript.exit_method_definition(def, ctx); } self.x2_es2018.exit_method_definition(def, ctx); - self.x2_es2017.exit_method_definition(def, ctx); + self.common.exit_method_definition(def, ctx); } fn enter_new_expression(&mut self, expr: &mut NewExpression<'a>, ctx: &mut TraverseCtx<'a>) {