1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use oxc_allocator::TakeIn;
use oxc_ast::ast::*;
use oxc_ecmascript::constant_evaluation::{ConstantEvaluation, IsInt32OrUint32};
use oxc_span::GetSpan;
use crate::TraverseCtx;
use super::PeepholeOptimizations;
impl<'a> PeepholeOptimizations {
/// Simplify syntax when we know it's used inside a boolean context, e.g. `if (boolean_context) {}`.
///
/// `SimplifyBooleanExpr`: <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L2059>
pub fn minimize_expression_in_boolean_context(
expr: &mut Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
match expr {
// "!!a" => "a"
Expression::UnaryExpression(u1) if u1.operator.is_not() => {
if let Expression::UnaryExpression(u2) = &mut u1.argument
&& u2.operator.is_not()
{
let mut e = u2.argument.take_in(ctx);
Self::minimize_expression_in_boolean_context(&mut e, ctx);
ctx.replace_expression(expr, e);
}
}
Expression::BinaryExpression(e)
if e.operator.is_equality()
&& e.right.is_number_0()
&& e.left.is_int32_or_uint32(ctx) =>
{
let argument = e.left.take_in(ctx);
let new_expr = if matches!(
e.operator,
BinaryOperator::StrictInequality | BinaryOperator::Inequality
) {
// `if ((a | b) !== 0)` -> `if (a | b);`
argument
} else {
// `if ((a | b) === 0);", "if (!(a | b));")`
Expression::new_unary_expression(
e.span,
UnaryOperator::LogicalNot,
argument,
ctx,
)
};
ctx.replace_expression(expr, new_expr);
}
// "if (!!a && !!b)" => "if (a && b)"
Expression::LogicalExpression(e) if e.operator.is_and() => {
Self::minimize_expression_in_boolean_context(&mut e.left, ctx);
Self::minimize_expression_in_boolean_context(&mut e.right, ctx);
// "if (anything && truthyNoSideEffects)" => "if (anything)"
if e.right.get_side_free_boolean_value(ctx) == Some(true) {
let new_expr = e.left.take_in(ctx);
ctx.replace_expression(expr, new_expr);
}
}
// "if (!!a ||!!b)" => "if (a || b)"
Expression::LogicalExpression(e) if e.operator == LogicalOperator::Or => {
Self::minimize_expression_in_boolean_context(&mut e.left, ctx);
Self::minimize_expression_in_boolean_context(&mut e.right, ctx);
// "if (anything || falsyNoSideEffects)" => "if (anything)"
if e.right.get_side_free_boolean_value(ctx) == Some(false) {
let new_expr = e.left.take_in(ctx);
ctx.replace_expression(expr, new_expr);
}
}
Expression::ConditionalExpression(e) => {
// "if (a ? !!b : !!c)" => "if (a ? b : c)"
Self::minimize_expression_in_boolean_context(&mut e.consequent, ctx);
Self::minimize_expression_in_boolean_context(&mut e.alternate, ctx);
if let Some(boolean) = e.consequent.get_side_free_boolean_value(ctx) {
let right = e.alternate.take_in(ctx);
let left = e.test.take_in(ctx);
let span = e.span;
let (op, left) = if boolean {
// "if (anything1 ? truthyNoSideEffects : anything2)" => "if (anything1 || anything2)"
(LogicalOperator::Or, left)
} else {
// "if (anything1 ? falsyNoSideEffects : anything2)" => "if (!anything1 && anything2)"
(LogicalOperator::And, Self::minimize_not(left.span(), left, ctx))
};
let new_expr = Self::join_with_left_associative_op(span, op, left, right, ctx);
ctx.replace_expression(expr, new_expr);
return;
}
if let Some(boolean) = e.alternate.get_side_free_boolean_value(ctx) {
let left = e.test.take_in(ctx);
let right = e.consequent.take_in(ctx);
let span = e.span;
let (op, left) = if boolean {
// "if (anything1 ? anything2 : truthyNoSideEffects)" => "if (!anything1 || anything2)"
(LogicalOperator::Or, Self::minimize_not(left.span(), left, ctx))
} else {
// "if (anything1 ? anything2 : falsyNoSideEffects)" => "if (anything1 && anything2)"
(LogicalOperator::And, left)
};
let new_expr = Self::join_with_left_associative_op(span, op, left, right, ctx);
ctx.replace_expression(expr, new_expr);
}
}
Expression::SequenceExpression(seq_expr) => {
if let Some(last) = seq_expr.expressions.last_mut() {
Self::minimize_expression_in_boolean_context(last, ctx);
}
}
// A binding that is provably falsy in boolean context (a write-once
// falsy `var` whose constant was withheld from value-context folding,
// e.g. a bundled `var hydrating = false` flag) folds to `false` here,
// where `undefined`-before-init is indistinguishable from the falsy
// init. The existing `if (false)` dead-code pass removes the branch.
Expression::Identifier(ident) => {
let reference_id = ident.reference_id();
let span = ident.span;
if let Some(symbol_id) = ctx.scoping().get_reference(reference_id).symbol_id()
&& ctx
.state
.symbol_values
.get_symbol_value(symbol_id)
.is_some_and(|sv| sv.boolean_falsy)
{
let new_expr = Expression::new_boolean_literal(span, false, ctx);
ctx.replace_expression(expr, new_expr);
}
}
_ => {}
}
}
}