From d6d2bcd1c7b2524addab2394c2849ba809006567 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:58:02 +0000 Subject: [PATCH] feat(minifier): remove unused function calls that are marked by `manual_pure_functions` (#16534) Remove unused function calls that are marked as pure by `manual_pure_functions`. --- .../src/peephole/remove_unused_expression.rs | 3 +- .../tests/peephole/manual_pure_functions.rs | 59 ++++++------------- 2 files changed, 19 insertions(+), 43 deletions(-) diff --git a/crates/oxc_minifier/src/peephole/remove_unused_expression.rs b/crates/oxc_minifier/src/peephole/remove_unused_expression.rs index baa741d07865d..bad78dd02bfaf 100644 --- a/crates/oxc_minifier/src/peephole/remove_unused_expression.rs +++ b/crates/oxc_minifier/src/peephole/remove_unused_expression.rs @@ -224,7 +224,7 @@ impl<'a> PeepholeOptimizations { fn remove_unused_new_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool { let Expression::NewExpression(new_expr) = e else { return false }; - if new_expr.pure && ctx.annotations() { + if (new_expr.pure && ctx.annotations()) || ctx.manual_pure_functions(&new_expr.callee) { let mut exprs = Self::fold_arguments_into_needed_expressions(&mut new_expr.arguments, ctx); if exprs.is_empty() { @@ -536,6 +536,7 @@ impl<'a> PeepholeOptimizations { let is_pure = { (call_expr.pure && ctx.annotations()) + || ctx.manual_pure_functions(&call_expr.callee) || (if let Expression::Identifier(id) = &call_expr.callee && let Some(symbol_id) = ctx.scoping().get_reference(id.reference_id()).symbol_id() diff --git a/crates/oxc_minifier/tests/peephole/manual_pure_functions.rs b/crates/oxc_minifier/tests/peephole/manual_pure_functions.rs index 2bc255f2ed2de..6441a153c5e0b 100644 --- a/crates/oxc_minifier/tests/peephole/manual_pure_functions.rs +++ b/crates/oxc_minifier/tests/peephole/manual_pure_functions.rs @@ -21,8 +21,8 @@ mod terser_tests { use super::test; #[test] - #[ignore = "FIXME"] fn array() { + // `a / b` and `c / b` are kept because they might throw error test( " var a; @@ -31,13 +31,12 @@ mod terser_tests { Math.floor(c / b); } ", - "export function f(b) {}", + "var a; export function f(b) { a / b, c / b; }", &["Math.floor"], ); } #[test] - #[ignore = "FIXME"] fn side_effects() { test( " @@ -56,7 +55,6 @@ mod terser_tests { } #[test] - #[ignore = "FIXME"] fn unused() { test( " @@ -70,8 +68,7 @@ mod terser_tests { ", " export function foo() { - side_effects(); - return pure(3); + return side_effects(), pure(3); } ", &["pure"], @@ -98,8 +95,8 @@ mod terser_tests { } #[test] - #[ignore = "FIXME"] fn conditional() { + // Bitwise operators like `2 & b()` are kept because they might throw test( " pure(1 | a() ? 2 & b() : 7 ^ c()); @@ -111,22 +108,14 @@ mod terser_tests { pure(3 ? 4 : 7 ^ c()); pure(3 ? 4 : 5); ", - " - 1 | a() ? b() : c(), - 1 | a() && b(), - 1 | a() || c(), - a(), - 3 ? b() : c(), - 3 && b(), - 3 || c() - ", + "1 | a() ? 2 & b() : 7 ^ c(), 1 | a() && 2 & b(), 1 | a() || 7 ^ c(), 1 | a(), 2 & b(), 2 & b()", &["pure"], ); } #[test] - #[ignore = "FIXME"] fn relational() { + // `in` and `instanceof` operators can throw test( r#" foo() in foo(); @@ -139,20 +128,14 @@ mod terser_tests { "bar" === bar(); "bar" >= "bar"; "#, - " - bar(), - bar(), - bar(), bar(), - bar(), - bar() - ", + "foo() in foo(), foo() instanceof bar(), bar(), bar(), bar(), bar(), bar();", &["foo"], ); } #[test] - #[ignore = "FIXME"] fn arithmetic() { + // Arithmetic/bitwise operators might throw test( r#" foo() + foo(); @@ -165,19 +148,12 @@ mod terser_tests { "bar" << bar(); "bar" >>> "bar"; "#, - " - bar(), - bar(), - bar(), bar(), - bar(), - bar() - ", + "foo() + foo(), foo() - bar(), foo() * 'bar', bar() / foo(), bar() & bar(), bar() | 'bar', 'bar' >> foo(), 'bar' << bar();", &["foo"], ); } #[test] - #[ignore = "FIXME"] fn boolean_and() { // Test logical AND with pure function calls test( @@ -192,13 +168,13 @@ mod terser_tests { "bar" && bar(); "bar" && "bar"; "#, - r#" + " foo() && bar(), bar(), bar() && bar(), bar(), - "bar" && bar() - "#, + bar() + ", &["foo"], ); } @@ -249,8 +225,8 @@ mod terser_tests { } #[test] - #[ignore = "FIXME"] fn unary() { + // ~ is kept because it may throw test( r#" typeof foo(); @@ -284,7 +260,8 @@ mod terser_tests { --a[foo()], --a[bar()], --a.bar, - bar() + ~foo(), + ~bar() ", &["foo"], ); @@ -314,7 +291,6 @@ mod terser_tests { } #[test] - #[ignore = "FIXME"] fn issue_3065_3() { test( r#" @@ -328,7 +304,7 @@ mod terser_tests { "#, r#" (function() { - console.log("PASS"); + return console.log("PASS"), "FAIL"; })(); "#, &["debug"], @@ -336,7 +312,6 @@ mod terser_tests { } #[test] - #[ignore = "FIXME"] fn issue_3065_4() { test( r#" @@ -350,7 +325,7 @@ mod terser_tests { "#, r#" (function() { - console.log("PASS"); + return console.log("PASS"), "FAIL"; })(); "#, &["debug"],