mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(minifier): compress more property keys (#8253)
This commit is contained in:
parent
d1224f95ce
commit
e84f267a39
4 changed files with 154 additions and 53 deletions
|
|
@ -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)]
|
||||
|
|
@ -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)]
|
||||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
@ -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>,
|
||||
|
|
@ -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(
|
||||
|
|
@ -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");
|
||||
|
|
@ -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})");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -170,10 +170,6 @@ 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>,
|
||||
|
|
@ -181,6 +177,47 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
|
|||
) {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
};
|
||||
|
|
@ -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>,
|
||||
|
|
@ -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>
|
||||
|
|
@ -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' = _ }"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue