use itertools::Itertools;
use oxc::allocator::GetAllocator;
use oxc::allocator::{Allocator, TakeIn};
use oxc::ast::NONE;
use oxc::ast::ast::{self, BindingPattern, Declaration, ImportOrExportKind, Statement};
use oxc::ast_visit::{VisitMut, walk_mut};
use oxc::span::{SPAN, Span};
use rolldown_ecmascript_utils::{AstFactory, StatementExt};
use rustc_hash::FxHashSet;
pub struct PreProcessor<'ast, 'a> {
ast_factory: AstFactory<'ast>,
top_level_stmt_temp_storage: Vec<Statement<'ast>>,
keep_names: bool,
drop_labels: Option<&'a FxHashSet<String>>,
defer_spans: Vec<Span>,
}
impl<'ast, 'a> PreProcessor<'ast, 'a> {
pub fn new(
alloc: &'ast Allocator,
keep_names: bool,
drop_labels: Option<&'a FxHashSet<String>>,
) -> Self {
Self {
ast_factory: AstFactory::new(alloc),
top_level_stmt_temp_storage: vec![],
keep_names,
drop_labels: drop_labels.filter(|set| !set.is_empty()),
defer_spans: vec![],
}
}
pub fn take_defer_spans(&mut self) -> Vec<Span> {
std::mem::take(&mut self.defer_spans)
}
fn try_drop_labeled(&self, it: &mut Statement<'ast>) -> bool {
let Some(labels) = self.drop_labels else { return false };
if let Statement::LabeledStatement(stmt) = it
&& labels.contains(stmt.label.name.as_str())
{
*it = ast::Statement::new_empty_statement(stmt.span, &self.ast_factory);
return true;
}
false
}
fn split_var_declaration(
&self,
var_decl: &mut ast::VariableDeclaration<'ast>,
named_decl_span: Option<Span>,
) -> Vec<Statement<'ast>> {
var_decl
.declarations
.take_in(&self.ast_factory.allocator())
.into_iter()
.enumerate()
.map(|(i, declarator)| {
let new_decl = ast::VariableDeclaration::boxed(
SPAN,
var_decl.kind,
oxc::allocator::Vec::from_iter_in([declarator], &self.ast_factory),
var_decl.declare,
&self.ast_factory,
);
if let Some(named_decl_span) = named_decl_span {
Statement::ExportNamedDeclaration(ast::ExportNamedDeclaration::boxed(
if i == 0 { named_decl_span } else { SPAN },
Some(Declaration::VariableDeclaration(new_decl)),
oxc::allocator::Vec::new_in(&self.ast_factory),
None,
ImportOrExportKind::Value,
NONE,
&self.ast_factory,
))
} else {
Statement::VariableDeclaration(new_decl)
}
})
.collect_vec()
}
fn split_multi_declarator(&self, stmt: &mut Statement<'ast>) -> Option<Vec<Statement<'ast>>> {
match stmt {
Statement::ExportNamedDeclaration(named_decl) => {
let named_decl_span = named_decl.span;
let Some(Declaration::VariableDeclaration(var_decl)) = named_decl.declaration.as_mut()
else {
return None;
};
(var_decl.declarations.len() > 1
&& var_decl
.declarations
.iter()
.any(|declarator| matches!(declarator.id, BindingPattern::BindingIdentifier(_))))
.then(|| self.split_var_declaration(var_decl, Some(named_decl_span)))
}
Statement::VariableDeclaration(var_decl) => (self.keep_names
&& var_decl.declarations.len() > 1)
.then(|| self.split_var_declaration(var_decl, None)),
_ => None,
}
}
}
impl<'ast> VisitMut<'ast> for PreProcessor<'ast, '_> {
fn visit_import_declaration(&mut self, it: &mut ast::ImportDeclaration<'ast>) {
if matches!(it.phase, Some(ast::ImportPhase::Defer)) {
self.defer_spans.push(it.span);
it.phase = None;
}
walk_mut::walk_import_declaration(self, it);
}
fn visit_program(&mut self, program: &mut ast::Program<'ast>) {
let original_body = program.body.take_in(&self.ast_factory.allocator());
program.body.reserve_exact(original_body.len());
self.top_level_stmt_temp_storage = Vec::with_capacity(
original_body.iter().filter(|stmt| !stmt.is_module_declaration_with_source()).count(),
);
for mut stmt in original_body {
if self.try_drop_labeled(&mut stmt) {
self.top_level_stmt_temp_storage.push(stmt);
continue;
}
walk_mut::walk_statement(self, &mut stmt);
if let Some(split) = self.split_multi_declarator(&mut stmt) {
self.top_level_stmt_temp_storage.extend(split);
} else if stmt.is_module_declaration_with_source() {
program.body.push(stmt);
} else {
self.top_level_stmt_temp_storage.push(stmt);
}
}
program.body.extend(std::mem::take(&mut self.top_level_stmt_temp_storage));
}
fn visit_statement(&mut self, it: &mut Statement<'ast>) {
if self.try_drop_labeled(it) {
return;
}
walk_mut::walk_statement(self, it);
if let Some(split) = self.split_multi_declarator(it) {
*it = Statement::BlockStatement(ast::BlockStatement::boxed(
SPAN,
oxc::allocator::Vec::from_iter_in(split, &self.ast_factory),
&self.ast_factory,
));
}
}
fn visit_statements(&mut self, it: &mut oxc::allocator::Vec<'ast, Statement<'ast>>) {
if !self.keep_names {
walk_mut::walk_statements(self, it);
return;
}
let stmts = it.take_in(&self.ast_factory.allocator());
for mut stmt in stmts {
if self.try_drop_labeled(&mut stmt) {
it.push(stmt);
continue;
}
walk_mut::walk_statement(self, &mut stmt);
if let Some(split) = self.split_multi_declarator(&mut stmt) {
it.extend(split);
} else {
it.push(stmt);
}
}
}
fn visit_import_expression(&mut self, it: &mut ast::ImportExpression<'ast>) {
if matches!(it.phase, Some(ast::ImportPhase::Defer)) {
self.defer_spans.push(it.span);
it.phase = None;
}
walk_mut::walk_import_expression(self, it);
}
fn visit_expression(&mut self, it: &mut ast::Expression<'ast>) {
let to_replaced = match it {
ast::Expression::CallExpression(expr)
if expr.callee.is_specific_id("require") && expr.arguments.len() == 1 =>
{
let arg = expr.arguments.get_mut(0).unwrap();
if let Some(cond_expr) = arg.as_expression_mut().and_then(|item| match item {
ast::Expression::ConditionalExpression(cond) => Some(cond),
_ => None,
}) {
let test = cond_expr.test.take_in(&self.ast_factory.allocator());
let consequent = cond_expr.consequent.take_in(&self.ast_factory.allocator());
let alternative = cond_expr.alternate.take_in(&self.ast_factory.allocator());
let new_cond_expr = ast::ConditionalExpression::boxed(
SPAN,
test,
ast::Expression::new_call_expression(
SPAN,
ast::Expression::new_identifier(SPAN, "require", &self.ast_factory),
NONE,
oxc::allocator::Vec::from_value_in(
ast::Argument::from(consequent),
&self.ast_factory,
),
false,
&self.ast_factory,
),
ast::Expression::new_call_expression(
SPAN,
ast::Expression::new_identifier(SPAN, "require", &self.ast_factory),
NONE,
oxc::allocator::Vec::from_value_in(
ast::Argument::from(alternative),
&self.ast_factory,
),
false,
&self.ast_factory,
),
&self.ast_factory,
);
Some(ast::Expression::ConditionalExpression(new_cond_expr))
} else {
None
}
}
ast::Expression::ImportExpression(expr) if expr.options.is_none() => {
let source = &mut expr.source;
match source {
ast::Expression::ConditionalExpression(cond_expr) => {
let test = cond_expr.test.take_in(&self.ast_factory.allocator());
let consequent = cond_expr.consequent.take_in(&self.ast_factory.allocator());
let alternative = cond_expr.alternate.take_in(&self.ast_factory.allocator());
let new_cond_expr = ast::Expression::new_conditional_expression(
SPAN,
test,
ast::Expression::new_import_expression(
SPAN,
consequent,
None,
None,
&self.ast_factory,
),
ast::Expression::new_import_expression(
SPAN,
alternative,
None,
None,
&self.ast_factory,
),
&self.ast_factory,
);
Some(new_cond_expr)
}
_ => None,
}
}
_ => None,
};
if let Some(replaced) = to_replaced {
*it = replaced;
}
walk_mut::walk_expression(self, it);
}
}