Skip to content

Commit

Permalink
feat(minifier): compress a = a + b to a += b
Browse files Browse the repository at this point in the history
  • Loading branch information
sapphi-red authored and Boshen committed Jan 7, 2025
1 parent 9ea4e31 commit e659142
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,19 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
Self::swap_binary_expressions(e);
self.try_compress_type_of_equal_string(e);
}
Expression::AssignmentExpression(e) => {
self.try_compress_normal_assignment_to_combined_assignment(e, ctx);
}
_ => {}
}

// Fold
if let Some(folded_expr) = match expr {
Expression::Identifier(ident) => self.try_compress_undefined(ident, ctx),
Expression::BooleanLiteral(_) => self.try_compress_boolean(expr, ctx),
Expression::AssignmentExpression(e) => Self::try_compress_assignment_expression(e, ctx),
Expression::AssignmentExpression(e) => {
Self::try_compress_assignment_to_update_expression(e, ctx)
}
Expression::LogicalExpression(e) => Self::try_compress_is_null_or_undefined(e, ctx),
Expression::NewExpression(e) => Self::try_fold_new_expression(e, ctx),
Expression::TemplateLiteral(t) => Self::try_fold_template_literal(t, ctx),
Expand Down Expand Up @@ -525,7 +530,32 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
}
}

fn try_compress_assignment_expression(
/// Compress `a = a + b` to `a += b`
fn try_compress_normal_assignment_to_combined_assignment(
&mut self,
expr: &mut AssignmentExpression<'a>,
ctx: Ctx<'a, 'b>,
) {
if !matches!(expr.operator, AssignmentOperator::Assign) {
return;
}
let AssignmentTarget::AssignmentTargetIdentifier(write_id_ref) = &mut expr.left else {
return;
};

let Expression::BinaryExpression(binary_expr) = &mut expr.right else { return };
let Some(new_op) = binary_expr.operator.to_assignment_operator() else { return };
let Expression::Identifier(read_id_ref) = &mut binary_expr.left else { return };
if write_id_ref.name != read_id_ref.name {
return;
}

expr.operator = new_op;
expr.right = ctx.ast.move_expression(&mut binary_expr.right);
self.changed = true;
}

fn try_compress_assignment_to_update_expression(
expr: &mut AssignmentExpression<'a>,
ctx: Ctx<'a, 'b>,
) -> Option<Expression<'a>> {
Expand Down Expand Up @@ -1020,6 +1050,58 @@ mod test {
test("x !== false", "x !== !1");
}

/// Based on https://github.com/terser/terser/blob/58ba5c163fa1684f2a63c7bc19b7ebcf85b74f73/test/compress/assignment.js
#[test]
fn test_fold_normal_assignment_to_combined_assignment() {
test("x = x + 3", "x += 3");
test("x = x - 3", "x -= 3");
test("x = x / 3", "x /= 3");
test("x = x * 3", "x *= 3");
test("x = x >> 3", "x >>= 3");
test("x = x << 3", "x <<= 3");
test("x = x >>> 3", "x >>>= 3");
test("x = x | 3", "x |= 3");
test("x = x ^ 3", "x ^= 3");
test("x = x % 3", "x %= 3");
test("x = x & 3", "x &= 3");
test("x = x + g()", "x += g()");
test("x = x - g()", "x -= g()");
test("x = x / g()", "x /= g()");
test("x = x * g()", "x *= g()");
test("x = x >> g()", "x >>= g()");
test("x = x << g()", "x <<= g()");
test("x = x >>> g()", "x >>>= g()");
test("x = x | g()", "x |= g()");
test("x = x ^ g()", "x ^= g()");
test("x = x % g()", "x %= g()");
test("x = x & g()", "x &= g()");

test_same("x = 3 + x");
test_same("x = 3 - x");
test_same("x = 3 / x");
test_same("x = 3 * x");
test_same("x = 3 >> x");
test_same("x = 3 << x");
test_same("x = 3 >>> x");
test_same("x = 3 | x");
test_same("x = 3 ^ x");
test_same("x = 3 % x");
test_same("x = 3 & x");
test_same("x = g() + x");
test_same("x = g() - x");
test_same("x = g() / x");
test_same("x = g() * x");
test_same("x = g() >> x");
test_same("x = g() << x");
test_same("x = g() >>> x");
test_same("x = g() | x");
test_same("x = g() ^ x");
test_same("x = g() % x");
test_same("x = g() & x");

test_same("x = (x -= 2) ^ x");
}

#[test]
fn test_fold_subtraction_assignment() {
test("x -= 1", "--x");
Expand Down
19 changes: 19 additions & 0 deletions crates/oxc_syntax/src/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,25 @@ impl BinaryOperator {
}
}

/// Get [`AssignmentOperator`] corresponding to this [`BinaryOperator`].
pub fn to_assignment_operator(self) -> Option<AssignmentOperator> {
match self {
Self::Addition => Some(AssignmentOperator::Addition),
Self::Subtraction => Some(AssignmentOperator::Subtraction),
Self::Multiplication => Some(AssignmentOperator::Multiplication),
Self::Division => Some(AssignmentOperator::Division),
Self::Remainder => Some(AssignmentOperator::Remainder),
Self::Exponential => Some(AssignmentOperator::Exponential),
Self::ShiftLeft => Some(AssignmentOperator::ShiftLeft),
Self::ShiftRight => Some(AssignmentOperator::ShiftRight),
Self::ShiftRightZeroFill => Some(AssignmentOperator::ShiftRightZeroFill),
Self::BitwiseOR => Some(AssignmentOperator::BitwiseOR),
Self::BitwiseXOR => Some(AssignmentOperator::BitwiseXOR),
Self::BitwiseAnd => Some(AssignmentOperator::BitwiseAnd),
_ => None,
}
}

/// The string representation of this operator as it appears in source code.
pub fn as_str(&self) -> &'static str {
match self {
Expand Down
8 changes: 4 additions & 4 deletions tasks/minsize/minsize.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ Original | minified | minified | gzip | gzip | Fixture
-------------------------------------------------------------------------------------
72.14 kB | 23.68 kB | 23.70 kB | 8.61 kB | 8.54 kB | react.development.js

173.90 kB | 59.79 kB | 59.82 kB | 19.42 kB | 19.33 kB | moment.js
173.90 kB | 59.78 kB | 59.82 kB | 19.42 kB | 19.33 kB | moment.js

287.63 kB | 90.06 kB | 90.07 kB | 32.07 kB | 31.95 kB | jquery.js
287.63 kB | 90.05 kB | 90.07 kB | 32.07 kB | 31.95 kB | jquery.js

342.15 kB | 118.16 kB | 118.14 kB | 44.52 kB | 44.37 kB | vue.js

Expand All @@ -19,9 +19,9 @@ Original | minified | minified | gzip | gzip | Fixture

2.14 MB | 726.02 kB | 724.14 kB | 180.14 kB | 181.07 kB | victory.js

3.20 MB | 1.01 MB | 1.01 MB | 331.84 kB | 331.56 kB | echarts.js
3.20 MB | 1.01 MB | 1.01 MB | 331.82 kB | 331.56 kB | echarts.js

6.69 MB | 2.32 MB | 2.31 MB | 492.75 kB | 488.28 kB | antd.js

10.95 MB | 3.50 MB | 3.49 MB | 909.11 kB | 915.50 kB | typescript.js
10.95 MB | 3.50 MB | 3.49 MB | 909.10 kB | 915.50 kB | typescript.js

0 comments on commit e659142

Please sign in to comment.