Skip to content

Commit

Permalink
feat(minifier): compress more property keys (#8253)
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Jan 5, 2025
1 parent d1224f9 commit e84f267
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 53 deletions.
3 changes: 0 additions & 3 deletions crates/oxc_ast/src/ast/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1477,7 +1477,6 @@ pub struct AssignmentPattern<'a> {
pub right: Expression<'a>,
}

// See serializer in serialize.rs
#[ast(visit)]
#[derive(Debug)]
#[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)]
Expand All @@ -1499,7 +1498,6 @@ pub struct BindingProperty<'a> {
pub computed: bool,
}

// See serializer in serialize.rs
#[ast(visit)]
#[derive(Debug)]
#[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)]
Expand Down Expand Up @@ -1644,7 +1642,6 @@ pub enum FunctionType {
}

/// <https://tc39.es/ecma262/#prod-FormalParameters>
// See serializer in serialize.rs
#[ast(visit)]
#[derive(Debug)]
#[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)]
Expand Down
48 changes: 2 additions & 46 deletions crates/oxc_minifier/src/ast_passes/convert_to_dotted_properties.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use oxc_ast::ast::*;
use oxc_syntax::identifier::is_identifier_name;
use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx};
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};

use crate::{node_util::Ctx, CompressorPass};

Expand All @@ -21,12 +21,6 @@ impl<'a> CompressorPass<'a> for ConvertToDottedProperties {
}

impl<'a> Traverse<'a> for ConvertToDottedProperties {
fn exit_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) {
if !self.in_fixed_loop {
self.try_compress_property_key(key, ctx);
}
}

fn exit_member_expression(
&mut self,
expr: &mut MemberExpression<'a>,
Expand All @@ -43,38 +37,6 @@ impl<'a> ConvertToDottedProperties {
Self { changed: false, in_fixed_loop }
}

// https://github.com/swc-project/swc/blob/4e2dae558f60a9f5c6d2eac860743e6c0b2ec562/crates/swc_ecma_minifier/src/compress/pure/properties.rs
#[allow(clippy::cast_lossless)]
fn try_compress_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) {
let PropertyKey::StringLiteral(s) = key else { return };
if match ctx.parent() {
Ancestor::ObjectPropertyKey(key) => *key.computed(),
Ancestor::BindingPropertyKey(key) => *key.computed(),
Ancestor::MethodDefinitionKey(key) => *key.computed(),
Ancestor::PropertyDefinitionKey(key) => *key.computed(),
Ancestor::AccessorPropertyKey(key) => *key.computed(),
_ => true,
} {
return;
}
if is_identifier_name(&s.value) {
self.changed = true;
*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::<u32>() {
self.changed = true;
*key = PropertyKey::NumericLiteral(ctx.ast.alloc_numeric_literal(
s.span,
value as f64,
None,
NumberBase::Decimal,
));
}
}
}

/// `foo['bar']` -> `foo.bar`
/// `foo?.['bar']` -> `foo?.bar`
fn try_compress_computed_member_expression(
Expand Down Expand Up @@ -113,12 +75,6 @@ mod test {
test(source_text, source_text);
}

#[test]
fn test_object_key() {
test("({ '0': _, 'a': _ })", "({ 0: _, a: _ })");
test_same("({ '1.1': _, '😊': _, 'a.a': _ })");
}

#[test]
fn test_computed_to_member_expression() {
test("x['true']", "x.true");
Expand Down Expand Up @@ -175,7 +131,7 @@ mod test {
fn test_convert_to_dotted_properties_quoted_props() {
test_same("({'':0})");
test_same("({'1.0':0})");
test("({'\\u1d17A':0})", "({ \u{1d17}A: 0 })");
test_same("({'\\u1d17A':0})");
test_same("({'a\\u0004b':0})");
}

Expand Down
45 changes: 41 additions & 4 deletions crates/oxc_minifier/src/ast_passes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,17 +170,54 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
self.x6_peephole_substitute_alternate_syntax.exit_call_expression(expr, ctx);
}

fn exit_property_key(&mut self, key: &mut PropertyKey<'a>, ctx: &mut TraverseCtx<'a>) {
self.x9_convert_to_dotted_properties.exit_property_key(key, ctx);
}

fn exit_member_expression(
&mut self,
expr: &mut MemberExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x9_convert_to_dotted_properties.exit_member_expression(expr, ctx);
}

fn exit_object_property(&mut self, prop: &mut ObjectProperty<'a>, ctx: &mut TraverseCtx<'a>) {
self.x6_peephole_substitute_alternate_syntax.exit_object_property(prop, ctx);
}

fn exit_assignment_target_property_property(
&mut self,
prop: &mut AssignmentTargetPropertyProperty<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x6_peephole_substitute_alternate_syntax
.exit_assignment_target_property_property(prop, ctx);
}

fn exit_binding_property(&mut self, prop: &mut BindingProperty<'a>, ctx: &mut TraverseCtx<'a>) {
self.x6_peephole_substitute_alternate_syntax.exit_binding_property(prop, ctx);
}

fn exit_method_definition(
&mut self,
prop: &mut MethodDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x6_peephole_substitute_alternate_syntax.exit_method_definition(prop, ctx);
}

fn exit_property_definition(
&mut self,
prop: &mut PropertyDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x6_peephole_substitute_alternate_syntax.exit_property_definition(prop, ctx);
}

fn exit_accessor_property(
&mut self,
prop: &mut AccessorProperty<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.x6_peephole_substitute_alternate_syntax.exit_accessor_property(prop, ctx);
}
}

pub struct DeadCodeElimination {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use oxc_ecmascript::{constant_evaluation::ConstantEvaluation, ToInt32, ToJsStrin
use oxc_semantic::IsGlobalReference;
use oxc_span::{GetSpan, SPAN};
use oxc_syntax::{
identifier::is_identifier_name,
number::NumberBase,
operator::{BinaryOperator, UnaryOperator},
};
Expand Down Expand Up @@ -35,6 +36,46 @@ impl<'a> CompressorPass<'a> for PeepholeSubstituteAlternateSyntax {
}

impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
fn exit_object_property(&mut self, prop: &mut ObjectProperty<'a>, ctx: &mut TraverseCtx<'a>) {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}

fn exit_assignment_target_property_property(
&mut self,
prop: &mut AssignmentTargetPropertyProperty<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.try_compress_property_key(&mut prop.name, &mut prop.computed, ctx);
}

fn exit_binding_property(&mut self, prop: &mut BindingProperty<'a>, ctx: &mut TraverseCtx<'a>) {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}

fn exit_method_definition(
&mut self,
prop: &mut MethodDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}

fn exit_property_definition(
&mut self,
prop: &mut PropertyDefinition<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}

fn exit_accessor_property(
&mut self,
prop: &mut AccessorProperty<'a>,
ctx: &mut TraverseCtx<'a>,
) {
self.try_compress_property_key(&mut prop.key, &mut prop.computed, ctx);
}

fn exit_return_statement(
&mut self,
stmt: &mut ReturnStatement<'a>,
Expand Down Expand Up @@ -691,6 +732,42 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
fn empty_array_literal(ctx: Ctx<'a, 'b>) -> Expression<'a> {
Self::array_literal(ctx.ast.vec(), ctx)
}

// https://github.com/swc-project/swc/blob/4e2dae558f60a9f5c6d2eac860743e6c0b2ec562/crates/swc_ecma_minifier/src/compress/pure/properties.rs
#[allow(clippy::cast_lossless)]
fn try_compress_property_key(
&mut self,
key: &mut PropertyKey<'a>,
computed: &mut bool,
ctx: &mut TraverseCtx<'a>,
) {
if self.in_fixed_loop {
return;
}
let PropertyKey::StringLiteral(s) = key else { return };
if s.value == "__proto__" || s.value == "constructor" {
return;
}
if *computed {
*computed = false;
}
if is_identifier_name(&s.value) {
self.changed = true;
*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::<u32>() {
self.changed = true;
*key = PropertyKey::NumericLiteral(ctx.ast.alloc_numeric_literal(
s.span,
value as f64,
None,
NumberBase::Decimal,
));
}
}
}
}

/// Port from <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeSubstituteAlternateSyntaxTest.java>
Expand Down Expand Up @@ -1185,4 +1262,38 @@ mod test {
test("typeof foo !== `number`", "typeof foo != 'number'");
test("`number` !== typeof foo", "typeof foo != 'number'");
}

#[test]
fn test_property_key() {
// Object Property
test(
"({ '0': _, 'a': _, ['1']: _, ['b']: _, ['c.c']: _, '1.1': _, '😊': _, 'd.d': _ })",
"({ 0: _, a: _, 1: _, b: _, 'c.c': _, '1.1': _, '😊': _, 'd.d': _ })",
);
// AssignmentTargetPropertyProperty
test(
"({ '0': _, 'a': _, ['1']: _, ['b']: _, ['c.c']: _, '1.1': _, '😊': _, 'd.d': _ } = {})",
"({ 0: _, a: _, 1: _, b: _, 'c.c': _, '1.1': _, '😊': _, 'd.d': _ } = {})",
);
// Binding Property
test(
"var { '0': _, 'a': _, ['1']: _, ['b']: _, ['c.c']: _, '1.1': _, '😊': _, 'd.d': _ } = {}",
"var { 0: _, a: _, 1: _, b: _, 'c.c': _, '1.1': _, '😊': _, 'd.d': _ } = {}",
);
// Method Definition
test(
"class F { '0'(){}; 'a'(){}; ['1'](){}; ['b'](){}; ['c.c'](){}; '1.1'(){}; '😊'(){}; 'd.d'(){} }",
"class F { 0(){}; a(){}; 1(){}; b(){}; 'c.c'(){}; '1.1'(){}; '😊'(){}; 'd.d'(){} }"
);
// Property Definition
test(
"class F { '0' = _; 'a' = _; ['1'] = _; ['b'] = _; ['c.c'] = _; '1.1' = _; '😊' = _; 'd.d' = _ }",
"class F { 0 = _; a = _; 1 = _; b = _; 'c.c' = _; '1.1' = _; '😊' = _; 'd.d' = _ }"
);
// Accessor Property
test(
"class F { accessor '0' = _; accessor 'a' = _; accessor ['1'] = _; accessor ['b'] = _; accessor ['c.c'] = _; accessor '1.1' = _; accessor '😊' = _; accessor 'd.d' = _ }",
"class F { accessor 0 = _; accessor a = _; accessor 1 = _; accessor b = _; accessor 'c.c' = _; accessor '1.1' = _; accessor '😊' = _; accessor 'd.d' = _ }"
);
}
}

0 comments on commit e84f267

Please sign in to comment.