From 6d0e3550883c290ec3bf17c926f009fa0b4f610b Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Sat, 30 Aug 2025 11:10:43 +0000 Subject: [PATCH] fix(minifier): avoid inlining single use variables when the name needs to be preserved (#13422) Made the single use variable inlining to respect `keep_names` option. refs https://github.com/rolldown/rolldown/pull/5975#discussion_r2311840923 --- crates/oxc_minifier/src/ctx.rs | 13 +++++ .../src/peephole/minimize_statements.rs | 3 ++ .../peephole/inline_single_use_variable.rs | 49 ++++++++++++++++++- 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/crates/oxc_minifier/src/ctx.rs b/crates/oxc_minifier/src/ctx.rs index f14bd841e3980..d8847c17b881c 100644 --- a/crates/oxc_minifier/src/ctx.rs +++ b/crates/oxc_minifier/src/ctx.rs @@ -265,4 +265,17 @@ impl<'a> Ctx<'a, '_> { }) .unwrap_or_default() } + + /// Whether the assignment expression needs to be kept to preserve the name + pub fn is_expression_whose_name_needs_to_be_kept(&self, expr: &Expression) -> bool { + let options = &self.options().keep_names; + if !options.class && !options.function { + return false; + } + if !expr.is_anonymous_function_definition() { + return false; + } + let is_class = matches!(expr.without_parentheses(), Expression::ClassExpression(_)); + (options.class && is_class) || (options.function && !is_class) + } } diff --git a/crates/oxc_minifier/src/peephole/minimize_statements.rs b/crates/oxc_minifier/src/peephole/minimize_statements.rs index 5cfecd4141eed..09a8665f076c3 100644 --- a/crates/oxc_minifier/src/peephole/minimize_statements.rs +++ b/crates/oxc_minifier/src/peephole/minimize_statements.rs @@ -1053,6 +1053,9 @@ impl<'a> PeepholeOptimizations { let BindingPatternKind::BindingIdentifier(prev_decl_id) = &prev_decl.id.kind else { return true; }; + if ctx.is_expression_whose_name_needs_to_be_kept(prev_decl_init) { + return true; + } let Some(symbol_value) = ctx.state.symbol_values.get_symbol_value(prev_decl_id.symbol_id()) else { diff --git a/crates/oxc_minifier/tests/peephole/inline_single_use_variable.rs b/crates/oxc_minifier/tests/peephole/inline_single_use_variable.rs index 7fcb942c9fdc1..e6d6786672383 100644 --- a/crates/oxc_minifier/tests/peephole/inline_single_use_variable.rs +++ b/crates/oxc_minifier/tests/peephole/inline_single_use_variable.rs @@ -1,6 +1,7 @@ +use oxc_minifier::{CompressOptions, CompressOptionsKeepNames}; use oxc_span::SourceType; -use crate::{default_options, test, test_options_source_type, test_same}; +use crate::{default_options, test, test_options, test_options_source_type, test_same}; #[track_caller] fn test_script_same(source_text: &str) { @@ -12,6 +13,15 @@ fn test_script(source_text: &str, expected: &str) { test_options_source_type(source_text, expected, SourceType::cjs(), &default_options()); } +#[track_caller] +fn test_keep_names(source_text: &str, expected: &str) { + test_options( + source_text, + expected, + &CompressOptions { keep_names: CompressOptionsKeepNames::all_true(), ..default_options() }, + ); +} + #[test] fn test_inline_single_use_variable() { test_same("function wrapper(arg0, arg1) {using x = foo; return x}"); @@ -146,6 +156,43 @@ fn keep_exposed_variables() { test_script("{ let x = foo; x() }", "foo()"); } +#[test] +fn keep_names() { + test( + "var x = function() {}; var y = x; console.log(y.name)", + "var y = function() {}; console.log(y.name)", + ); + test_keep_names( + "var x = function() {}; var y = x; console.log(y.name)", + "var x = function() {}, y = x; console.log(y.name)", + ); + test_keep_names( + "var x = (function() {}); var y = x; console.log(y.name)", + "var x = (function() {}), y = x; console.log(y.name)", + ); + test_keep_names( + "var x = function foo() {}; var y = x; console.log(y.name)", + "var y = function foo() {}; console.log(y.name)", + ); + + test( + "var x = class {}; var y = x; console.log(y.name)", + "var y = class {}; console.log(y.name)", + ); + test_keep_names( + "var x = class {}; var y = x; console.log(y.name)", + "var x = class {}, y = x; console.log(y.name)", + ); + test_keep_names( + "var x = (class {}); var y = x; console.log(y.name)", + "var x = (class {}), y = x; console.log(y.name)", + ); + test_keep_names( + "var x = class Foo {}; var y = x; console.log(y.name)", + "var y = class Foo {}; console.log(y.name)", + ); +} + #[test] fn integration() { test(