diff --git a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs index ab47066c7de256..79722b5aedbf97 100644 --- a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs +++ b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, cmp::Ordering}; use num_bigint::BigInt; -use num_traits::Zero; +use num_traits::{ToPrimitive, Zero}; use oxc_ast::ast::*; @@ -193,6 +193,7 @@ pub trait ConstantEvaluation<'a> { Expression::StringLiteral(lit) => { Some(ConstantValue::String(Cow::Borrowed(lit.value.as_str()))) } + Expression::StaticMemberExpression(e) => self.eval_static_member_expression(e), _ => None, } } @@ -458,6 +459,31 @@ pub trait ConstantEvaluation<'a> { } } + fn eval_static_member_expression( + &self, + expr: &StaticMemberExpression<'a>, + ) -> Option> { + match expr.property.name.as_str() { + "length" => { + if let Some(ConstantValue::String(s)) = self.eval_expression(&expr.object) { + // TODO(perf): no need to actually convert, only need the length + Some(ConstantValue::Number(s.encode_utf16().count().to_f64().unwrap())) + } else { + if expr.object.may_have_side_effects() { + return None; + } + + if let Expression::ArrayExpression(arr) = &expr.object { + Some(ConstantValue::Number(arr.elements.len().to_f64().unwrap())) + } else { + None + } + } + } + _ => None, + } + } + /// fn is_less_than( &self, diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index d0e55587a4aa34..60d5eadd18b17e 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -51,7 +51,9 @@ impl<'a> Traverse<'a> for PeepholeFoldConstants { _ => ctx.eval_unary_expression(e).map(|v| ctx.value_to_expr(e.span, v)), } } - // TODO: return tryFoldGetProp(subtree); + Expression::StaticMemberExpression(e) => { + Self::try_fold_static_member_expression(e, ctx) + } Expression::LogicalExpression(e) => Self::try_fold_logical_expression(e, ctx), Expression::ChainExpression(e) => Self::try_fold_optional_chain(e, ctx), // TODO: tryFoldGetElem @@ -97,6 +99,16 @@ impl<'a, 'b> PeepholeFoldConstants { None } + fn try_fold_static_member_expression( + static_member_expr: &mut StaticMemberExpression<'a>, + ctx: Ctx<'a, 'b>, + ) -> Option> { + // TODO: tryFoldObjectPropAccess(n, left, name) + + ctx.eval_static_member_expression(static_member_expr) + .map(|value| ctx.value_to_expr(static_member_expr.span, value)) + } + fn try_fold_logical_expression( logical_expr: &mut LogicalExpression<'a>, ctx: Ctx<'a, 'b>, @@ -1504,6 +1516,31 @@ mod test { test("(+x & 1) & 2", "+x & 0"); } + #[test] + fn test_fold_array_length() { + // Can fold + test("x = [].length", "x = 0"); + test("x = [1,2,3].length", "x = 3"); + // test("x = [a,b].length", "x = 2"); + + // Not handled yet + test("x = [,,1].length", "x = 3"); + + // Cannot fold + test("x = [foo(), 0].length", "x = [foo(),0].length"); + test_same("x = y.length"); + } + + #[test] + fn test_fold_string_length() { + // Can fold basic strings. + test("x = ''.length", "x = 0"); + test("x = '123'.length", "x = 3"); + + // Test Unicode escapes are accounted for. + test("x = '123\\u01dc'.length", "x = 4"); + } + #[test] fn test_fold_instance_of() { // Non object types are never instances of anything. diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 8b34fd75ae1fb6..02cbfa3242035f 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -13,7 +13,7 @@ Original | minified | minified | gzip | gzip | Fixture 555.77 kB | 273.89 kB | 270.13 kB | 91.18 kB | 90.80 kB | d3.js -1.01 MB | 461.09 kB | 458.89 kB | 126.91 kB | 126.71 kB | bundle.min.js +1.01 MB | 461.08 kB | 458.89 kB | 126.90 kB | 126.71 kB | bundle.min.js 1.25 MB | 656.66 kB | 646.76 kB | 164.14 kB | 163.73 kB | three.js @@ -23,5 +23,5 @@ Original | minified | minified | gzip | gzip | Fixture 6.69 MB | 2.36 MB | 2.31 MB | 494.93 kB | 488.28 kB | antd.js -10.95 MB | 3.51 MB | 3.49 MB | 910.92 kB | 915.50 kB | typescript.js +10.95 MB | 3.51 MB | 3.49 MB | 910.86 kB | 915.50 kB | typescript.js