diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 8008387e02344..d3fe1672a9d67 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -1345,7 +1345,7 @@ impl Gen for RegExpLiteral<'_> { fn gen(&self, p: &mut Codegen, _ctx: Context) { p.add_source_mapping(self.span); let last = p.last_byte(); - let pattern_text = self.regex.pattern.source_text(p.source_text); + let pattern_text = self.regex.pattern.to_string(); // Avoid forming a single-line comment or ": MayHaveSideEffects { } None } - _ => None, + BinaryOperator::In + | BinaryOperator::Equality + | BinaryOperator::Inequality + | BinaryOperator::StrictEquality + | BinaryOperator::StrictInequality => None, } } diff --git a/crates/oxc_minifier/src/peephole/fold_constants.rs b/crates/oxc_minifier/src/peephole/fold_constants.rs index f273f50d3e005..5deda13e397f4 100644 --- a/crates/oxc_minifier/src/peephole/fold_constants.rs +++ b/crates/oxc_minifier/src/peephole/fold_constants.rs @@ -1,4 +1,5 @@ use oxc_ast::ast::*; +use oxc_codegen::{Codegen, CodegenOptions}; use oxc_ecmascript::{ constant_evaluation::{ConstantEvaluation, ConstantValue, ValueType}, side_effects::MayHaveSideEffects, @@ -19,8 +20,9 @@ impl<'a> PeepholeOptimizations { /// /// pub fn fold_constants_exit_expression(&mut self, expr: &mut Expression<'a>, ctx: Ctx<'a, '_>) { + let current_len = Self::count_char_when_printing(expr); if let Some(folded_expr) = match expr { - Expression::BinaryExpression(e) => Self::try_fold_binary_expr(e, ctx) + Expression::BinaryExpression(e) => Self::try_fold_binary_expr(e, current_len, ctx) .or_else(|| Self::try_fold_binary_typeof_comparison(e, ctx)), Expression::UnaryExpression(e) => Self::try_fold_unary_expr(e, ctx), Expression::StaticMemberExpression(e) => Self::try_fold_static_member_expr(e, ctx), @@ -202,25 +204,15 @@ impl<'a> PeepholeOptimizations { } } - fn extract_numeric_values(e: &BinaryExpression<'a>) -> Option<(f64, f64)> { - if let (Expression::NumericLiteral(left), Expression::NumericLiteral(right)) = - (&e.left, &e.right) - { - return Some((left.value, right.value)); - } - None - } - - #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)] fn try_fold_binary_expr( e: &mut BinaryExpression<'a>, + current_len: usize, ctx: Ctx<'a, '_>, ) -> Option> { // TODO: tryReduceOperandsForOp // https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L1136 // https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L1222 - let span = e.span; match e.operator { BinaryOperator::Equality | BinaryOperator::Inequality @@ -234,80 +226,19 @@ impl<'a> PeepholeOptimizations { ctx.eval_binary(e).or_else(|| Self::try_fold_left_child_op(e, ctx)) } BinaryOperator::Addition => Self::try_fold_add(e, ctx), - BinaryOperator::Subtraction => { - // Subtraction of small-ish integers can definitely be folded without issues - Self::extract_numeric_values(e) - .filter(|(left, right)| { - left.is_nan() - || left.is_finite() - || right.is_nan() - || right.is_finite() - || (left.fract() == 0.0 - && right.fract() == 0.0 - && (left.abs() as usize) <= 0xFFFF_FFFF - && (right.abs() as usize) <= 0xFFFF_FFFF) - }) - .and_then(|_| ctx.eval_binary(e)) + _ => { + let folded_expr = ctx.eval_binary(e)?; + let folded_len = Self::count_char_when_printing(&folded_expr); + (folded_len <= current_len).then_some(folded_expr) } - BinaryOperator::Multiplication - | BinaryOperator::Exponential - | BinaryOperator::Remainder => Self::extract_numeric_values(e) - .filter(|(left, right)| { - *left == 0.0 - || left.is_nan() - || left.is_infinite() - || *right == 0.0 - || right.is_nan() - || right.is_infinite() - }) - .and_then(|_| ctx.eval_binary(e)), - BinaryOperator::Division => Self::extract_numeric_values(e) - .filter(|(_, right)| *right == 0.0 || right.is_nan() || right.is_infinite()) - .and_then(|_| ctx.eval_binary(e)), - BinaryOperator::ShiftLeft => { - if let Some((left, right)) = Self::extract_numeric_values(e) { - let result = ctx.eval_binary_expression(e)?.into_number()?; - let left_len = Self::approximate_printed_int_char_count(left); - let right_len = Self::approximate_printed_int_char_count(right); - let result_len = Self::approximate_printed_int_char_count(result); - if result_len <= left_len + 2 + right_len { - return Some(ctx.value_to_expr(span, ConstantValue::Number(result))); - } - } - None - } - BinaryOperator::ShiftRightZeroFill => { - if let Some((left, right)) = Self::extract_numeric_values(e) { - let result = ctx.eval_binary_expression(e)?.into_number()?; - let left_len = Self::approximate_printed_int_char_count(left); - let right_len = Self::approximate_printed_int_char_count(right); - let result_len = Self::approximate_printed_int_char_count(result); - if result_len <= left_len + 3 + right_len { - return Some(ctx.value_to_expr(span, ConstantValue::Number(result))); - } - } - None - } - BinaryOperator::ShiftRight | BinaryOperator::Instanceof => ctx.eval_binary(e), - BinaryOperator::In => None, } } - // https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L1128 - #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - #[must_use] - fn approximate_printed_int_char_count(value: f64) -> usize { - let mut count = if value.is_infinite() { - "Infinity".len() - } else if value.is_nan() { - "NaN".len() - } else { - 1 + 0.max(value.abs().log10().floor() as usize) - }; - if value.is_sign_negative() { - count += 1; - } - count + fn count_char_when_printing(expr: &Expression) -> usize { + let mut cg = Codegen::new() + .with_options(CodegenOptions { minify: true, ..CodegenOptions::default() }); + cg.print_expression(expr); + cg.into_source_text().len() } // Simplified version of `tryFoldAdd` from closure compiler. @@ -1614,7 +1545,7 @@ mod test { #[test] fn test_fold_multiply() { - fold_same("x = 2.25 * 3"); + fold("x = 2.25 * 3", "x = 6.75"); fold_same("z = x * y"); fold_same("x = y * 5"); // test("x = null * undefined", "x = NaN"); @@ -1631,28 +1562,28 @@ mod test { fold("x = Infinity / 0", "x = Infinity"); fold("x = 1 / 0", "x = Infinity"); fold("x = 0 / 0", "x = NaN"); - fold_same("x = 2 / 4"); + fold("x = 2 / 4", "x = .5"); fold_same("x = y / 2 / 4"); } #[test] fn test_fold_remainder() { - fold_same("x = 3 % 2"); - fold_same("x = 3 % -2"); - fold_same("x = -1 % 3"); + fold("x = 3 % 2", "x = 1"); + fold("x = 3 % -2", "x = 1"); + fold("x = -1 % 3", "x = -1"); fold("x = 1 % 0", "x = NaN"); fold("x = 0 % 0", "x = NaN"); } #[test] fn test_fold_exponential() { - fold_same("x = 2 ** 3"); - fold_same("x = 2 ** -3"); + fold("x = 2 ** 3", "x = 8"); + fold("x = 2 ** -3", "x = .125"); fold_same("x = 2 ** 55"); fold_same("x = 3 ** -1"); - fold_same("x = (-1) ** 0.5"); + fold("x = (-1) ** 0.5", "x = NaN"); fold("x = (-0) ** 3", "x = -0"); - fold_same("x = null ** 0"); + fold("x = null ** 0", "x = 1"); } #[test] diff --git a/crates/oxc_minifier/src/peephole/replace_known_methods.rs b/crates/oxc_minifier/src/peephole/replace_known_methods.rs index 8473794a9790e..981bb77280a72 100644 --- a/crates/oxc_minifier/src/peephole/replace_known_methods.rs +++ b/crates/oxc_minifier/src/peephole/replace_known_methods.rs @@ -1813,19 +1813,20 @@ mod test { #[test] fn test_fold_pow() { - test("Math.pow(2, 3)", "2 ** 3"); - test("Math.pow(a, 3)", "a ** 3"); - test("Math.pow(2, b)", "2 ** b"); - test("Math.pow(a, b)", "a ** +b"); - test("Math.pow(2n, 3n)", "2n ** +3n"); // errors both before and after - test("Math.pow(a + b, c)", "(a + b) ** +c"); - test_same("Math.pow()"); - test_same("Math.pow(1)"); - test_same("Math.pow(...a, 1)"); - test_same("Math.pow(1, ...a)"); - test_same("Math.pow(1, 2, 3)"); - test_es2015("Math.pow(2, 3)", "Math.pow(2, 3)"); - test_same("Unknown.pow(1, 2)"); + test_value("Math.pow(2, 3)", "8"); + test_value("Math.pow(a, 3)", "a ** 3"); + test_value("Math.pow(2, b)", "2 ** b"); + test_value("Math.pow(a, b)", "a ** +b"); + test_value("Math.pow(2n, 3n)", "2n ** +3n"); // errors both before and after + test_value("Math.pow(a + b, c)", "(a + b) ** +c"); + test_same_value("Math.pow()"); + test_same_value("Math.pow(1)"); + test_same_value("Math.pow(...a, 1)"); + test_same_value("Math.pow(1, ...a)"); + test_same_value("Math.pow(1, 2, 3)"); + test_es2015("x = Math.pow(2, 3)", "x = Math.pow(2, 3)"); // NOTE: can fold to 8 + test_es2015("x = Math.pow(a, 3)", "x = Math.pow(a, 3)"); + test_same_value("Unknown.pow(1, 2)"); } #[test] diff --git a/crates/oxc_minifier/tests/peephole/esbuild.rs b/crates/oxc_minifier/tests/peephole/esbuild.rs index 86bc7b8bde35c..50758965d9535 100644 --- a/crates/oxc_minifier/tests/peephole/esbuild.rs +++ b/crates/oxc_minifier/tests/peephole/esbuild.rs @@ -907,10 +907,10 @@ fn constant_evaluation_test() { test("x = +{valueOf:()=>1}", "x = +{ valueOf: () => 1 };"); test("x = 3 + 6", "x = 9;"); test("x = 3 - 6", "x = -3;"); - test("x = 3 * 6", "x = 3 * 6;"); - test("x = 3 / 6", "x = 3 / 6;"); - test("x = 3 % 6", "x = 3 % 6;"); - test("x = 3 ** 6", "x = 3 ** 6;"); + test("x = 3 * 6", "x = 18;"); + test("x = 3 / 6", "x = .5;"); + test("x = 3 % 6", "x = 3;"); + test("x = 3 ** 6", "x = 729;"); test("x = 0 / 0", "x = NaN;"); test("x = 123 / 0", "x = Infinity;"); test("x = 123 / -0", "x = -Infinity;"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index f7a50e9b26d9b..09f79817caa66 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -3,7 +3,7 @@ Original | minified | minified | gzip | gzip | Fixture ------------------------------------------------------------------------------------- 72.14 kB | 23.49 kB | 23.70 kB | 8.47 kB | 8.54 kB | react.development.js -173.90 kB | 59.53 kB | 59.82 kB | 19.18 kB | 19.33 kB | moment.js +173.90 kB | 59.51 kB | 59.82 kB | 19.18 kB | 19.33 kB | moment.js 287.63 kB | 89.34 kB | 90.07 kB | 30.95 kB | 31.95 kB | jquery.js @@ -11,15 +11,15 @@ Original | minified | minified | gzip | gzip | Fixture 544.10 kB | 71.38 kB | 72.48 kB | 25.85 kB | 26.20 kB | lodash.js -555.77 kB | 270.83 kB | 270.13 kB | 88.26 kB | 90.80 kB | d3.js +555.77 kB | 270.83 kB | 270.13 kB | 88.27 kB | 90.80 kB | d3.js -1.01 MB | 440.17 kB | 458.89 kB | 122.38 kB | 126.71 kB | bundle.min.js +1.01 MB | 440.17 kB | 458.89 kB | 122.37 kB | 126.71 kB | bundle.min.js -1.25 MB | 647.05 kB | 646.76 kB | 160.27 kB | 163.73 kB | three.js +1.25 MB | 647.04 kB | 646.76 kB | 160.27 kB | 163.73 kB | three.js 2.14 MB | 716.14 kB | 724.14 kB | 161.79 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 324.15 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 324.14 kB | 331.56 kB | echarts.js 6.69 MB | 2.28 MB | 2.31 MB | 466.12 kB | 488.28 kB | antd.js