Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 31 additions & 31 deletions crates/oxc_minifier/src/peephole/minimize_conditions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1308,8 +1308,8 @@ mod test {
test("if((-0 != +0) !== false){}", "");
test_same("foo(x >> y == 0)");

test("(x = 1) === 1", "(x = 1) == 1");
test("(x = 1) !== 1", "(x = 1) != 1");
test("v = (x = 1) === 1", "v = (x = 1) == 1");
test("v = (x = 1) !== 1", "v = (x = 1) != 1");
test("v = !0 + null !== 1", "v = !1");
}

Expand Down Expand Up @@ -1360,39 +1360,39 @@ mod test {

#[test]
fn test_fold_is_null_or_undefined() {
test("foo === null || foo === undefined", "foo == null");
test("foo === undefined || foo === null", "foo == null");
test("foo === null || foo === void 0", "foo == null");
test("foo === null || foo === void 0 || foo === 1", "foo == null || foo === 1");
test("foo === 1 || foo === null || foo === void 0", "foo === 1 || foo == null");
test_same("foo === void 0 || bar === null");
test_same("var undefined = 1; foo === null || foo === undefined");
test_same("foo !== 1 && foo === void 0 || foo === null");
test_same("foo.a === void 0 || foo.a === null"); // cannot be folded because accessing foo.a might have a side effect

test("foo !== null && foo !== undefined", "foo != null");
test("foo !== undefined && foo !== null", "foo != null");
test("foo !== null && foo !== void 0", "foo != null");
test("foo !== null && foo !== void 0 && foo !== 1", "foo != null && foo !== 1");
test("foo !== 1 && foo !== null && foo !== void 0", "foo !== 1 && foo != null");
test("foo !== 1 || foo !== void 0 && foo !== null", "foo !== 1 || foo != null");
test_same("foo !== void 0 && bar !== null");

test("(_foo = foo) === null || _foo === undefined", "(_foo = foo) == null");
test("(_foo = foo) === null || _foo === void 0", "(_foo = foo) == null");
test("(_foo = foo.bar) === null || _foo === undefined", "(_foo = foo.bar) == null");
test("(_foo = foo) !== null && _foo !== undefined", "(_foo = foo) != null");
test("(_foo = foo) === undefined || _foo === null", "(_foo = foo) == null");
test("(_foo = foo) === void 0 || _foo === null", "(_foo = foo) == null");
test("v = foo === null || foo === undefined", "v = foo == null");
test("v = foo === undefined || foo === null", "v = foo == null");
test("v = foo === null || foo === void 0", "v = foo == null");
test("v = foo === null || foo === void 0 || foo === 1", "v = foo == null || foo === 1");
test("v = foo === 1 || foo === null || foo === void 0", "v = foo === 1 || foo == null");
test_same("v = foo === void 0 || bar === null");
test_same("var undefined = 1; v = foo === null || foo === undefined");
test_same("v = foo !== 1 && foo === void 0 || foo === null");
test_same("v = foo.a === void 0 || foo.a === null"); // cannot be folded because accessing foo.a might have a side effect

test("v = foo !== null && foo !== undefined", "v = foo != null");
test("v = foo !== undefined && foo !== null", "v = foo != null");
test("v = foo !== null && foo !== void 0", "v = foo != null");
test("v = foo !== null && foo !== void 0 && foo !== 1", "v = foo != null && foo !== 1");
test("v = foo !== 1 && foo !== null && foo !== void 0", "v = foo !== 1 && foo != null");
test("v = foo !== 1 || foo !== void 0 && foo !== null", "v = foo !== 1 || foo != null");
test_same("v = foo !== void 0 && bar !== null");

test("v = (_foo = foo) === null || _foo === undefined", "v = (_foo = foo) == null");
test("v = (_foo = foo) === null || _foo === void 0", "v = (_foo = foo) == null");
test("v = (_foo = foo.bar) === null || _foo === undefined", "v = (_foo = foo.bar) == null");
test("v = (_foo = foo) !== null && _foo !== undefined", "v = (_foo = foo) != null");
test("v = (_foo = foo) === undefined || _foo === null", "v = (_foo = foo) == null");
test("v = (_foo = foo) === void 0 || _foo === null", "v = (_foo = foo) == null");
test(
"(_foo = foo) === null || _foo === void 0 || _foo === 1",
"(_foo = foo) == null || _foo === 1",
"v = (_foo = foo) === null || _foo === void 0 || _foo === 1",
"v = (_foo = foo) == null || _foo === 1",
);
test(
"_foo === 1 || (_foo = foo) === null || _foo === void 0",
"_foo === 1 || (_foo = foo) == null",
"v = _foo === 1 || (_foo = foo) === null || _foo === void 0",
"v = _foo === 1 || (_foo = foo) == null",
);
test_same("(_foo = foo) === void 0 || bar === null");
test_same("v = (_foo = foo) === void 0 || bar === null");
}

#[test]
Expand Down
6 changes: 3 additions & 3 deletions crates/oxc_minifier/src/peephole/minimize_not_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ mod test {

#[test]
fn minimize_nots_with_binary_expressions() {
test("!(x === undefined)", "x !== void 0");
test("!(x === undefined)", "x");
test("!(typeof(x) === 'undefined')", "");
test("!(typeof(x()) === 'undefined')", "x() !== void 0");
test("!(x === void 0)", "x !== void 0");
test("!(typeof(x()) === 'undefined')", "x()");
test("!(x === void 0)", "x");
test("!!delete x.y", "delete x.y");
test("!!!delete x.y", "delete x.y");
test("!!!!delete x.y", "delete x.y");
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_minifier/src/peephole/remove_dead_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ mod test {
test("{if(false)if(false)if(false)foo(); {bar()}}", "bar()");

test("{'hi'}", "");
test("{x==3}", "x == 3");
test("{x==3}", "x");
test("{`hello ${foo}`}", "`${foo}`");
test("{ (function(){x++}) }", "");
test("{ (function foo(){x++; foo()}) }", "");
Expand Down
111 changes: 109 additions & 2 deletions crates/oxc_minifier/src/peephole/remove_unused_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ impl<'a> PeepholeOptimizations {
Expression::TemplateLiteral(_) => self.fold_template_literal(e, ctx),
Expression::ObjectExpression(_) => self.fold_object_expression(e, ctx),
Expression::ConditionalExpression(_) => self.fold_conditional_expression(e, ctx),
Expression::BinaryExpression(_) => self.fold_binary_expression(e, ctx),
// TODO
// Expression::BinaryExpression(_)
// | Expression::CallExpression(_)
// Expression::CallExpression(_)
// | Expression::NewExpression(_) => {
// false
// }
Expand Down Expand Up @@ -347,6 +347,89 @@ impl<'a> PeepholeOptimizations {

false
}

fn fold_binary_expression(&self, e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool {
let Expression::BinaryExpression(binary_expr) = e else {
return false;
};

match binary_expr.operator {
BinaryOperator::Equality
| BinaryOperator::Inequality
| BinaryOperator::StrictEquality
| BinaryOperator::StrictInequality
| BinaryOperator::LessThan
| BinaryOperator::LessEqualThan
| BinaryOperator::GreaterThan
| BinaryOperator::GreaterEqualThan => {
let left = self.remove_unused_expression(&mut binary_expr.left, ctx);
let right = self.remove_unused_expression(&mut binary_expr.right, ctx);
match (left, right) {
(true, true) => true,
(true, false) => {
*e = ctx.ast.move_expression(&mut binary_expr.right);
false
}
(false, true) => {
*e = ctx.ast.move_expression(&mut binary_expr.left);
false
}
(false, false) => {
*e = ctx.ast.expression_sequence(
binary_expr.span,
ctx.ast.vec_from_array([
ctx.ast.move_expression(&mut binary_expr.left),
ctx.ast.move_expression(&mut binary_expr.right),
]),
);
false
}
}
}
BinaryOperator::Addition => {
Self::fold_string_addition_chain(e, ctx);
matches!(e, Expression::StringLiteral(_))
}
_ => !e.may_have_side_effects(&ctx),
}
}

/// returns whether the passed expression is a string
fn fold_string_addition_chain(e: &mut Expression<'a>, ctx: Ctx<'a, '_>) -> bool {
let Expression::BinaryExpression(binary_expr) = e else {
return e.to_primitive(&ctx).is_string() == Some(true);
};
if binary_expr.operator != BinaryOperator::Addition {
return e.to_primitive(&ctx).is_string() == Some(true);
}

let left_is_string = Self::fold_string_addition_chain(&mut binary_expr.left, ctx);
if left_is_string {
if !binary_expr.left.may_have_side_effects(&ctx) {
binary_expr.left =
ctx.ast.expression_string_literal(binary_expr.left.span(), "", None);
}

let right_as_primitive = binary_expr.right.to_primitive(&ctx);
if right_as_primitive.is_symbol() == Some(false)
&& !binary_expr.right.may_have_side_effects(&ctx)
{
*e = ctx.ast.move_expression(&mut binary_expr.left);
return true;
}
return true;
}

let right_as_primitive = binary_expr.right.to_primitive(&ctx);
if right_as_primitive.is_string() == Some(true) {
if !binary_expr.right.may_have_side_effects(&ctx) {
binary_expr.right =
ctx.ast.expression_string_literal(binary_expr.right.span(), "", None);
}
return true;
}
false
}
}

#[cfg(test)]
Expand Down Expand Up @@ -509,4 +592,28 @@ mod test {
test("foo() ? bar() : 2", "!foo() || bar()"); // can be improved to "foo() && bar()"
test_same("foo() ? bar() : baz()");
}

#[test]
fn test_fold_binary_expression() {
test("var a, b; a === b", "var a, b;");
test("var a, b; a() === b", "var a, b; a()");
test("var a, b; a === b()", "var a, b; b()");
test("var a, b; a() === b()", "var a, b; a(), b()");

test("var a, b; a !== b", "var a, b;");
test("var a, b; a == b", "var a, b;");
test("var a, b; a != b", "var a, b;");
test("var a, b; a < b", "var a, b;");
test("var a, b; a > b", "var a, b;");
test("var a, b; a <= b", "var a, b;");
test("var a, b; a >= b", "var a, b;");

test_same("var a, b; a + b");
test("var a, b; 'a' + b", "var a, b; '' + b");
test_same("var a, b; a + '' + b");
test("var a, b, c; 'a' + (b === c)", "var a, b, c;");
test("var a, b; 'a' + +b", "var a, b; '' + +b"); // can be improved to "var a, b; +b"
test_same("var a, b; a + ('' + b)");
test("var a, b, c; a + ('' + (b === c))", "var a, b, c; a + ''");
}
}
38 changes: 19 additions & 19 deletions crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1215,25 +1215,25 @@ mod test {

#[test]
fn test_fold_true_false_comparison() {
test("x == true", "x == !0");
test("x == false", "x == !1");
test("x != true", "x != !0");
test("x < true", "x < !0");
test("x <= true", "x <= !0");
test("x > true", "x > !0");
test("x >= true", "x >= !0");
test("v = x == true", "v = x == !0");
test("v = x == false", "v = x == !1");
test("v = x != true", "v = x != !0");
test("v = x < true", "v = x < !0");
test("v = x <= true", "v = x <= !0");
test("v = x > true", "v = x > !0");
test("v = x >= true", "v = x >= !0");

test("x instanceof true", "x instanceof !0");
test("x + false", "x + !1");
test("v = x instanceof true", "v = x instanceof !0");
test("v = x + false", "v = x + !1");

// Order: should perform the nearest.
test("x == x instanceof false", "x == x instanceof !1");
test("x in x >> true", "x in x >> !0");
test("x == fake(false)", "x == fake(!1)");
test("v = x == x instanceof false", "v = x == x instanceof !1");
test("v = x in x >> true", "v = x in x >> !0");
test("v = x == fake(false)", "v = x == fake(!1)");

// The following should not be folded.
test("x === true", "x === !0");
test("x !== false", "x !== !1");
test("v = x === true", "v = x === !0");
test("v = x !== false", "v = x !== !1");
}

/// Based on https://github.com/terser/terser/blob/58ba5c163fa1684f2a63c7bc19b7ebcf85b74f73/test/compress/assignment.js
Expand Down Expand Up @@ -1622,11 +1622,11 @@ mod test {

#[test]
fn test_fold_loose_equals_undefined() {
test_same("foo != null");
test("foo != undefined", "foo != null");
test("foo != void 0", "foo != null");
test("undefined != foo", "foo != null");
test("void 0 != foo", "foo != null");
test_same("v = foo != null");
test("v = foo != undefined", "v = foo != null");
test("v = foo != void 0", "v = foo != null");
test("v = undefined != foo", "v = foo != null");
test("v = void 0 != foo", "v = foo != null");
}

#[test]
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_minifier/tests/peephole/esbuild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -623,15 +623,15 @@ fn js_parser_test() {
test("a ? b() : b()", "a, b();");
test("a ? b?.() : b?.()", "a, b?.();");
test("a ? b?.[c] : b?.[c]", "a, b?.[c];");
test("a ? b == c : b == c", "a, b == c;");
test("a ? b == c : b == c", "a, b, c;");
test("a ? b.c(d + e[f]) : b.c(d + e[f])", "a, b.c(d + e[f]);");
// test("a ? -b : !b", "a ? -b : b;");
test("a ? b() : b(c)", "a ? b() : b(c);");
test("a ? b(c) : b(d)", "a ? b(c) : b(d);");
test("a ? b?.c : b.c", "a ? b?.c : b.c;");
test("a ? b?.() : b()", "a ? b?.() : b();");
test("a ? b?.[c] : b[c]", "a ? b?.[c] : b[c];");
test("a ? b == c : b != c", "a ? b == c : b != c;");
// test("a ? b == c : b != c", "a ? (b, c) : (b, c);");
test("a ? b.c(d + e[f]) : b.c(d + e[g])", "a ? b.c(d + e[f]) : b.c(d + e[g]);");
test("(a, b) ? c : d", "a, b ? c : d;");
test(
Expand Down
Loading