Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/oxc_minifier/src/peephole/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {

fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
let ctx = &mut Ctx::new(ctx);
Self::keep_track_of_empty_functions(stmt, ctx);
Self::keep_track_of_pure_functions(stmt, ctx);
}

fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
Expand Down
30 changes: 21 additions & 9 deletions crates/oxc_minifier/src/peephole/remove_dead_code.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use oxc_allocator::{TakeIn, Vec};
use oxc_ast::ast::*;
use oxc_ast_visit::Visit;
use oxc_ecmascript::{constant_evaluation::ConstantEvaluation, side_effects::MayHaveSideEffects};
use oxc_ecmascript::{
constant_evaluation::{ConstantEvaluation, ConstantValue},
side_effects::MayHaveSideEffects,
};
use oxc_span::GetSpan;
use oxc_traverse::Ancestor;

Expand Down Expand Up @@ -340,11 +343,11 @@ impl<'a> PeepholeOptimizations {
}
}

pub fn keep_track_of_empty_functions(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
pub fn keep_track_of_pure_functions(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
match stmt {
Statement::FunctionDeclaration(f) => {
if let Some(body) = &f.body {
Self::try_save_empty_function(
Self::try_save_pure_function(
f.id.as_ref(),
&f.params,
body,
Expand All @@ -359,7 +362,7 @@ impl<'a> PeepholeOptimizations {
if let BindingPatternKind::BindingIdentifier(id) = &d.id.kind {
match &d.init {
Some(Expression::ArrowFunctionExpression(a)) => {
Self::try_save_empty_function(
Self::try_save_pure_function(
Some(id),
&a.params,
&a.body,
Expand All @@ -370,7 +373,7 @@ impl<'a> PeepholeOptimizations {
}
Some(Expression::FunctionExpression(f)) => {
if let Some(body) = &f.body {
Self::try_save_empty_function(
Self::try_save_pure_function(
Some(id),
&f.params,
body,
Expand All @@ -389,24 +392,30 @@ impl<'a> PeepholeOptimizations {
}
}

fn try_save_empty_function(
fn try_save_pure_function(
id: Option<&BindingIdentifier<'a>>,
params: &FormalParameters<'a>,
body: &FunctionBody<'a>,
r#async: bool,
generator: bool,
ctx: &mut Ctx<'a, '_>,
) {
if !body.is_empty() || r#async || generator {
if r#async || generator {
return;
}
// `function foo({}) {} foo(null)` is runtime type error.
if !params.items.iter().all(|pat| pat.pattern.kind.is_binding_identifier()) {
return;
}
if body.statements.iter().any(|stmt| stmt.may_have_side_effects(ctx)) {
return;
}
let Some(symbol_id) = id.and_then(|id| id.symbol_id.get()) else { return };
if ctx.scoping().get_resolved_references(symbol_id).all(|r| r.flags().is_read_only()) {
ctx.state.empty_functions.insert(symbol_id);
ctx.state.pure_functions.insert(
symbol_id,
if body.is_empty() { Some(ConstantValue::Undefined) } else { None },
);
}
}

Expand All @@ -415,7 +424,10 @@ impl<'a> PeepholeOptimizations {
if let Expression::Identifier(ident) = &e.callee {
if let Some(reference_id) = ident.reference_id.get() {
if let Some(symbol_id) = ctx.scoping().get_reference(reference_id).symbol_id() {
if ctx.state.empty_functions.contains(&symbol_id) {
if matches!(
ctx.state.pure_functions.get(&symbol_id),
Some(Some(ConstantValue::Undefined))
) {
let mut exprs =
Self::fold_arguments_into_needed_expressions(&mut e.arguments, ctx);
if exprs.is_empty() {
Expand Down
17 changes: 16 additions & 1 deletion crates/oxc_minifier/src/peephole/remove_unused_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,19 @@ impl<'a> PeepholeOptimizations {
fn remove_unused_call_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
let Expression::CallExpression(call_expr) = e else { return false };

if call_expr.pure && ctx.annotations() {
let is_pure = {
(call_expr.pure && ctx.annotations())
|| (if let Expression::Identifier(id) = &call_expr.callee
&& let Some(symbol_id) =
ctx.scoping().get_reference(id.reference_id()).symbol_id()
{
ctx.state.pure_functions.contains_key(&symbol_id)
} else {
false
})
};

if is_pure {
let mut exprs =
Self::fold_arguments_into_needed_expressions(&mut call_expr.arguments, ctx);
if exprs.is_empty() {
Expand Down Expand Up @@ -954,6 +966,9 @@ mod test {
test("/* @__PURE__ */ new Foo(a)", "a");
test("true && /* @__PURE__ */ noEffect()", "");
test("false || /* @__PURE__ */ noEffect()", "");

test("var foo = () => 1; foo(), foo()", "var foo = () => 1");
test_same("var foo = () => { bar() }; foo(), foo()");
}

#[test]
Expand Down
9 changes: 5 additions & 4 deletions crates/oxc_minifier/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use rustc_hash::FxHashSet;
use oxc_ecmascript::constant_evaluation::ConstantValue;
use rustc_hash::FxHashMap;

use oxc_span::SourceType;
use oxc_syntax::symbol::SymbolId;
Expand All @@ -10,8 +11,8 @@ pub struct MinifierState<'a> {

pub options: CompressOptions,

/// Function declarations that are empty
pub empty_functions: FxHashSet<SymbolId>,
/// The return value of function declarations that are pure
pub pure_functions: FxHashMap<SymbolId, Option<ConstantValue<'a>>>,

pub symbol_values: SymbolValues<'a>,

Expand All @@ -23,7 +24,7 @@ impl MinifierState<'_> {
Self {
source_type,
options,
empty_functions: FxHashSet::default(),
pure_functions: FxHashMap::default(),
symbol_values: SymbolValues::default(),
changed: false,
}
Expand Down
Loading