From 803eff63e6eb1e4df41c25f3cc2ff4069a1e8ba3 Mon Sep 17 00:00:00 2001
From: Dunqing <29533304+Dunqing@users.noreply.github.com>
Date: Mon, 21 Oct 2024 11:00:18 +0000
Subject: [PATCH] feat(transformer): introduce `StatementInjector` helper
(#6653)
close: #6641
---
.../src/common/statement_injector.rs | 139 ++++++++++++++++++
1 file changed, 139 insertions(+)
diff --git a/crates/oxc_transformer/src/common/statement_injector.rs b/crates/oxc_transformer/src/common/statement_injector.rs
index 084ac44de29ab4..6a390b2b599010 100644
--- a/crates/oxc_transformer/src/common/statement_injector.rs
+++ b/crates/oxc_transformer/src/common/statement_injector.rs
@@ -137,3 +137,142 @@ impl<'a> StatementInjectorStore<'a> {
*statements = new_statements;
}
}
+//! Utility transform to add new statements before or after the specified statement.
+//!
+//! `StatementInjectorStore` contains a `FxHashMap
>`. It is stored on `TransformCtx`.
+//!
+//! `StatementInjector` transform inserts new statements before or after a statement which is determined by the address of the statement.
+//!
+//! Other transforms can add statements to the store with following methods:
+//!
+//! ```rs
+//! self.ctx.statement_injector.insert_before(address, statement);
+//! self.ctx.statement_injector.insert_after(address, statement);
+//! self.ctx.statement_injector.insert_many_after(address, statements);
+//! ```
+
+use std::cell::RefCell;
+
+use oxc_allocator::{Address, Vec as OxcVec};
+
+use oxc_ast::{address::GetAddress, ast::*};
+use oxc_traverse::{Traverse, TraverseCtx};
+use rustc_hash::FxHashMap;
+
+use crate::TransformCtx;
+
+/// Transform that inserts any statements which have been requested insertion via `StatementInjectorStore`
+pub struct StatementInjector<'a, 'ctx> {
+ ctx: &'ctx TransformCtx<'a>,
+}
+
+impl<'a, 'ctx> StatementInjector<'a, 'ctx> {
+ pub fn new(ctx: &'ctx TransformCtx<'a>) -> Self {
+ Self { ctx }
+ }
+}
+
+impl<'a, 'ctx> Traverse<'a> for StatementInjector<'a, 'ctx> {
+ fn exit_statements(
+ &mut self,
+ statements: &mut OxcVec<'a, Statement<'a>>,
+ ctx: &mut TraverseCtx<'a>,
+ ) {
+ self.ctx.statement_injector.insert_into_statements(statements, ctx);
+ }
+}
+
+enum Direction {
+ Before,
+ After,
+}
+
+struct AdjacentStatement<'a> {
+ stmt: Statement<'a>,
+ direction: Direction,
+}
+
+/// Store for statements to be added to the statements.
+pub struct StatementInjectorStore<'a> {
+ insertions: RefCell>>>,
+}
+
+// Public methods
+impl<'a> StatementInjectorStore<'a> {
+ /// Create new `StatementInjectorStore`.
+ pub fn new() -> Self {
+ Self { insertions: RefCell::new(FxHashMap::default()) }
+ }
+
+ /// Add a statement to be inserted immediately before the target statement.
+ #[expect(dead_code)]
+ pub fn insert_before(&self, target: Address, stmt: Statement<'a>) {
+ let mut insertions = self.insertions.borrow_mut();
+ let adjacent_stmts = insertions.entry(target).or_default();
+ let index = adjacent_stmts
+ .iter()
+ .position(|s| matches!(s.direction, Direction::After))
+ .unwrap_or(adjacent_stmts.len());
+ adjacent_stmts.insert(index, AdjacentStatement { stmt, direction: Direction::Before });
+ }
+
+ /// Add a statement to be inserted immediately after the target statement.
+ #[expect(dead_code)]
+ pub fn insert_after(&self, target: Address, stmt: Statement<'a>) {
+ let mut insertions = self.insertions.borrow_mut();
+ let adjacent_stmts = insertions.entry(target).or_default();
+ adjacent_stmts.push(AdjacentStatement { stmt, direction: Direction::After });
+ }
+
+ /// Add multiple statements to be inserted immediately after the target statement.
+ #[expect(dead_code)]
+ pub fn insert_many_after(&self, target: Address, stmts: Vec>) {
+ let mut insertions = self.insertions.borrow_mut();
+ let adjacent_stmts = insertions.entry(target).or_default();
+ adjacent_stmts.extend(
+ stmts.into_iter().map(|stmt| AdjacentStatement { stmt, direction: Direction::After }),
+ );
+ }
+
+ /// Insert statements immediately before / after the target statement.
+ pub(self) fn insert_into_statements(
+ &self,
+ statements: &mut OxcVec<'a, Statement<'a>>,
+ ctx: &mut TraverseCtx<'a>,
+ ) {
+ let mut insertions = self.insertions.borrow_mut();
+ if insertions.is_empty() {
+ return;
+ }
+
+ let new_statement_count = statements
+ .iter()
+ .filter_map(|s| insertions.get(&s.address()).map(Vec::len))
+ .sum::();
+ if new_statement_count == 0 {
+ return;
+ }
+
+ let mut new_statements = ctx.ast.vec_with_capacity(statements.len() + new_statement_count);
+
+ for stmt in statements.drain(..) {
+ if let Some(mut adjacent_stmts) = insertions.remove(&stmt.address()) {
+ let first_after_stmt_index = adjacent_stmts
+ .iter()
+ .position(|s| matches!(s.direction, Direction::After))
+ .unwrap_or(adjacent_stmts.len());
+ if first_after_stmt_index != 0 {
+ let right = adjacent_stmts.split_off(first_after_stmt_index);
+ new_statements.extend(adjacent_stmts.into_iter().map(|s| s.stmt));
+ new_statements.push(stmt);
+ new_statements.extend(right.into_iter().map(|s| s.stmt));
+ } else {
+ new_statements.push(stmt);
+ new_statements.extend(adjacent_stmts.into_iter().map(|s| s.stmt));
+ }
+ }
+ }
+
+ *statements = new_statements;
+ }
+}