diff --git a/crates/oxc_transformer/src/common/arrow_function_converter.rs b/crates/oxc_transformer/src/common/arrow_function_converter.rs index acde13c691ff34..404e6c31c04611 100644 --- a/crates/oxc_transformer/src/common/arrow_function_converter.rs +++ b/crates/oxc_transformer/src/common/arrow_function_converter.rs @@ -90,7 +90,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use oxc_allocator::{Box as ArenaBox, String as ArenaString, Vec as ArenaVec}; -use oxc_ast::{ast::*, AstBuilder, NONE}; +use oxc_ast::{ast::*, NONE}; use oxc_data_structures::stack::SparseStack; use oxc_semantic::{ReferenceFlags, SymbolId}; use oxc_span::{CompactStr, SPAN}; @@ -568,7 +568,7 @@ impl<'a> ArrowFunctionConverter<'a> { let super_methods = self.super_methods.as_mut()?; let mut argument = None; - let mut property = Atom::empty(); + let mut property = ""; let init = match expr.to_member_expression_mut() { MemberExpression::ComputedMemberExpression(computed_member) => { if !matches!(computed_member.object, Expression::Super(_)) { @@ -585,7 +585,7 @@ impl<'a> ArrowFunctionConverter<'a> { } // Used to generate the name of the arrow function. - property = static_member.property.name.clone(); + property = static_member.property.name.as_str(); ctx.ast.move_expression(expr) } MemberExpression::PrivateFieldExpression(_) => { @@ -594,8 +594,7 @@ impl<'a> ArrowFunctionConverter<'a> { } }; - let binding_name = - Self::generate_super_binding_name(assign_value.is_some(), &property, ctx.ast); + let binding_name = Self::generate_super_binding_name(assign_value.is_some(), property, ctx); let super_info = super_methods.entry(binding_name.clone()).or_insert_with(|| { let binding = ctx .generate_uid_in_current_scope(&binding_name, SymbolFlags::FunctionScopedVariable); @@ -801,10 +800,10 @@ impl<'a> ArrowFunctionConverter<'a> { /// Generate a binding name for the super method, like `_superprop_getXXX`. fn generate_super_binding_name( is_assignment: bool, - property: &Atom<'a>, - ast: AstBuilder<'a>, + property: &str, + ctx: &TraverseCtx<'a>, ) -> Atom<'a> { - let mut name = ArenaString::new_in(ast.allocator); + let mut name = ArenaString::new_in(ctx.ast.allocator); name.push_str("superprop_"); if is_assignment { @@ -820,7 +819,157 @@ impl<'a> ArrowFunctionConverter<'a> { if property.len() > 1 { name.push_str(&property[1..]); } - ast.atom(name.into_bump_str()) + ctx.ast.atom(name.into_bump_str()) + } + + /// Whether to transform the `arguments` identifier. + fn should_transform_arguments_identifier(&self, name: &str, ctx: &mut TraverseCtx<'a>) -> bool { + self.is_async_only() && name == "arguments" && Self::is_affected_arguments_identifier(ctx) + } + + /// Check if the `arguments` identifier is affected by the transformation. + fn is_affected_arguments_identifier(ctx: &mut TraverseCtx<'a>) -> bool { + let mut ancestors = ctx.ancestors().skip(1); + while let Some(ancestor) = ancestors.next() { + match ancestor { + Ancestor::ArrowFunctionExpressionParams(arrow) => { + if *arrow.r#async() { + return true; + } + } + Ancestor::ArrowFunctionExpressionBody(arrow) => { + if *arrow.r#async() { + return true; + } + } + Ancestor::FunctionBody(func) => { + return *func.r#async() + && Self::is_class_method_like_ancestor(ancestors.next().unwrap()); + } + _ => (), + } + } + + false + } + + /// Rename the `arguments` symbol to a new name. + fn rename_arguments_symbol(symbol_id: SymbolId, name: CompactStr, ctx: &mut TraverseCtx<'a>) { + let scope_id = ctx.symbols().get_scope_id(symbol_id); + ctx.symbols_mut().rename(symbol_id, name.clone()); + ctx.scopes_mut().rename_binding(scope_id, "arguments", name); + } + + /// Transform the identifier reference for `arguments` if it's affected after transformation. + /// + /// See [`Self::transform_member_expression_for_super`] for the reason. + fn transform_identifier_reference_for_arguments( + &mut self, + ident: &mut IdentifierReference<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + if !self.should_transform_arguments_identifier(&ident.name, ctx) { + return; + } + + let reference_id = ident.reference_id(); + let symbol_id = ctx.symbols().get_reference(reference_id).symbol_id(); + + let binding = self.arguments_var_stack.last_or_init(|| { + if let Some(symbol_id) = symbol_id { + let arguments_name = ctx.generate_uid_name("arguments"); + let arguments_name_atom = ctx.ast.atom(&arguments_name); + Self::rename_arguments_symbol(symbol_id, arguments_name, ctx); + // Record the symbol ID as a renamed `arguments` variable. + self.renamed_arguments_symbol_ids.insert(symbol_id); + BoundIdentifier::new(arguments_name_atom, symbol_id) + } else { + // We cannot determine the final scope ID of the `arguments` variable insertion, + // because the `arguments` variable will be inserted to a new scope which haven't been created yet, + // so we temporary use root scope id as the fake target scope ID. + let target_scope_id = ctx.scopes().root_scope_id(); + ctx.generate_uid("arguments", target_scope_id, SymbolFlags::FunctionScopedVariable) + } + }); + + // If no symbol ID, it means there is no variable named `arguments` in the scope. + // The following code is just to sync semantics. + if symbol_id.is_none() { + let reference = ctx.symbols_mut().get_reference_mut(reference_id); + reference.set_symbol_id(binding.symbol_id); + ctx.scopes_mut().delete_root_unresolved_reference(&ident.name, reference_id); + ctx.symbols_mut().resolved_references[binding.symbol_id].push(reference_id); + } + + ident.name = binding.name.clone(); + } + + /// Transform the binding identifier for `arguments` if it's affected after transformation. + /// + /// The main work is to rename the `arguments` binding identifier to a new name. + fn transform_binding_identifier_for_arguments( + &mut self, + ident: &mut BindingIdentifier<'a>, + ctx: &mut TraverseCtx<'a>, + ) { + if ctx.current_scope_flags().is_strict_mode() // `arguments` is not allowed to be defined in strict mode. + || !self.should_transform_arguments_identifier(&ident.name, ctx) + { + return; + } + + self.arguments_var_stack.last_or_init(|| { + let arguments_name = ctx.generate_uid_name("arguments"); + ident.name = ctx.ast.atom(&arguments_name); + let symbol_id = ident.symbol_id(); + Self::rename_arguments_symbol(symbol_id, arguments_name, ctx); + // Record the symbol ID as a renamed `arguments` variable. + self.renamed_arguments_symbol_ids.insert(symbol_id); + BoundIdentifier::new(ident.name.clone(), symbol_id) + }); + } + + /// Create a variable declarator looks like `_arguments = arguments;`. + fn create_arguments_var_declarator( + &self, + target_scope_id: ScopeId, + arguments_var: Option>, + ctx: &mut TraverseCtx<'a>, + ) -> Option> { + let arguments_var = arguments_var?; + + // Just a renamed `arguments` variable, we don't need to create a new variable declaration. + if self.renamed_arguments_symbol_ids.contains(&arguments_var.symbol_id) { + return None; + } + + Self::adjust_binding_scope(target_scope_id, &arguments_var, ctx); + let reference = + ctx.create_unbound_ident_reference(SPAN, Atom::from("arguments"), ReferenceFlags::Read); + let mut init = Expression::Identifier(ctx.ast.alloc(reference.clone())); + + // Top level may doesn't have `arguments`, so we need to check it. + // `typeof arguments === "undefined" ? void 0 : arguments;` + if ctx.scopes().root_scope_id() == target_scope_id { + let argument = Expression::Identifier(ctx.ast.alloc(reference)); + let typeof_arguments = ctx.ast.expression_unary(SPAN, UnaryOperator::Typeof, argument); + let undefined_literal = ctx.ast.expression_string_literal(SPAN, "undefined"); + let test = ctx.ast.expression_binary( + SPAN, + typeof_arguments, + BinaryOperator::StrictEquality, + undefined_literal, + ); + init = ctx.ast.expression_conditional(SPAN, test, ctx.ast.void_0(SPAN), init); + } + + Some(ctx.ast.variable_declarator( + SPAN, + VariableDeclarationKind::Var, + arguments_var.create_binding_pattern(ctx), + Some(init), + false, + )) } /// Whether to transform the `arguments` identifier.