From e23f7e6bc13f824d8d8555b3019e373ebe565d64 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Fri, 1 Nov 2024 04:08:59 +0000 Subject: [PATCH] refactor(transformer/common): `VarDeclarations` insert either `var` or `let` statements (#7043) Add ability for `VarDeclarations` to insert `let` declarations as well as `var` declarations. This is required for class properties transform. Implementation note: `var` and `let` declarators are stored in 2 separate `ArenaVec`s. This allows using those `ArenaVec>`s directly in the AST, rather storing all `Declarator`s in a single `Vec` and having to loop through it when inserting the `var` / `let` statements to split it into 2 `ArenaVec`s of `var` / `let` declarators. I'm not completely sure this is better than using a single `Vec` for both, but I think *probably* it is. --- .../src/common/var_declarations.rs | 121 ++++++++++++++---- .../src/es2016/exponentiation_operator.rs | 2 +- .../src/es2020/nullish_coalescing_operator.rs | 2 +- .../es2021/logical_assignment_operators.rs | 2 +- crates/oxc_transformer/src/jsx/jsx_impl.rs | 2 +- crates/oxc_transformer/src/jsx/refresh.rs | 2 +- 6 files changed, 100 insertions(+), 31 deletions(-) diff --git a/crates/oxc_transformer/src/common/var_declarations.rs b/crates/oxc_transformer/src/common/var_declarations.rs index ec490f63908a4..7701c15a5378c 100644 --- a/crates/oxc_transformer/src/common/var_declarations.rs +++ b/crates/oxc_transformer/src/common/var_declarations.rs @@ -1,15 +1,17 @@ -//! Utility transform to add `var` declarations to top of statement blocks. +//! Utility transform to add `var` or `let` declarations to top of statement blocks. //! -//! `VarDeclarationsStore` contains a stack of `Vec`s. -//! It is stored on `TransformCtx`. +//! `VarDeclarationsStore` contains a stack of `Declarators`s, each comprising +//! 2 x `Vec>` (1 for `var`s, 1 for `let`s). +//! `VarDeclarationsStore` is stored on `TransformCtx`. //! //! `VarDeclarations` transform pushes an empty entry onto this stack when entering a statement block, -//! and when exiting the block, writes a `var` statement to top of block containing the declarators. +//! and when exiting the block, writes `var` / `let` statements to top of block. //! //! Other transforms can add declarators to the store by calling methods of `VarDeclarationsStore`: //! //! ```rs -//! self.ctx.var_declarations.insert_declarator(name, symbol_id, None, ctx); +//! self.ctx.var_declarations.insert_var(name, binding, None, ctx); +//! self.ctx.var_declarations.insert_let(name2, binding2, None, ctx); //! ``` use std::cell::RefCell; @@ -60,7 +62,19 @@ impl<'a, 'ctx> Traverse<'a> for VarDeclarations<'a, 'ctx> { /// Store for `VariableDeclarator`s to be added to enclosing statement block. pub struct VarDeclarationsStore<'a> { - stack: RefCell>>>, + stack: RefCell>>, +} + +/// Declarators to be inserted in a statement block. +struct Declarators<'a> { + var_declarators: ArenaVec<'a, VariableDeclarator<'a>>, + let_declarators: ArenaVec<'a, VariableDeclarator<'a>>, +} + +impl<'a> Declarators<'a> { + fn new(ctx: &TraverseCtx<'a>) -> Self { + Self { var_declarators: ctx.ast.vec(), let_declarators: ctx.ast.vec() } + } } // Public methods @@ -70,21 +84,34 @@ impl<'a> VarDeclarationsStore<'a> { Self { stack: RefCell::new(SparseStack::new()) } } - /// Add a `VariableDeclarator` to be inserted at top of current enclosing statement block, + /// Add a `var` declaration to be inserted at top of current enclosing statement block, /// given a `BoundIdentifier`. - pub fn insert( + pub fn insert_var( &self, binding: &BoundIdentifier<'a>, init: Option>, ctx: &TraverseCtx<'a>, ) { let pattern = binding.create_binding_pattern(ctx); - self.insert_binding_pattern(pattern, init, ctx); + self.insert_var_binding_pattern(pattern, init, ctx); } - /// Add a `VariableDeclarator` to be inserted at top of current enclosing statement block, + /// Add a `let` declaration to be inserted at top of current enclosing statement block, + /// given a `BoundIdentifier`. + #[expect(dead_code)] + pub fn insert_let( + &self, + binding: &BoundIdentifier<'a>, + init: Option>, + ctx: &TraverseCtx<'a>, + ) { + let pattern = binding.create_binding_pattern(ctx); + self.insert_let_binding_pattern(pattern, init, ctx); + } + + /// Add a `var` declaration to be inserted at top of current enclosing statement block, /// given a `BindingPattern`. - pub fn insert_binding_pattern( + pub fn insert_var_binding_pattern( &self, ident: BindingPattern<'a>, init: Option>, @@ -92,13 +119,34 @@ impl<'a> VarDeclarationsStore<'a> { ) { let declarator = ctx.ast.variable_declarator(SPAN, VariableDeclarationKind::Var, ident, init, false); - self.insert_declarator(declarator, ctx); + self.insert_var_declarator(declarator, ctx); + } + + /// Add a `let` declaration to be inserted at top of current enclosing statement block, + /// given a `BindingPattern`. + pub fn insert_let_binding_pattern( + &self, + ident: BindingPattern<'a>, + init: Option>, + ctx: &TraverseCtx<'a>, + ) { + let declarator = + ctx.ast.variable_declarator(SPAN, VariableDeclarationKind::Let, ident, init, false); + self.insert_let_declarator(declarator, ctx); } - /// Add a `VariableDeclarator` to be inserted at top of current enclosing statement block. - pub fn insert_declarator(&self, declarator: VariableDeclarator<'a>, ctx: &TraverseCtx<'a>) { + /// Add a `var` declaration to be inserted at top of current enclosing statement block. + pub fn insert_var_declarator(&self, declarator: VariableDeclarator<'a>, ctx: &TraverseCtx<'a>) { let mut stack = self.stack.borrow_mut(); - stack.last_mut_or_init(|| ctx.ast.vec()).push(declarator); + let declarators = stack.last_mut_or_init(|| Declarators::new(ctx)); + declarators.var_declarators.push(declarator); + } + + /// Add a `let` declaration to be inserted at top of current enclosing statement block. + pub fn insert_let_declarator(&self, declarator: VariableDeclarator<'a>, ctx: &TraverseCtx<'a>) { + let mut stack = self.stack.borrow_mut(); + let declarators = stack.last_mut_or_init(|| Declarators::new(ctx)); + declarators.let_declarators.push(declarator); } } @@ -119,15 +167,15 @@ impl<'a> VarDeclarationsStore<'a> { return; } - if let Some(stmt) = self.get_var_statement(ctx) { - stmts.insert(0, stmt); + if let Some(insert_stmts) = self.get_var_statement(ctx) { + stmts.splice(0..0, insert_stmts); } } fn insert_into_program(&self, transform_ctx: &TransformCtx<'a>, ctx: &mut TraverseCtx<'a>) { - if let Some(stmt) = self.get_var_statement(ctx) { + if let Some(insert_stmts) = self.get_var_statement(ctx) { // Delegate to `TopLevelStatements` - transform_ctx.top_level_statements.insert_statement(stmt); + transform_ctx.top_level_statements.insert_statements(insert_stmts); } // Check stack is emptied @@ -136,17 +184,38 @@ impl<'a> VarDeclarationsStore<'a> { debug_assert!(stack.last().is_none()); } - fn get_var_statement(&self, ctx: &mut TraverseCtx<'a>) -> Option> { + fn get_var_statement(&self, ctx: &mut TraverseCtx<'a>) -> Option>> { let mut stack = self.stack.borrow_mut(); - let declarators = stack.pop()?; - debug_assert!(!declarators.is_empty()); + let Declarators { var_declarators, let_declarators } = stack.pop()?; + + let mut stmts = Vec::with_capacity(2); + if !var_declarators.is_empty() { + stmts.push(Self::create_declaration( + VariableDeclarationKind::Var, + var_declarators, + ctx, + )); + } + if !let_declarators.is_empty() { + stmts.push(Self::create_declaration( + VariableDeclarationKind::Let, + let_declarators, + ctx, + )); + } + Some(stmts) + } - let stmt = Statement::VariableDeclaration(ctx.ast.alloc_variable_declaration( + fn create_declaration( + kind: VariableDeclarationKind, + declarators: ArenaVec<'a, VariableDeclarator<'a>>, + ctx: &mut TraverseCtx<'a>, + ) -> Statement<'a> { + Statement::VariableDeclaration(ctx.ast.alloc_variable_declaration( SPAN, - VariableDeclarationKind::Var, + kind, declarators, false, - )); - Some(stmt) + )) } } diff --git a/crates/oxc_transformer/src/es2016/exponentiation_operator.rs b/crates/oxc_transformer/src/es2016/exponentiation_operator.rs index 2afd099f500f6..495fff967e4ca 100644 --- a/crates/oxc_transformer/src/es2016/exponentiation_operator.rs +++ b/crates/oxc_transformer/src/es2016/exponentiation_operator.rs @@ -575,7 +575,7 @@ impl<'a, 'ctx> ExponentiationOperator<'a, 'ctx> { ); // var _name; - self.ctx.var_declarations.insert(&binding, None, ctx); + self.ctx.var_declarations.insert_var(&binding, None, ctx); // Add new reference `_name = name` to `temp_var_inits` temp_var_inits.push(ctx.ast.expression_assignment( diff --git a/crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs b/crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs index 403b2bd55f866..4071c3e638051 100644 --- a/crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs +++ b/crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs @@ -124,7 +124,7 @@ impl<'a, 'ctx> Traverse<'a> for NullishCoalescingOperator<'a, 'ctx> { // `(x) => x;` -> `((x) => x)();` new_expr = ctx.ast.expression_call(SPAN, arrow_function, NONE, ctx.ast.vec(), false); } else { - self.ctx.var_declarations.insert(&binding, None, ctx); + self.ctx.var_declarations.insert_var(&binding, None, ctx); } *expr = new_expr; diff --git a/crates/oxc_transformer/src/es2021/logical_assignment_operators.rs b/crates/oxc_transformer/src/es2021/logical_assignment_operators.rs index 35c6628cd329b..2be9609c06a37 100644 --- a/crates/oxc_transformer/src/es2021/logical_assignment_operators.rs +++ b/crates/oxc_transformer/src/es2021/logical_assignment_operators.rs @@ -331,7 +331,7 @@ impl<'a, 'ctx> LogicalAssignmentOperators<'a, 'ctx> { // var _name; let binding = ctx .generate_uid_in_current_scope_based_on_node(expr, SymbolFlags::FunctionScopedVariable); - self.ctx.var_declarations.insert(&binding, None, ctx); + self.ctx.var_declarations.insert_var(&binding, None, ctx); Some(binding) } diff --git a/crates/oxc_transformer/src/jsx/jsx_impl.rs b/crates/oxc_transformer/src/jsx/jsx_impl.rs index df96a05f88057..2585342517b68 100644 --- a/crates/oxc_transformer/src/jsx/jsx_impl.rs +++ b/crates/oxc_transformer/src/jsx/jsx_impl.rs @@ -481,7 +481,7 @@ impl<'a, 'ctx> JsxImpl<'a, 'ctx> { self.ctx.top_level_statements.insert_statement(stmt); } else { // Insert after imports - add to `var_declarations`, which are inserted after `require` statements - self.ctx.var_declarations.insert_declarator(declarator, ctx); + self.ctx.var_declarations.insert_var_declarator(declarator, ctx); } } diff --git a/crates/oxc_transformer/src/jsx/refresh.rs b/crates/oxc_transformer/src/jsx/refresh.rs index bfefe8e255a20..03c76ffb7262f 100644 --- a/crates/oxc_transformer/src/jsx/refresh.rs +++ b/crates/oxc_transformer/src/jsx/refresh.rs @@ -609,7 +609,7 @@ impl<'a, 'ctx> ReactRefresh<'a, 'ctx> { body.statements.insert(0, call_expression); // _s = refresh_sig(); - self.ctx.var_declarations.insert( + self.ctx.var_declarations.insert_var( &binding, Some(ctx.ast.expression_call( SPAN,