diff --git a/crates/oxc_minifier/src/peephole/replace_known_methods.rs b/crates/oxc_minifier/src/peephole/replace_known_methods.rs index 3bb71099f9f16..270a1722f4c8a 100644 --- a/crates/oxc_minifier/src/peephole/replace_known_methods.rs +++ b/crates/oxc_minifier/src/peephole/replace_known_methods.rs @@ -4,8 +4,8 @@ use cow_utils::CowUtils; use oxc_ast::ast::*; use oxc_ecmascript::{ - constant_evaluation::ConstantEvaluation, StringCharAt, StringCharCodeAt, StringIndexOf, - StringLastIndexOf, StringSubstring, ToInt32, + constant_evaluation::{ConstantEvaluation, ValueType}, + StringCharAt, StringCharCodeAt, StringIndexOf, StringLastIndexOf, StringSubstring, ToInt32, }; use oxc_span::SPAN; use oxc_syntax::es_target::ESTarget; @@ -63,6 +63,7 @@ impl<'a> PeepholeOptimizations { } "fromCharCode" => Self::try_fold_string_from_char_code(*span, arguments, object, ctx), "toString" => Self::try_fold_to_string(*span, arguments, object, ctx), + "pow" => self.try_fold_pow(*span, arguments, object, ctx), _ => None, }; if let Some(replacement) = replacement { @@ -342,6 +343,51 @@ impl<'a> PeepholeOptimizations { result.into_iter().rev().collect() } + /// `Math.pow(a, b)` -> `+(a) ** +b` + fn try_fold_pow( + &self, + span: Span, + arguments: &mut Arguments<'a>, + object: &Expression<'a>, + ctx: Ctx<'a, '_>, + ) -> Option> { + if self.target < ESTarget::ES2016 { + return None; + } + + let Expression::Identifier(ident) = object else { return None }; + if ident.name != "Math" || !ctx.is_global_reference(ident) { + return None; + } + if arguments.len() != 2 || arguments.iter().any(|arg| !arg.is_expression()) { + return None; + } + + let mut second_arg = arguments.pop().expect("checked len above"); + let second_arg = second_arg.to_expression_mut(); // checked above + let mut first_arg = arguments.pop().expect("checked len above"); + let first_arg = first_arg.to_expression_mut(); // checked above + + let wrap_with_unary_plus_if_needed = |expr: &mut Expression<'a>| { + if ValueType::from(&*expr).is_number() { + ctx.ast.move_expression(expr) + } else { + ctx.ast.expression_unary( + SPAN, + UnaryOperator::UnaryPlus, + ctx.ast.move_expression(expr), + ) + } + }; + + Some(ctx.ast.expression_binary( + span, + wrap_with_unary_plus_if_needed(first_arg), + BinaryOperator::Exponential, + wrap_with_unary_plus_if_needed(second_arg), + )) + } + /// `[].concat(a).concat(b)` -> `[].concat(a, b)` /// `"".concat(a).concat(b)` -> `"".concat(a, b)` fn try_fold_concat_chain(&mut self, node: &mut Expression<'a>, ctx: Ctx<'a, '_>) { @@ -1470,6 +1516,23 @@ mod test { test("/./.toString(b)", "/./.toString(b)"); } + #[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] fn test_number_constants() { test("v = Number.POSITIVE_INFINITY", "v = Infinity"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index b2756462d9b25..ac6019ca73df8 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.61 kB | 23.70 kB | 8.55 kB | 8.54 kB | react.development.js -173.90 kB | 59.71 kB | 59.82 kB | 19.26 kB | 19.33 kB | moment.js +173.90 kB | 59.70 kB | 59.82 kB | 19.26 kB | 19.33 kB | moment.js 287.63 kB | 89.58 kB | 90.07 kB | 31.08 kB | 31.95 kB | jquery.js @@ -11,17 +11,17 @@ Original | minified | minified | gzip | gzip | Fixture 544.10 kB | 71.50 kB | 72.48 kB | 25.92 kB | 26.20 kB | lodash.js -555.77 kB | 272.35 kB | 270.13 kB | 88.60 kB | 90.80 kB | d3.js +555.77 kB | 272.12 kB | 270.13 kB | 88.59 kB | 90.80 kB | d3.js 1.01 MB | 458.28 kB | 458.89 kB | 123.94 kB | 126.71 kB | bundle.min.js -1.25 MB | 650.82 kB | 646.76 kB | 161.51 kB | 163.73 kB | three.js +1.25 MB | 650.70 kB | 646.76 kB | 161.49 kB | 163.73 kB | three.js -2.14 MB | 719.54 kB | 724.14 kB | 162.47 kB | 181.07 kB | victory.js +2.14 MB | 719.06 kB | 724.14 kB | 162.43 kB | 181.07 kB | victory.js -3.20 MB | 1.01 MB | 1.01 MB | 325.41 kB | 331.56 kB | echarts.js +3.20 MB | 1.01 MB | 1.01 MB | 325.38 kB | 331.56 kB | echarts.js 6.69 MB | 2.30 MB | 2.31 MB | 470.00 kB | 488.28 kB | antd.js -10.95 MB | 3.37 MB | 3.49 MB | 866.65 kB | 915.50 kB | typescript.js +10.95 MB | 3.37 MB | 3.49 MB | 866.66 kB | 915.50 kB | typescript.js