mod convert_to_dotted_properties;
mod fold_constants;
mod inline;
mod minimize_conditional_expression;
mod minimize_conditions;
mod minimize_expression_in_boolean_context;
mod minimize_for_statement;
mod minimize_if_statement;
mod minimize_logical_expression;
mod minimize_not_expression;
mod minimize_statements;
mod normalize;
mod remove_dead_code;
mod remove_unused_declaration;
mod remove_unused_expression;
mod remove_unused_private_members;
mod replace_known_methods;
mod substitute_alternate_syntax;
use oxc_syntax::{scope::ScopeId, symbol::SymbolId};
use oxc_allocator::ArenaVec;
use oxc_ast::ast::*;
use oxc_ecmascript::constant_evaluation::IsLiteralValue;
use crate::{Traverse, TraverseCtx, generated::ancestor::Ancestor, state::BodyFrame};
pub use self::normalize::{Normalize, NormalizeOptions};
pub struct PeepholeOptimizations;
impl<'a> PeepholeOptimizations {
pub fn commutative_pair<'x, A, F, G, RetF: 'x, RetG: 'x>(
pair: (&'x A, &'x A),
check_a: F,
check_b: G,
) -> Option<(RetF, RetG)>
where
F: Fn(&'x A) -> Option<RetF>,
G: Fn(&'x A) -> Option<RetG>,
{
match check_a(pair.0) {
Some(a) => {
if let Some(b) = check_b(pair.1) {
return Some((a, b));
}
}
_ => {
if let Some(a) = check_a(pair.1)
&& let Some(b) = check_b(pair.0)
{
return Some((a, b));
}
}
}
None
}
fn is_declarative_body_statement(stmt: &Statement<'a>) -> bool {
match stmt {
Statement::EmptyStatement(_)
| Statement::ImportDeclaration(_)
| Statement::ExportAllDeclaration(_)
| Statement::ExportNamedDeclaration(_)
| Statement::ExportFromDeclaration(_) => true,
Statement::ExportDeclaration(e) => Self::is_declarative_declaration(&e.declaration),
Statement::ExportDefaultDeclaration(e) => {
matches!(&e.declaration, ExportDefaultDeclarationKind::FunctionDeclaration(_))
}
_ => stmt.as_declaration().is_some_and(Self::is_declarative_declaration),
}
}
fn is_declarative_declaration(decl: &Declaration<'a>) -> bool {
match decl {
Declaration::FunctionDeclaration(_)
| Declaration::TSTypeAliasDeclaration(_)
| Declaration::TSInterfaceDeclaration(_) => true,
Declaration::VariableDeclaration(decl) => {
Self::is_declarative_variable_declaration(decl)
}
_ => false,
}
}
fn is_declarative_variable_declaration(decl: &VariableDeclaration<'a>) -> bool {
decl.declarations.iter().all(Self::is_declarative_variable_declarator)
}
fn is_declarative_variable_declarator(decl: &VariableDeclarator<'a>) -> bool {
matches!(decl.id, BindingPattern::BindingIdentifier(_))
&& decl.init.as_ref().is_none_or(Expression::is_literal)
}
fn mark_current_body_unsafe(ctx: &mut TraverseCtx<'a>) {
let current_scope_id = ctx.current_scope_id();
let frame = ctx.state.body_frames.last_mut();
if !frame.hoisted_var_inlining_unsafe && frame.scope_id == current_scope_id {
frame.hoisted_var_inlining_unsafe = true;
}
}
fn unconditional_super_call_end(expr: &Expression<'a>) -> Option<u32> {
match expr {
Expression::CallExpression(call) if call.callee.is_super() => Some(call.span.end),
Expression::SequenceExpression(seq) => {
seq.expressions.iter().find_map(Self::unconditional_super_call_end)
}
_ => None,
}
}
fn expression_contains_super_call(expr: &Expression<'a>) -> bool {
Self::unconditional_super_call_end(expr).is_some()
}
pub fn member_object_may_be_mutated(
assignment_target: &AssignmentTarget<'a>,
ctx: &TraverseCtx<'a>,
) -> bool {
let object = match assignment_target {
AssignmentTarget::ComputedMemberExpression(member_expr) => &member_expr.object,
AssignmentTarget::PrivateFieldExpression(member_expr) => &member_expr.object,
AssignmentTarget::StaticMemberExpression(member_expr) => &member_expr.object,
_ => return false,
};
Self::is_expression_that_reference_may_change(object, ctx)
}
pub fn is_expression_that_reference_may_change(
expr: &Expression<'a>,
ctx: &TraverseCtx<'a>,
) -> bool {
match expr {
Expression::Identifier(id) => {
let symbol_id = ctx.scoping().get_reference(id.reference_id()).symbol_id();
symbol_id.is_none_or(|symbol_id| Self::symbol_value_may_change(symbol_id, ctx))
}
Expression::ThisExpression(_) => false,
_ => true,
}
}
fn symbol_value_may_change(symbol_id: SymbolId, ctx: &TraverseCtx<'a>) -> bool {
let scoping = ctx.scoping();
if scoping.symbol_flags(symbol_id).is_import()
|| (ctx.source_type().is_script()
&& scoping.symbol_scope_id(symbol_id) == scoping.root_scope_id())
{
return true;
}
if let Some(sv) = ctx.state.symbols.value(symbol_id) {
sv.references.has_writes()
} else {
scoping.symbol_is_mutated(symbol_id)
}
}
fn is_closed_over_block_scoped_read(symbol_id: SymbolId, ctx: &TraverseCtx<'a>) -> bool {
let scoping = ctx.scoping();
if !scoping.symbol_flags(symbol_id).is_block_scoped() {
return false;
}
let binding_scope = scoping.symbol_scope_id(symbol_id);
Self::read_crosses_function_boundary(ctx.current_scope_id(), binding_scope, ctx)
}
fn identifier_read_blocks_reorder(id: &IdentifierReference<'a>, ctx: &TraverseCtx<'a>) -> bool {
let symbol_id = ctx.scoping().get_reference(id.reference_id()).symbol_id();
symbol_id.is_none_or(|symbol_id| {
Self::symbol_value_may_change(symbol_id, ctx)
|| Self::is_closed_over_block_scoped_read(symbol_id, ctx)
})
}
fn member_part_blocks_reorder(expr: &Expression<'a>, ctx: &TraverseCtx<'a>) -> bool {
match expr {
Expression::Identifier(id) => Self::identifier_read_blocks_reorder(id, ctx),
Expression::ThisExpression(this_expr) => {
let Some(this_scope) = Self::derived_constructor_this_scope(ctx) else {
return false;
};
let Some(owner_index) =
ctx.state.body_frames.iter().rposition(|frame| frame.scope_id == this_scope)
else {
return true;
};
!ctx.state.body_frames[owner_index..].iter().any(|frame| {
frame
.this_initialized_at
.is_some_and(|initialized_at| this_expr.span.start >= initialized_at)
})
}
_ => true,
}
}
fn computed_key_blocks_reorder(key: &Expression<'a>, ctx: &TraverseCtx<'a>) -> bool {
!key.is_literal_value(false, ctx) && Self::member_part_blocks_reorder(key, ctx)
}
fn read_crosses_function_boundary(
read_scope: ScopeId,
stop_scope: ScopeId,
ctx: &TraverseCtx<'a>,
) -> bool {
let scoping = ctx.scoping();
scoping
.scope_ancestors(read_scope)
.take_while(|&scope_id| scope_id != stop_scope)
.any(|scope_id| scoping.scope_flags(scope_id).is_function())
}
}
impl<'a> Traverse<'a> for PeepholeOptimizations {
fn enter_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
let module_has_loaders = program
.body
.iter()
.any(|s| s.as_module_declaration().is_some_and(|m| m.source().is_some()));
*ctx.state.body_frames.last_mut() = BodyFrame {
scope_id: ctx.scoping().root_scope_id(),
hoisted_var_inlining_unsafe: module_has_loaders,
this_initialized_at: None,
};
}
fn enter_function_body(&mut self, body: &mut FunctionBody<'a>, ctx: &mut TraverseCtx<'a>) {
if matches!(ctx.parent(), Ancestor::ArrowFunctionExpressionBody(_)) {
return;
}
let initialized_at = if Self::derived_constructor_this_scope(ctx).is_some() {
body.statements.iter().find_map(|stmt| match stmt {
Statement::ExpressionStatement(stmt) => {
Self::unconditional_super_call_end(&stmt.expression)
}
_ => None,
})
} else {
None
};
ctx.state.body_frames.push(BodyFrame {
scope_id: ctx.current_scope_id(),
hoisted_var_inlining_unsafe: false,
this_initialized_at: initialized_at,
});
}
fn exit_function_body(&mut self, _body: &mut FunctionBody<'a>, ctx: &mut TraverseCtx<'a>) {
if !matches!(ctx.parent(), Ancestor::ArrowFunctionExpressionBody(_)) {
ctx.state.body_frames.pop();
}
}
fn enter_arrow_function_body(
&mut self,
body: &mut ArrowFunctionBody<'a>,
ctx: &mut TraverseCtx<'a>,
) {
let initialized_at = if Self::derived_constructor_this_scope(ctx).is_some() {
if let Some(body) = body.as_function_body() {
body.statements.iter().find_map(|stmt| match stmt {
Statement::ExpressionStatement(stmt) => {
Self::unconditional_super_call_end(&stmt.expression)
}
_ => None,
})
} else {
body.as_expression().and_then(Self::unconditional_super_call_end)
}
} else {
None
};
ctx.state.body_frames.push(BodyFrame {
scope_id: ctx.current_scope_id(),
hoisted_var_inlining_unsafe: false,
this_initialized_at: initialized_at,
});
}
fn exit_arrow_function_body(
&mut self,
_body: &mut ArrowFunctionBody<'a>,
ctx: &mut TraverseCtx<'a>,
) {
ctx.state.body_frames.pop();
}
fn exit_program(&mut self, _program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
debug_assert!(ctx.is_tree_shake_only() || ctx.state.private_member_usage.is_at_root());
}
fn exit_statements(
&mut self,
stmts: &mut ArenaVec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
Self::minimize_statements(stmts, ctx);
}
fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
Self::keep_track_of_pure_functions(stmt, ctx);
}
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
if ctx.is_tree_shake_only() {
match stmt {
Statement::BlockStatement(_) => Self::try_optimize_block(stmt, ctx),
Statement::IfStatement(_) => Self::try_fold_if(stmt, ctx),
Statement::ForStatement(_) => Self::try_fold_for(stmt, ctx),
Statement::TryStatement(_) => Self::try_fold_try(stmt, ctx),
Statement::LabeledStatement(_) => Self::try_fold_labeled(stmt, ctx),
Statement::FunctionDeclaration(_) => {
Self::remove_unused_function_declaration(stmt, ctx);
}
Statement::ClassDeclaration(_) => {
Self::remove_unused_class_declaration(stmt, ctx);
}
Statement::ExpressionStatement(_) => {
Self::try_fold_expression_stmt(stmt, ctx);
}
Statement::ImportDeclaration(_) => {
Self::remove_unused_import_specifiers(stmt, ctx);
}
_ => {}
}
} else {
match stmt {
Statement::BlockStatement(_) => Self::try_optimize_block(stmt, ctx),
Statement::IfStatement(s) => {
Self::minimize_expression_in_boolean_context(&mut s.test, ctx);
Self::try_fold_if(stmt, ctx);
if let Statement::IfStatement(if_stmt) = stmt
&& let Some(folded_stmt) = Self::try_minimize_if(if_stmt, ctx)
{
ctx.replace_statement(stmt, folded_stmt);
}
}
Statement::WhileStatement(s) => {
Self::minimize_expression_in_boolean_context(&mut s.test, ctx);
}
Statement::ForStatement(s) => {
if let Some(test) = &mut s.test {
Self::minimize_expression_in_boolean_context(test, ctx);
}
Self::try_fold_for(stmt, ctx);
}
Statement::DoWhileStatement(s) => {
Self::minimize_expression_in_boolean_context(&mut s.test, ctx);
}
Statement::TryStatement(_) => Self::try_fold_try(stmt, ctx),
Statement::LabeledStatement(_) => Self::try_fold_labeled(stmt, ctx),
Statement::FunctionDeclaration(f) => {
Self::init_function_declaration_symbol_value(f.id.as_ref(), ctx);
Self::remove_unused_function_declaration(stmt, ctx);
}
Statement::ClassDeclaration(c) => {
Self::init_class_declaration_symbol_value(c, ctx);
Self::remove_unused_class_declaration(stmt, ctx);
}
Statement::ImportDeclaration(_) => Self::remove_unused_import_specifiers(stmt, ctx),
_ => {}
}
Self::try_fold_expression_stmt(stmt, ctx);
}
if !Self::is_declarative_body_statement(stmt) {
Self::mark_current_body_unsafe(ctx);
}
}
fn exit_for_statement(&mut self, stmt: &mut ForStatement<'a>, ctx: &mut TraverseCtx<'a>) {
if ctx.is_tree_shake_only() {
return;
}
Self::substitute_for_statement(stmt, ctx);
Self::minimize_for_statement(stmt, ctx);
}
fn exit_return_statement(&mut self, stmt: &mut ReturnStatement<'a>, ctx: &mut TraverseCtx<'a>) {
if ctx.is_tree_shake_only() {
return;
}
Self::substitute_return_statement(stmt, ctx);
}
fn exit_variable_declaration(
&mut self,
decl: &mut VariableDeclaration<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if ctx.is_tree_shake_only() {
return;
}
Self::substitute_variable_declaration(decl, ctx);
}
fn exit_variable_declarator(
&mut self,
decl: &mut VariableDeclarator<'a>,
ctx: &mut TraverseCtx<'a>,
) {
Self::init_symbol_value(decl, ctx);
if !Self::is_declarative_variable_declarator(decl) {
Self::mark_current_body_unsafe(ctx);
}
}
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
if ctx.is_tree_shake_only() {
match expr {
Expression::TemplateLiteral(t) => {
Self::inline_template_literal(t, ctx);
}
Expression::ObjectExpression(e) => Self::fold_object_exp(e, ctx),
Expression::BinaryExpression(_) => {
Self::fold_binary_expr(expr, ctx);
Self::fold_binary_typeof_comparison(expr, ctx);
}
Expression::UnaryExpression(_) => Self::fold_unary_expr(expr, ctx),
Expression::StaticMemberExpression(_) => {
Self::fold_static_member_expr(expr, ctx);
}
Expression::ComputedMemberExpression(_) => {
Self::fold_computed_member_expr(expr, ctx);
}
Expression::LogicalExpression(_) => Self::fold_logical_expr(expr, ctx),
Expression::ChainExpression(_) => Self::fold_chain_expr(expr, ctx),
Expression::CallExpression(_) => {
Self::fold_call_expression(expr, ctx);
Self::substitute_iife_call(expr, ctx);
Self::remove_dead_code_call_expression(expr, ctx);
}
Expression::ConditionalExpression(_) => {
Self::try_fold_conditional_expression(expr, ctx);
}
Expression::SequenceExpression(_) => {
Self::remove_sequence_expression(expr, ctx);
}
Expression::AssignmentExpression(_) => {
Self::remove_unused_assignment_expr(expr, ctx);
}
_ => {}
}
} else {
match expr {
Expression::TemplateLiteral(t) => {
Self::inline_template_literal(t, ctx);
Self::substitute_template_literal(expr, ctx);
}
Expression::ObjectExpression(e) => Self::fold_object_exp(e, ctx),
Expression::BinaryExpression(e) => {
Self::substitute_swap_binary_expressions(e);
Self::fold_binary_expr(expr, ctx);
Self::fold_binary_typeof_comparison(expr, ctx);
Self::fold_sequence_expression(expr, ctx);
Self::minimize_loose_boolean(expr, ctx);
Self::minimize_binary(expr, ctx);
Self::substitute_loose_equals_undefined(expr, ctx);
Self::substitute_typeof_undefined(expr, ctx);
Self::substitute_rotate_binary_expression(expr, ctx);
}
Expression::UnaryExpression(_) => {
Self::fold_unary_expr(expr, ctx);
Self::minimize_unary(expr, ctx);
Self::substitute_unary_plus(expr, ctx);
Self::fold_sequence_expression(expr, ctx);
}
Expression::YieldExpression(_) | Expression::AwaitExpression(_) => {
Self::fold_sequence_expression(expr, ctx);
}
Expression::StaticMemberExpression(_) => {
Self::fold_static_member_expr(expr, ctx);
Self::replace_known_property_access(expr, ctx);
}
Expression::ComputedMemberExpression(_) => {
Self::fold_computed_member_expr(expr, ctx);
Self::replace_known_property_access(expr, ctx);
}
Expression::LogicalExpression(_) => {
Self::fold_logical_expr(expr, ctx);
Self::fold_sequence_expression(expr, ctx);
Self::minimize_logical_expression(expr, ctx);
Self::substitute_is_object_and_not_null(expr, ctx);
Self::substitute_rotate_logical_expression(expr, ctx);
}
Expression::ChainExpression(_) => {
Self::fold_chain_expr(expr, ctx);
Self::substitute_chain_expression(expr, ctx);
}
Expression::CallExpression(_) => {
Self::fold_call_expression(expr, ctx);
Self::substitute_iife_call(expr, ctx);
Self::remove_dead_code_call_expression(expr, ctx);
Self::replace_concat_chain(expr, ctx);
Self::replace_known_global_methods(expr, ctx);
Self::substitute_simple_function_call(expr, ctx);
Self::substitute_object_or_array_constructor(expr, ctx);
}
Expression::ConditionalExpression(logical_expr) => {
Self::minimize_expression_in_boolean_context(&mut logical_expr.test, ctx);
if let Some(changed) = Self::minimize_conditional_expression(logical_expr, ctx)
{
ctx.replace_expression(expr, changed);
}
Self::try_fold_conditional_expression(expr, ctx);
}
Expression::AssignmentExpression(e) => {
Self::minimize_normal_assignment_to_combined_logical_assignment(e, ctx);
Self::minimize_normal_assignment_to_combined_assignment(e, ctx);
Self::minimize_assignment_to_update_expression(expr, ctx);
Self::remove_unused_assignment_expr(expr, ctx);
}
Expression::SequenceExpression(_) => Self::remove_sequence_expression(expr, ctx),
Expression::ArrowFunctionExpression(e) => Self::substitute_arrow_expression(e, ctx),
Expression::FunctionExpression(e) => Self::try_remove_name_from_functions(e, ctx),
Expression::ClassExpression(e) => Self::try_remove_name_from_classes(e, ctx),
Expression::NewExpression(e) => {
Self::substitute_typed_array_constructor(e, ctx);
Self::substitute_global_new_expression(expr, ctx);
Self::substitute_object_or_array_constructor(expr, ctx);
}
Expression::BooleanLiteral(_) => Self::substitute_boolean(expr, ctx),
Expression::ArrayExpression(_) => {
Self::try_flatten_array_expression_elements(expr, ctx);
Self::substitute_array_expression(expr, ctx);
}
Expression::Identifier(_) => Self::inline_identifier_reference(expr, ctx),
_ => {}
}
}
}
fn exit_unary_expression(&mut self, expr: &mut UnaryExpression<'a>, ctx: &mut TraverseCtx<'a>) {
if ctx.is_tree_shake_only() {
return;
}
if expr.operator.is_not() {
Self::minimize_expression_in_boolean_context(&mut expr.argument, ctx);
}
}
fn exit_call_expression(&mut self, e: &mut CallExpression<'a>, ctx: &mut TraverseCtx<'a>) {
if !ctx.is_tree_shake_only() {
Self::substitute_call_expression(e, ctx);
Self::remove_empty_spread_arguments(&mut e.arguments);
}
Normalize::set_no_side_effects_to_call_expr(e, ctx);
}
fn exit_new_expression(&mut self, e: &mut NewExpression<'a>, ctx: &mut TraverseCtx<'a>) {
if !ctx.is_tree_shake_only() {
Self::substitute_new_expression(e, ctx);
Self::remove_empty_spread_arguments(&mut e.arguments);
}
Normalize::set_pure_or_no_side_effects_to_new_expr(e, ctx);
}
fn exit_object_property(&mut self, prop: &mut ObjectProperty<'a>, ctx: &mut TraverseCtx<'a>) {
if ctx.is_tree_shake_only() {
return;
}
Self::substitute_object_property(prop, ctx);
}
fn exit_assignment_target_property(
&mut self,
node: &mut AssignmentTargetProperty<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if ctx.is_tree_shake_only() {
return;
}
Self::substitute_assignment_target_property(node, ctx);
}
fn exit_assignment_target_property_property(
&mut self,
prop: &mut AssignmentTargetPropertyProperty<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if ctx.is_tree_shake_only() {
return;
}
Self::substitute_assignment_target_property_property(prop, ctx);
}
fn exit_binding_property(&mut self, prop: &mut BindingProperty<'a>, ctx: &mut TraverseCtx<'a>) {
if ctx.is_tree_shake_only() {
return;
}
Self::substitute_binding_property(prop, ctx);
}
fn exit_method_definition(
&mut self,
prop: &mut MethodDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if ctx.is_tree_shake_only() {
return;
}
Self::substitute_method_definition(prop, ctx);
}
fn exit_property_definition(
&mut self,
prop: &mut PropertyDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if ctx.is_tree_shake_only() {
return;
}
Self::substitute_property_definition(prop, ctx);
}
fn exit_accessor_property(
&mut self,
prop: &mut AccessorProperty<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if ctx.is_tree_shake_only() {
return;
}
Self::substitute_accessor_property(prop, ctx);
}
fn exit_member_expression(
&mut self,
expr: &mut MemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if ctx.is_tree_shake_only() {
return;
}
Self::convert_to_dotted_properties(expr, ctx);
}
fn enter_class_body(&mut self, _body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) {
if ctx.is_tree_shake_only() {
return;
}
ctx.state.private_member_usage.enter_class();
}
fn exit_class_body(&mut self, body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) {
if ctx.is_tree_shake_only() {
return;
}
Self::remove_dead_code_exit_class_body(body, ctx);
Self::remove_unused_private_members(body, ctx);
ctx.state.private_member_usage.exit_class(Self::declared_private_member_names(body));
}
fn exit_catch_clause(&mut self, catch: &mut CatchClause<'a>, ctx: &mut TraverseCtx<'a>) {
if ctx.is_tree_shake_only() {
return;
}
Self::substitute_catch_clause(catch, ctx);
}
fn exit_private_field_expression(
&mut self,
node: &mut PrivateFieldExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if ctx.is_tree_shake_only() {
return;
}
ctx.state.private_member_usage.record_use(node.field.name.into());
}
fn exit_private_in_expression(
&mut self,
node: &mut PrivateInExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if ctx.is_tree_shake_only() {
return;
}
ctx.state.private_member_usage.record_use(node.left.name.into());
}
}