diff --git a/crates/oxc_transformer/src/common/arrow_function_converter.rs b/crates/oxc_transformer/src/common/arrow_function_converter.rs new file mode 100644 index 0000000000000..dd1cc7dc5a71c --- /dev/null +++ b/crates/oxc_transformer/src/common/arrow_function_converter.rs @@ -0,0 +1,439 @@ +//! Arrow Functions Converter +//! +//! This converter transforms arrow functions (`() => {}`) to function expressions (`function () {}`). +//! +//! ## Example +//! +//! Input: +//! ```js +//! var a = () => {}; +//! var a = b => b; +//! +//! const double = [1, 2, 3].map(num => num * 2); +//! console.log(double); // [2,4,6] +//! +//! var bob = { +//! name: "Bob", +//! friends: ["Sally", "Tom"], +//! printFriends() { +//! this.friends.forEach(f => console.log(this.name + " knows " + f)); +//! }, +//! }; +//! console.log(bob.printFriends()); +//! ``` +//! +//! Output: +//! ```js +//! var a = function() {}; +//! var a = function(b) { return b; }; +//! +//! const double = [1, 2, 3].map(function(num) { +//! return num * 2; +//! }); +//! console.log(double); // [2,4,6] +//! +//! var bob = { +//! name: "Bob", +//! friends: ["Sally", "Tom"], +//! printFriends() { +//! var _this = this; +//! this.friends.forEach(function(f) { +//! return console.log(_this.name + " knows " + f); +//! }); +//! }, +//! }; +//! console.log(bob.printFriends()); +//! ``` +//! +//! #### Example +//! +//! Using spec mode with the above example produces: +//! +//! ```js +//! var _this = this; +//! +//! var a = function a() { +//! babelHelpers.newArrowCheck(this, _this); +//! }.bind(this); +//! var a = function a(b) { +//! babelHelpers.newArrowCheck(this, _this); +//! return b; +//! }.bind(this); +//! +//! const double = [1, 2, 3].map( +//! function(num) { +//! babelHelpers.newArrowCheck(this, _this); +//! return num * 2; +//! }.bind(this) +//! ); +//! console.log(double); // [2,4,6] +//! +//! var bob = { +//! name: "Bob", +//! friends: ["Sally", "Tom"], +//! printFriends() { +//! var _this2 = this; +//! this.friends.forEach( +//! function(f) { +//! babelHelpers.newArrowCheck(this, _this2); +//! return console.log(this.name + " knows " + f); +//! }.bind(this) +//! ); +//! }, +//! }; +//! console.log(bob.printFriends()); +//! ``` +//! +//! The Implementation based on +//! + +use oxc_allocator::{Box as ArenaBox, Vec as ArenaVec}; +use oxc_ast::ast::*; +use oxc_data_structures::stack::SparseStack; +use oxc_span::SPAN; +use oxc_syntax::{ + scope::{ScopeFlags, ScopeId}, + symbol::SymbolFlags, +}; +use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx}; + +/// Mode for arrow function conversion +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ArrowFunctionConverterMode { + /// Disable arrow function conversion + Disabled, + + /// Convert all arrow functions to regular functions + Enabled, + + /// Only convert async arrow functions + #[expect(unused)] + AsyncOnly, +} + +pub struct ArrowFunctionConverterOptions { + pub mode: ArrowFunctionConverterMode, +} + +pub struct ArrowFunctionConverter<'a> { + mode: ArrowFunctionConverterMode, + this_var_stack: SparseStack>, +} + +impl<'a> ArrowFunctionConverter<'a> { + pub fn new(options: &ArrowFunctionConverterOptions) -> Self { + // `SparseStack` is created with 1 empty entry, for `Program` + Self { mode: options.mode, this_var_stack: SparseStack::new() } + } +} + +impl<'a> Traverse<'a> for ArrowFunctionConverter<'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>) { + if self.is_disabled() { + return; + } + + if let Some(this_var) = self.this_var_stack.take_last() { + self.insert_this_var_statement_at_the_top_of_statements( + &mut program.body, + &this_var, + ctx, + ); + } + debug_assert!(self.this_var_stack.len() == 1); + debug_assert!(self.this_var_stack.last().is_none()); + } + + fn enter_function(&mut self, _func: &mut Function<'a>, _ctx: &mut TraverseCtx<'a>) { + if self.is_disabled() { + return; + } + + self.this_var_stack.push(None); + } + + /// ```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() { + 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(None); + } + + 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() { + 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 = JSXElementName::IdentifierReference(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 = JSXMemberExpressionObject::IdentifierReference(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 = Expression::Identifier(ident); + } + } + } + + fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + if self.is_disabled() { + return; + } + + 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> ArrowFunctionConverter<'a> { + /// Check if arrow function conversion is disabled + fn is_disabled(&self) -> bool { + self.mode == ArrowFunctionConverterMode::Disabled + } + + fn get_this_identifier( + &mut self, + span: Span, + ctx: &mut TraverseCtx<'a>, + ) -> Option>> { + // Find arrow function we are currently in (if we are) + let arrow_scope_id = Self::get_arrow_function_scope(ctx)?; + + // 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_or_init(|| { + let target_scope_id = ctx + .scopes() + .ancestors(arrow_scope_id) + // Skip arrow function scope + .skip(1) + .find(|&scope_id| { + let scope_flags = ctx.scopes().get_flags(scope_id); + scope_flags.intersects( + ScopeFlags::Function | ScopeFlags::Top | ScopeFlags::ClassStaticBlock, + ) && !scope_flags.contains(ScopeFlags::Arrow) + }) + .unwrap(); + ctx.generate_uid("this", target_scope_id, SymbolFlags::FunctionScopedVariable) + }); + Some(ctx.ast.alloc(this_var.create_spanned_read_reference(span, ctx))) + } + + /// Find arrow function we are currently in, if it's between current node, and where `this` is bound. + /// Return its `ScopeId`. + fn get_arrow_function_scope(ctx: &mut TraverseCtx<'a>) -> Option { + // `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 None, + // Arrow function + Ancestor::ArrowFunctionExpressionParams(func) => { + return Some(func.scope_id().get().unwrap()) + } + Ancestor::ArrowFunctionExpressionBody(func) => { + return Some(func.scope_id().get().unwrap()) + } + _ => {} + } + } + 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; + + Expression::FunctionExpression(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, + )) + } + + /// Insert `var _this = this;` at the top of the statements. + #[expect(clippy::unused_self)] + fn insert_this_var_statement_at_the_top_of_statements( + &mut self, + statements: &mut ArenaVec<'a, Statement<'a>>, + this_var: &BoundIdentifier<'a>, + ctx: &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); + } +} diff --git a/crates/oxc_transformer/src/common/mod.rs b/crates/oxc_transformer/src/common/mod.rs index 56d10c6bed500..cb8d995540f9d 100644 --- a/crates/oxc_transformer/src/common/mod.rs +++ b/crates/oxc_transformer/src/common/mod.rs @@ -1,11 +1,15 @@ //! Utility transforms which are in common between other transforms. +use arrow_function_converter::{ + ArrowFunctionConverter, ArrowFunctionConverterMode, ArrowFunctionConverterOptions, +}; use oxc_allocator::Vec as ArenaVec; use oxc_ast::ast::*; use oxc_traverse::{Traverse, TraverseCtx}; -use crate::TransformCtx; +use crate::{TransformCtx, TransformOptions}; +pub mod arrow_function_converter; pub mod helper_loader; pub mod module_imports; pub mod statement_injector; @@ -22,15 +26,28 @@ pub struct Common<'a, 'ctx> { var_declarations: VarDeclarations<'a, 'ctx>, statement_injector: StatementInjector<'a, 'ctx>, top_level_statements: TopLevelStatements<'a, 'ctx>, + arrow_function_converter: ArrowFunctionConverter<'a>, } impl<'a, 'ctx> Common<'a, 'ctx> { - pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self { + pub fn new(options: &TransformOptions, ctx: &'ctx TransformCtx<'a>) -> Self { + let arrow_function_converter_options = { + let mode = if options.env.es2015.arrow_function.is_some() { + ArrowFunctionConverterMode::Enabled + } else { + ArrowFunctionConverterMode::Disabled + }; + ArrowFunctionConverterOptions { mode } + }; + Self { module_imports: ModuleImports::new(ctx), var_declarations: VarDeclarations::new(ctx), statement_injector: StatementInjector::new(ctx), top_level_statements: TopLevelStatements::new(ctx), + arrow_function_converter: ArrowFunctionConverter::new( + &arrow_function_converter_options, + ), } } } @@ -40,6 +57,7 @@ impl<'a, 'ctx> Traverse<'a> for Common<'a, 'ctx> { self.module_imports.exit_program(program, ctx); self.var_declarations.exit_program(program, ctx); self.top_level_statements.exit_program(program, ctx); + self.arrow_function_converter.exit_program(program, ctx); } fn enter_statements( @@ -58,4 +76,44 @@ 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_converter.enter_function(func, ctx); + } + + fn exit_function(&mut self, func: &mut Function<'a>, ctx: &mut TraverseCtx<'a>) { + self.arrow_function_converter.exit_function(func, ctx); + } + + fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { + self.arrow_function_converter.enter_static_block(block, ctx); + } + + fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { + self.arrow_function_converter.exit_static_block(block, ctx); + } + + fn enter_jsx_element_name( + &mut self, + element_name: &mut JSXElementName<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + self.arrow_function_converter.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_converter.enter_jsx_member_expression_object(object, ctx); + } + + fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + self.arrow_function_converter.enter_expression(expr, ctx); + } + + fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { + self.arrow_function_converter.exit_expression(expr, ctx); + } } diff --git a/crates/oxc_transformer/src/es2015/arrow_functions.rs b/crates/oxc_transformer/src/es2015/arrow_functions.rs index 072c624381f81..a0779dd52a5e8 100644 --- a/crates/oxc_transformer/src/es2015/arrow_functions.rs +++ b/crates/oxc_transformer/src/es2015/arrow_functions.rs @@ -117,7 +117,8 @@ //! //! ## Implementation //! -//! Implementation based on [@babel/plugin-transform-arrow-functions](https://babel.dev/docs/babel-plugin-transform-arrow-functions). +//! The implementation is placed in [`crate::common::arrow_function_converter::ArrowFunctionConverter`], +//! which can be used in other plugins. //! //! ## References: //! @@ -126,15 +127,9 @@ use serde::Deserialize; -use oxc_allocator::{Box as ArenaBox, Vec as ArenaVec}; -use oxc_ast::ast::*; -use oxc_data_structures::stack::SparseStack; -use oxc_span::SPAN; -use oxc_syntax::{ - scope::{ScopeFlags, ScopeId}, - symbol::SymbolFlags, -}; -use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx}; +use oxc_traverse::Traverse; + +use crate::context::TransformCtx; #[derive(Debug, Default, Clone, Copy, Deserialize)] pub struct ArrowFunctionsOptions { @@ -146,284 +141,15 @@ pub struct ArrowFunctionsOptions { pub spec: bool, } -pub struct ArrowFunctions<'a> { +pub struct ArrowFunctions<'a, 'ctx> { _options: ArrowFunctionsOptions, - this_var_stack: SparseStack>, + _ctx: &'ctx TransformCtx<'a>, } -impl<'a> ArrowFunctions<'a> { - pub fn new(options: ArrowFunctionsOptions) -> Self { - // `SparseStack` is created with 1 empty entry, for `Program` - Self { _options: options, this_var_stack: SparseStack::new() } - } -} - -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>) { - if let Some(this_var) = self.this_var_stack.take_last() { - self.insert_this_var_statement_at_the_top_of_statements( - &mut program.body, - &this_var, - ctx, - ); - } - debug_assert!(self.this_var_stack.len() == 1); - debug_assert!(self.this_var_stack.last().is_none()); - } - - fn enter_function(&mut self, _func: &mut Function<'a>, _ctx: &mut TraverseCtx<'a>) { - self.this_var_stack.push(None); - } - - /// ```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() { - 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(None); - } - - fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) { - if let Some(this_var) = self.this_var_stack.pop() { - 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 = JSXElementName::IdentifierReference(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 = JSXMemberExpressionObject::IdentifierReference(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 = Expression::Identifier(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> { + pub fn new(options: ArrowFunctionsOptions, ctx: &'ctx TransformCtx<'a>) -> Self { + Self { _options: options, _ctx: ctx } } } -impl<'a> ArrowFunctions<'a> { - fn get_this_identifier( - &mut self, - span: Span, - ctx: &mut TraverseCtx<'a>, - ) -> Option>> { - // Find arrow function we are currently in (if we are) - let arrow_scope_id = Self::get_arrow_function_scope(ctx)?; - - // 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_or_init(|| { - let target_scope_id = ctx - .scopes() - .ancestors(arrow_scope_id) - // Skip arrow function scope - .skip(1) - .find(|&scope_id| { - let scope_flags = ctx.scopes().get_flags(scope_id); - scope_flags.intersects( - ScopeFlags::Function | ScopeFlags::Top | ScopeFlags::ClassStaticBlock, - ) && !scope_flags.contains(ScopeFlags::Arrow) - }) - .unwrap(); - ctx.generate_uid("this", target_scope_id, SymbolFlags::FunctionScopedVariable) - }); - Some(ctx.ast.alloc(this_var.create_spanned_read_reference(span, ctx))) - } - - /// Find arrow function we are currently in, if it's between current node, and where `this` is bound. - /// Return its `ScopeId`. - fn get_arrow_function_scope(ctx: &mut TraverseCtx<'a>) -> Option { - // `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 None, - // Arrow function - Ancestor::ArrowFunctionExpressionParams(func) => { - return Some(func.scope_id().get().unwrap()) - } - Ancestor::ArrowFunctionExpressionBody(func) => { - return Some(func.scope_id().get().unwrap()) - } - _ => {} - } - } - 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; - - Expression::FunctionExpression(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, - )) - } - - /// Insert `var _this = this;` at the top of the statements. - #[expect(clippy::unused_self)] - fn insert_this_var_statement_at_the_top_of_statements( - &mut self, - statements: &mut ArenaVec<'a, Statement<'a>>, - this_var: &BoundIdentifier<'a>, - ctx: &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); - } -} +impl<'a, 'ctx> Traverse<'a> for ArrowFunctions<'a, 'ctx> {} diff --git a/crates/oxc_transformer/src/es2015/mod.rs b/crates/oxc_transformer/src/es2015/mod.rs index f25631d805757..50af87f4e4d42 100644 --- a/crates/oxc_transformer/src/es2015/mod.rs +++ b/crates/oxc_transformer/src/es2015/mod.rs @@ -1,5 +1,4 @@ -use oxc_ast::ast::*; -use oxc_traverse::{Traverse, TraverseCtx}; +use oxc_traverse::Traverse; mod arrow_functions; mod options; @@ -7,78 +6,24 @@ mod options; pub use arrow_functions::{ArrowFunctions, ArrowFunctionsOptions}; pub use options::ES2015Options; -pub struct ES2015<'a> { +use crate::context::TransformCtx; + +pub struct ES2015<'a, 'ctx> { + #[expect(unused)] options: ES2015Options, // Plugins - arrow_functions: ArrowFunctions<'a>, + #[expect(unused)] + arrow_functions: ArrowFunctions<'a, 'ctx>, } -impl<'a> ES2015<'a> { - pub fn new(options: ES2015Options) -> Self { +impl<'a, 'ctx> ES2015<'a, 'ctx> { + pub fn new(options: ES2015Options, ctx: &'ctx TransformCtx<'a>) -> Self { Self { - arrow_functions: ArrowFunctions::new(options.arrow_function.unwrap_or_default()), + arrow_functions: ArrowFunctions::new(options.arrow_function.unwrap_or_default(), ctx), options, } } } -impl<'a> Traverse<'a> for ES2015<'a> { - fn exit_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); - } - } -} +impl<'a, 'ctx> Traverse<'a> for ES2015<'a, 'ctx> {} diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index c4a63763229b2..fb288016a027e 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -94,6 +94,7 @@ impl<'a> Transformer<'a> { jsx::update_options_with_comments(&program.comments, &mut self.options, &self.ctx); let mut transformer = TransformerImpl { + common: Common::new(&self.options, &self.ctx), x0_typescript: program .source_type .is_typescript() @@ -106,9 +107,8 @@ impl<'a> Transformer<'a> { x2_es2018: ES2018::new(self.options.env.es2018, &self.ctx), x2_es2016: ES2016::new(self.options.env.es2016, &self.ctx), x2_es2017: ES2017::new(self.options.env.es2017, &self.ctx), - x3_es2015: ES2015::new(self.options.env.es2015), + x3_es2015: ES2015::new(self.options.env.es2015, &self.ctx), x4_regexp: RegExp::new(self.options.env.regexp, &self.ctx), - common: Common::new(&self.ctx), }; let (symbols, scopes) = traverse_mut(&mut transformer, allocator, program, symbols, scopes); @@ -127,7 +127,8 @@ struct TransformerImpl<'a, 'ctx> { x2_es2018: ES2018<'a, 'ctx>, x2_es2017: ES2017<'a, 'ctx>, x2_es2016: ES2016<'a, 'ctx>, - x3_es2015: ES2015<'a>, + #[expect(unused)] + x3_es2015: ES2015<'a, 'ctx>, x4_regexp: RegExp<'a, 'ctx>, common: Common<'a, 'ctx>, } @@ -145,7 +146,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 +198,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( @@ -224,15 +224,15 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, '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); + self.common.enter_expression(expr, ctx); } fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) { 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); + self.common.exit_expression(expr, ctx); } fn enter_simple_assignment_target( @@ -267,7 +267,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>) { @@ -277,7 +277,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> { self.x1_jsx.exit_function(func, ctx); self.x2_es2018.exit_function(func, ctx); self.x2_es2017.exit_function(func, ctx); - self.x3_es2015.exit_function(func, ctx); + self.common.exit_function(func, ctx); } fn enter_jsx_element(&mut self, node: &mut JSXElement<'a>, ctx: &mut TraverseCtx<'a>) { @@ -287,7 +287,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( @@ -295,7 +295,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>) {