diff --git a/crates/oxc_minifier/src/ast_passes/convert_to_dotted_properties.rs b/crates/oxc_minifier/src/ast_passes/convert_to_dotted_properties.rs index 2068c7a148334..14976eeb864d0 100644 --- a/crates/oxc_minifier/src/ast_passes/convert_to_dotted_properties.rs +++ b/crates/oxc_minifier/src/ast_passes/convert_to_dotted_properties.rs @@ -46,15 +46,22 @@ impl<'a> ConvertToDottedProperties { ) { if let MemberExpression::ComputedMemberExpression(e) = expr { let Expression::StringLiteral(s) = &e.expression else { return }; - if !is_identifier_name(&s.value) { + if is_identifier_name(&s.value) { + let property = ctx.ast.identifier_name(s.span, s.value.clone()); + let object = ctx.ast.move_expression(&mut e.object); + *expr = MemberExpression::StaticMemberExpression( + ctx.ast.alloc_static_member_expression(e.span, object, property, e.optional), + ); + self.changed = true; return; } - let property = ctx.ast.identifier_name(s.span, s.value.clone()); - let object = ctx.ast.move_expression(&mut e.object); - *expr = MemberExpression::StaticMemberExpression( - ctx.ast.alloc_static_member_expression(e.span, object, property, e.optional), - ); - self.changed = true; + let v = s.value.as_str(); + if !e.optional { + if let Some(n) = Ctx::string_to_equivalent_number_value(v) { + e.expression = + ctx.ast.expression_numeric_literal(s.span, n, None, NumberBase::Decimal); + } + } } } } @@ -110,7 +117,6 @@ mod test { test_same("a[';']"); test_same("a[':']"); test_same("a['.']"); - test_same("a['0']"); test_same("a['p ']"); test_same("a['p' + '']"); test_same("a[p]"); @@ -282,4 +288,29 @@ mod test { ", ); } + + #[test] + fn test_index() { + test("x['y']", "x.y;"); + test_same("x['y z']"); + test("x?.['y']", "x?.y;"); + test_same("x?.['y z']"); + test("x?.['y']()", "x?.y();"); + test_same("x?.['y z']()"); + test_same("x['y' + 'z']"); + test_same("x?.['y' + 'z']"); + test("x['0']", "x[0];"); + test("x['123']", "x[123];"); + test("x['-123']", "x[-123];"); + test_same("x['-0']"); + test_same("x['+0']"); + test_same("x['01']"); + test_same("x['-01']"); + test_same("x['0x1']"); + test_same("x['-0x1']"); + test("x['2147483647']", "x[2147483647]"); + test_same("x['2147483648']"); + test_same("x['-2147483648']"); + test_same("x['-2147483649']"); + } } diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 2fcd1449a7871..a8394cc4ab98a 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -843,16 +843,14 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { *key = PropertyKey::StaticIdentifier( ctx.ast.alloc_identifier_name(s.span, s.value.clone()), ); - } else if (!s.value.starts_with('0') && !s.value.starts_with('+')) || s.value.len() <= 1 { - if let Ok(value) = s.value.parse::() { - self.changed = true; - *key = PropertyKey::NumericLiteral(ctx.ast.alloc_numeric_literal( - s.span, - value as f64, - None, - NumberBase::Decimal, - )); - } + } else if let Some(value) = Ctx::string_to_equivalent_number_value(s.value.as_str()) { + self.changed = true; + *key = PropertyKey::NumericLiteral(ctx.ast.alloc_numeric_literal( + s.span, + value, + None, + NumberBase::Decimal, + )); } } diff --git a/crates/oxc_minifier/src/node_util/mod.rs b/crates/oxc_minifier/src/node_util/mod.rs index d1fea52b3ff51..8ffedabf05f7f 100644 --- a/crates/oxc_minifier/src/node_util/mod.rs +++ b/crates/oxc_minifier/src/node_util/mod.rs @@ -82,4 +82,34 @@ impl<'a> Ctx<'a, '_> { } false } + + // https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L2641 + pub fn string_to_equivalent_number_value(s: &str) -> Option { + if s.is_empty() { + return None; + } + let mut is_negative = false; + let mut int_value = 0i32; + let mut start = 0; + let bytes = s.as_bytes(); + if bytes[0] == b'-' && s.len() > 1 { + is_negative = true; + start += 1; + } + if bytes[start] == b'0' && s.len() > 1 { + return None; + } + for b in &bytes[start..] { + if b.is_ascii_digit() { + int_value = + int_value.checked_mul(10).and_then(|v| v.checked_add(i32::from(b & 15)))?; + } else { + return None; + } + } + if is_negative { + int_value = -int_value; + } + Some(f64::from(int_value)) + } } diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 975e36aa0a364..f1e5d69314223 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -21,7 +21,7 @@ Original | minified | minified | gzip | gzip | Fixture 3.20 MB | 1.01 MB | 1.01 MB | 331.88 kB | 331.56 kB | echarts.js -6.69 MB | 2.32 MB | 2.31 MB | 492.76 kB | 488.28 kB | antd.js +6.69 MB | 2.32 MB | 2.31 MB | 492.77 kB | 488.28 kB | antd.js 10.95 MB | 3.50 MB | 3.49 MB | 909.12 kB | 915.50 kB | typescript.js