Skip to content

Commit

Permalink
refactor(transformer): shared VarDeclarations (#6170)
Browse files Browse the repository at this point in the history
First step towards #5049.

Various transforms need to add a `var` statement at top of enclosing statement block.

e.g.:

```js
// Input
a ??= b;
```

```js
// Output
var _a;
(_a = a) !== null && _a !== void 0 ? _a : (a = b);
```

Each of these transforms previously maintained it's own stack and added `var` statements individually.

Share this functionality in a "common" utility transform which maintains a single stack to serve them all.
  • Loading branch information
overlookmotel committed Sep 30, 2024
1 parent b92fe84 commit 21b08ba
Show file tree
Hide file tree
Showing 11 changed files with 228 additions and 263 deletions.
37 changes: 37 additions & 0 deletions crates/oxc_transformer/src/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! Utility transforms which are in common between other transforms.

use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_traverse::{Traverse, TraverseCtx};

use crate::TransformCtx;

mod var_declarations;

use var_declarations::VarDeclarations;
pub use var_declarations::VarDeclarationsStore;

pub struct Common<'a, 'ctx> {
var_declarations: VarDeclarations<'a, 'ctx>,
}

impl<'a, 'ctx> Common<'a, 'ctx> {
pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self {
Self { var_declarations: VarDeclarations::new(ctx) }
}
}

impl<'a, 'ctx> Traverse<'a> for Common<'a, 'ctx> {
#[inline] // Inline because it's no-op in release mode
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
self.var_declarations.exit_program(program, ctx);
}

fn enter_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
self.var_declarations.enter_statements(stmts, ctx);
}

fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
self.var_declarations.exit_statements(stmts, ctx);
}
}
111 changes: 111 additions & 0 deletions crates/oxc_transformer/src/common/var_declarations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! Utility transform to add `var` declarations to top of statement blocks.
//!
//! `VarDeclarationsStore` contains a stack of `Vec<VariableDeclarator>`s.
//! It 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.
//!
//! 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);
//! ```

use std::cell::RefCell;

use oxc_allocator::Vec;
use oxc_ast::{ast::*, NONE};
use oxc_span::SPAN;
use oxc_syntax::symbol::SymbolId;
use oxc_traverse::{Traverse, TraverseCtx};

use crate::{context::TransformCtx, helpers::stack::SparseStack};

/// Transform that maintains the stack of `Vec<VariableDeclarator>`s, and adds a `var` statement
/// to top of a statement block if another transform has requested that.
///
/// Must run after all other transforms.
pub struct VarDeclarations<'a, 'ctx> {
ctx: &'ctx TransformCtx<'a>,
}

impl<'a, 'ctx> VarDeclarations<'a, 'ctx> {
pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self {
Self { ctx }
}
}

impl<'a, 'ctx> Traverse<'a> for VarDeclarations<'a, 'ctx> {
#[inline] // Inline because it's no-op in release mode
fn exit_program(&mut self, _program: &mut Program<'a>, _ctx: &mut TraverseCtx<'a>) {
let declarators = self.ctx.var_declarations.declarators.borrow();
debug_assert!(declarators.len() == 1);
debug_assert!(declarators.last().is_none());
}

fn enter_statements(
&mut self,
_stmts: &mut Vec<'a, Statement<'a>>,
_ctx: &mut TraverseCtx<'a>,
) {
let mut declarators = self.ctx.var_declarations.declarators.borrow_mut();
declarators.push(None);
}

fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
let mut declarators = self.ctx.var_declarations.declarators.borrow_mut();
if let Some(declarators) = declarators.pop() {
debug_assert!(!declarators.is_empty());
let variable = ctx.ast.alloc_variable_declaration(
SPAN,
VariableDeclarationKind::Var,
declarators,
false,
);
stmts.insert(0, Statement::VariableDeclaration(variable));
}
}
}

/// Store for `VariableDeclarator`s to be added to enclosing statement block.
pub struct VarDeclarationsStore<'a> {
declarators: RefCell<SparseStack<Vec<'a, VariableDeclarator<'a>>>>,
}

impl<'a> VarDeclarationsStore<'a> {
pub fn new() -> Self {
Self { declarators: RefCell::new(SparseStack::new()) }
}
}

impl<'a> VarDeclarationsStore<'a> {
/// Add a `VariableDeclarator` to be inserted at top of current enclosing statement block,
/// given `name` and `symbol_id`.
pub fn insert_declarator(
&self,
name: Atom<'a>,
symbol_id: SymbolId,
init: Option<Expression<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
let ident = BindingIdentifier::new_with_symbol_id(SPAN, name, symbol_id);
let ident = ctx.ast.binding_pattern_kind_from_binding_identifier(ident);
let ident = ctx.ast.binding_pattern(ident, NONE, false);
self.insert_declarator_binding_pattern(ident, init, ctx);
}

/// Add a `VariableDeclarator` to be inserted at top of current enclosing statement block,
/// given a `BindingPattern`.
pub fn insert_declarator_binding_pattern(
&self,
ident: BindingPattern<'a>,
init: Option<Expression<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
let declarator =
ctx.ast.variable_declarator(SPAN, VariableDeclarationKind::Var, ident, init, false);
let mut declarators = self.declarators.borrow_mut();
declarators.last_mut_or_init(|| ctx.ast.vec()).push(declarator);
}
}
7 changes: 6 additions & 1 deletion crates/oxc_transformer/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use oxc_ast::{AstBuilder, Trivias};
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::SourceType;

use crate::{helpers::module_imports::ModuleImports, TransformOptions};
use crate::{
common::VarDeclarationsStore, helpers::module_imports::ModuleImports, TransformOptions,
};

pub struct TransformCtx<'a> {
errors: RefCell<Vec<OxcDiagnostic>>,
Expand All @@ -31,6 +33,8 @@ pub struct TransformCtx<'a> {
// Helpers
/// Manage import statement globally
pub module_imports: ModuleImports<'a>,
/// Manage inserting `var` statements globally
pub var_declarations: VarDeclarationsStore<'a>,
}

impl<'a> TransformCtx<'a> {
Expand Down Expand Up @@ -59,6 +63,7 @@ impl<'a> TransformCtx<'a> {
source_text,
trivias,
module_imports: ModuleImports::new(allocator),
var_declarations: VarDeclarationsStore::new(),
}
}

Expand Down
60 changes: 10 additions & 50 deletions crates/oxc_transformer/src/es2016/exponentiation_operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ use oxc_span::SPAN;
use oxc_syntax::operator::{AssignmentOperator, BinaryOperator};
use oxc_traverse::{Traverse, TraverseCtx};

use crate::helpers::stack::SparseStack;
use crate::TransformCtx;

/// ES2016: Exponentiation Operator
///
/// References:
/// * <https://babel.dev/docs/babel-plugin-transform-exponentiation-operator>
/// * <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-exponentiation-operator>
/// * <https://github.com/babel/babel/blob/main/packages/babel-helper-builder-binary-assignment-operator-visitor>
pub struct ExponentiationOperator<'a> {
var_declarations: SparseStack<Vec<'a, VariableDeclarator<'a>>>,
pub struct ExponentiationOperator<'a, 'ctx> {
ctx: &'ctx TransformCtx<'a>,
}

#[derive(Debug)]
Expand All @@ -55,44 +55,13 @@ struct Exploded<'a> {
uid: Expression<'a>,
}

impl<'a> ExponentiationOperator<'a> {
pub fn new() -> Self {
Self { var_declarations: SparseStack::new() }
impl<'a, 'ctx> ExponentiationOperator<'a, 'ctx> {
pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self {
Self { ctx }
}
}

impl<'a> Traverse<'a> for ExponentiationOperator<'a> {
#[inline] // Inline because it's no-op in release mode
fn exit_program(&mut self, _program: &mut Program<'a>, _ctx: &mut TraverseCtx<'a>) {
debug_assert!(self.var_declarations.len() == 1);
debug_assert!(self.var_declarations.last().is_none());
}

fn enter_statements(
&mut self,
_statements: &mut Vec<'a, Statement<'a>>,
_ctx: &mut TraverseCtx<'a>,
) {
self.var_declarations.push(None);
}

fn exit_statements(
&mut self,
statements: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
if let Some(declarations) = self.var_declarations.pop() {
debug_assert!(!declarations.is_empty());
let variable = ctx.ast.alloc_variable_declaration(
SPAN,
VariableDeclarationKind::Var,
declarations,
false,
);
statements.insert(0, Statement::VariableDeclaration(variable));
}
}

impl<'a, 'ctx> Traverse<'a> for ExponentiationOperator<'a, 'ctx> {
// NOTE: Bail bigint arguments to `Math.pow`, which are runtime errors.
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
match expr {
Expand Down Expand Up @@ -139,7 +108,7 @@ impl<'a> Traverse<'a> for ExponentiationOperator<'a> {
}
}

impl<'a> ExponentiationOperator<'a> {
impl<'a, 'ctx> ExponentiationOperator<'a, 'ctx> {
fn clone_expression(expr: &Expression<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
match expr {
Expression::Identifier(ident) => ctx.ast.expression_from_identifier_reference(
Expand Down Expand Up @@ -325,17 +294,8 @@ impl<'a> ExponentiationOperator<'a> {
ctx.generate_uid_in_current_scope(name, SymbolFlags::FunctionScopedVariable);
let symbol_name = ctx.ast.atom(ctx.symbols().get_name(symbol_id));

{
// var _name;
let binding_identifier =
BindingIdentifier::new_with_symbol_id(SPAN, symbol_name.clone(), symbol_id);
let kind = VariableDeclarationKind::Var;
let id = ctx.ast.binding_pattern_kind_from_binding_identifier(binding_identifier);
let id = ctx.ast.binding_pattern(id, NONE, false);
self.var_declarations
.last_mut_or_init(|| ctx.ast.vec())
.push(ctx.ast.variable_declarator(SPAN, kind, id, None, false));
}
// var _name;
self.ctx.var_declarations.insert_declarator(symbol_name.clone(), symbol_id, None, ctx);

let ident =
ctx.create_reference_id(SPAN, symbol_name, Some(symbol_id), ReferenceFlags::Read);
Expand Down
42 changes: 8 additions & 34 deletions crates/oxc_transformer/src/es2016/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,25 @@ mod options;

pub use exponentiation_operator::ExponentiationOperator;
pub use options::ES2016Options;
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_traverse::{Traverse, TraverseCtx};

pub struct ES2016<'a> {
use crate::TransformCtx;

pub struct ES2016<'a, 'ctx> {
options: ES2016Options,

// Plugins
exponentiation_operator: ExponentiationOperator<'a>,
exponentiation_operator: ExponentiationOperator<'a, 'ctx>,
}

impl<'a> ES2016<'a> {
pub fn new(options: ES2016Options) -> Self {
Self { exponentiation_operator: ExponentiationOperator::new(), options }
impl<'a, 'ctx> ES2016<'a, 'ctx> {
pub fn new(options: ES2016Options, ctx: &'ctx TransformCtx<'a>) -> Self {
Self { exponentiation_operator: ExponentiationOperator::new(ctx), options }
}
}

impl<'a> Traverse<'a> for ES2016<'a> {
#[inline] // Inline because it's no-op in release mode
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.exponentiation_operator {
self.exponentiation_operator.exit_program(program, ctx);
}
}

fn enter_statements(
&mut self,
statements: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
if self.options.exponentiation_operator {
self.exponentiation_operator.enter_statements(statements, ctx);
}
}

fn exit_statements(
&mut self,
statements: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
if self.options.exponentiation_operator {
self.exponentiation_operator.exit_statements(statements, ctx);
}
}

impl<'a, 'ctx> Traverse<'a> for ES2016<'a, 'ctx> {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.exponentiation_operator {
self.exponentiation_operator.enter_expression(expr, ctx);
Expand Down
42 changes: 8 additions & 34 deletions crates/oxc_transformer/src/es2020/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,25 @@ mod options;

pub use nullish_coalescing_operator::NullishCoalescingOperator;
pub use options::ES2020Options;
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_traverse::{Traverse, TraverseCtx};

pub struct ES2020<'a> {
use crate::TransformCtx;

pub struct ES2020<'a, 'ctx> {
options: ES2020Options,

// Plugins
nullish_coalescing_operator: NullishCoalescingOperator<'a>,
nullish_coalescing_operator: NullishCoalescingOperator<'a, 'ctx>,
}

impl<'a> ES2020<'a> {
pub fn new(options: ES2020Options) -> Self {
Self { nullish_coalescing_operator: NullishCoalescingOperator::new(), options }
impl<'a, 'ctx> ES2020<'a, 'ctx> {
pub fn new(options: ES2020Options, ctx: &'ctx TransformCtx<'a>) -> Self {
Self { nullish_coalescing_operator: NullishCoalescingOperator::new(ctx), options }
}
}

impl<'a> Traverse<'a> for ES2020<'a> {
#[inline] // Inline because it's no-op in release mode
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.nullish_coalescing_operator {
self.nullish_coalescing_operator.exit_program(program, ctx);
}
}

fn enter_statements(
&mut self,
statements: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
if self.options.nullish_coalescing_operator {
self.nullish_coalescing_operator.enter_statements(statements, ctx);
}
}

fn exit_statements(
&mut self,
statements: &mut Vec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
if self.options.nullish_coalescing_operator {
self.nullish_coalescing_operator.exit_statements(statements, ctx);
}
}

impl<'a, 'ctx> Traverse<'a> for ES2020<'a, 'ctx> {
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.nullish_coalescing_operator {
self.nullish_coalescing_operator.enter_expression(expr, ctx);
Expand Down
Loading

0 comments on commit 21b08ba

Please sign in to comment.