swc_ecma_minifier/compress/pure/
conds.rs1use std::mem::take;
2
3use swc_common::{util::take::Take, EqIgnoreSpan, Spanned, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_utils::{ExprExt, IsEmpty, StmtExt, Type, Value};
6
7use super::{DropOpts, Pure};
8use crate::{compress::util::can_absorb_negate, util::make_bool};
9
10impl Pure<'_> {
11 pub(super) fn merge_nested_if(&mut self, s: &mut IfStmt) {
12 if !self.options.conditionals && !self.options.bools {
13 return;
14 }
15
16 if s.alt.is_some() {
17 return;
18 }
19
20 if let Stmt::If(IfStmt {
21 test,
22 cons,
23 alt: None,
24 ..
25 }) = &mut *s.cons
26 {
27 self.changed = true;
28 report_change!("if_return: Merging nested if statements");
29
30 s.test = BinExpr {
31 span: s.test.span(),
32 op: op!("&&"),
33 left: s.test.take(),
34 right: test.take(),
35 }
36 .into();
37 s.cons = cons.take();
38 }
39 }
40
41 pub(super) fn optimize_const_cond(&mut self, e: &mut Expr) {
42 let Expr::Cond(cond) = e else {
43 return;
44 };
45
46 let (p, Value::Known(v)) = cond.test.cast_to_bool(self.expr_ctx) else {
47 return;
48 };
49
50 if p.is_pure() {
51 if v {
52 self.changed = true;
53 report_change!("conditionals: `true ? foo : bar` => `foo` (pure test)");
54 *e = if cond.cons.directness_matters() {
55 Expr::Seq(SeqExpr {
56 span: cond.span,
57 exprs: vec![0.into(), cond.cons.take()],
58 })
59 } else {
60 *cond.cons.take()
61 };
62 } else {
63 self.changed = true;
64 report_change!("conditionals: `false ? foo : bar` => `bar` (pure test)");
65 *e = if cond.alt.directness_matters() {
66 Expr::Seq(SeqExpr {
67 span: cond.span,
68 exprs: vec![0.into(), cond.alt.take()],
69 })
70 } else {
71 *cond.alt.take()
72 };
73 }
74 } else {
75 self.ignore_return_value(
76 &mut cond.test,
77 DropOpts::DROP_NUMBER.union(DropOpts::DROP_STR_LIT),
78 );
79
80 self.changed = true;
81
82 let mut exprs = Vec::with_capacity(2);
83 if !cond.test.is_invalid() {
84 exprs.push(take(&mut cond.test));
85 }
86
87 if v {
88 report_change!("conditionals: `true ? foo : bar` => `true, foo`");
89 exprs.push(take(&mut cond.cons));
90 } else {
91 report_change!("conditionals: `false ? foo : bar` => `false, bar`");
92 exprs.push(take(&mut cond.alt));
93 }
94
95 *e = *Expr::from_exprs(exprs);
96 }
97 }
98
99 pub(super) fn compress_conds_as_logical(&mut self, e: &mut Expr) {
104 if !self.options.conditionals {
105 return;
106 }
107
108 let Expr::Cond(cond) = e else { return };
109
110 if let Value::Known(Type::Bool) = cond.cons.get_type(self.expr_ctx) {
111 let lb = cond.cons.as_pure_bool(self.expr_ctx);
112 if let Value::Known(true) = lb {
113 report_change!("conditionals: `foo ? true : bar` => `!!foo || bar`");
114
115 self.negate_twice(&mut cond.test, false);
117
118 self.changed = true;
119 *e = BinExpr {
120 span: cond.span,
121 op: op!("||"),
122 left: cond.test.take(),
123 right: cond.alt.take(),
124 }
125 .into();
126 return;
127 }
128
129 if let Value::Known(false) = lb {
131 report_change!("conditionals: `foo ? false : bar` => `!foo && bar`");
132
133 self.changed = true;
134 self.negate(&mut cond.test, false, false);
135
136 *e = BinExpr {
137 span: cond.span,
138 op: op!("&&"),
139 left: cond.test.take(),
140 right: cond.alt.take(),
141 }
142 .into();
143 return;
144 }
145 }
146
147 let rt = cond.alt.get_type(self.expr_ctx);
148 if let Value::Known(Type::Bool) = rt {
149 let rb = cond.alt.as_pure_bool(self.expr_ctx);
150 if let Value::Known(false) = rb {
151 report_change!("conditionals: `foo ? bar : false` => `!!foo && bar`");
152 self.changed = true;
153
154 self.negate_twice(&mut cond.test, false);
156
157 *e = BinExpr {
158 span: cond.span,
159 op: op!("&&"),
160 left: cond.test.take(),
161 right: cond.cons.take(),
162 }
163 .into();
164 return;
165 }
166
167 if let Value::Known(true) = rb {
168 report_change!("conditionals: `foo ? bar : true` => `!foo || bar");
169 self.changed = true;
170
171 self.negate(&mut cond.test, false, false);
172
173 *e = BinExpr {
174 span: cond.span,
175 op: op!("||"),
176 left: cond.test.take(),
177 right: cond.cons.take(),
178 }
179 .into();
180 }
181 }
182 }
183
184 pub(super) fn compress_cond_with_logical_as_logical(&mut self, e: &mut Expr) {
185 if !self.options.conditionals {
186 return;
187 }
188
189 let Expr::Cond(cond) = e else { return };
190
191 match (&mut *cond.cons, &mut *cond.alt) {
192 (Expr::Bin(cons @ BinExpr { op: op!("||"), .. }), alt)
193 if (*cons.right).eq_ignore_span(&*alt) =>
194 {
195 let cons_span = cons.span;
196
197 report_change!("conditionals: `x ? y || z : z` => `x || y && z`");
198 self.changed = true;
199
200 *e = BinExpr {
201 span: cond.span,
202 op: op!("||"),
203 left: BinExpr {
204 span: cons_span,
205 op: op!("&&"),
206 left: cond.test.take(),
207 right: cons.left.take(),
208 }
209 .into(),
210 right: cons.right.take(),
211 }
212 .into();
213 }
214 _ => {}
215 }
216 }
217
218 pub(super) fn compress_conds_as_arithmetic(&mut self, e: &mut Expr) {
222 if !self.options.conditionals {
223 return;
224 }
225
226 let Expr::Cond(cond) = e else { return };
227 let span = cond.span;
228
229 match (&mut *cond.cons, &mut *cond.alt) {
230 (
231 Expr::Lit(Lit::Num(Number { value, .. })),
232 Expr::Lit(Lit::Num(Number { value: 0.0, .. })),
233 ) if *value > 0.0
234 && (!cond.test.is_bin()
235 || cond.test.get_type(self.expr_ctx) == Value::Known(Type::Bool)) =>
236 {
237 report_change!("conditionals: `foo ? num : 0` => `num * !!foo`");
238 self.changed = true;
239
240 let left = cond.cons.take();
241 let mut right = cond.test.take();
242 self.negate_twice(&mut right, false);
243
244 *e = Expr::Bin(BinExpr {
245 span,
246 op: op!("*"),
247 left,
248 right,
249 })
250 }
251 (
252 Expr::Lit(Lit::Num(Number { value: 0.0, .. })),
253 Expr::Lit(Lit::Num(Number { value, .. })),
254 ) if *value > 0.0
255 && (!cond.test.is_bin() || can_absorb_negate(&cond.test, self.expr_ctx)) =>
256 {
257 report_change!("conditionals: `foo ? 0 : num` => `num * !foo`");
258 self.changed = true;
259
260 let left = cond.alt.take();
261 let mut right = cond.test.take();
262 self.negate(&mut right, false, false);
263
264 *e = Expr::Bin(BinExpr {
265 span,
266 op: op!("*"),
267 left,
268 right,
269 })
270 }
271 _ => (),
272 }
273 }
274
275 pub(super) fn drop_logical_operands(&mut self, e: &mut Expr) {
277 if !self.options.conditionals {
278 return;
279 }
280
281 let bin = match e {
282 Expr::Bin(b) => b,
283 _ => return,
284 };
285
286 if bin.op != op!("||") && bin.op != op!("&&") {
287 return;
288 }
289
290 if bin.left.may_have_side_effects(self.expr_ctx) {
291 return;
292 }
293
294 let lt = bin.left.get_type(self.expr_ctx);
295 let rt = bin.right.get_type(self.expr_ctx);
296
297 let _lb = bin.left.as_pure_bool(self.expr_ctx);
298 let rb = bin.right.as_pure_bool(self.expr_ctx);
299
300 if bin.op == op!("||") {
301 if let (Value::Known(Type::Bool), Value::Known(Type::Bool)) = (lt, rt) {
302 if let Value::Known(true) = rb {
304 self.changed = true;
305 report_change!("conditionals: `!!foo || true` => `true`");
306 *e = make_bool(bin.span, true);
307 }
308 }
309 }
310 }
311
312 pub(super) fn optimize_empty_try_stmt(&mut self, s: &mut Stmt) {
313 if !self.options.dead_code {
314 return;
315 }
316
317 let Stmt::Try(ts) = s else {
318 return;
319 };
320
321 if !ts.block.stmts.is_empty() {
322 return;
323 }
324
325 report_change!("conditionals: Optimizing empty try block");
326 self.changed = true;
327
328 let mut vars = None;
329
330 if ts.handler.is_some() {
331 let vec = ts
332 .handler
333 .iter()
334 .flat_map(|c| c.body.stmts.iter())
335 .flat_map(|s| s.extract_var_ids())
336 .map(|i| VarDeclarator {
337 span: DUMMY_SP,
338 name: i.into(),
339 init: None,
340 definite: false,
341 })
342 .collect::<Vec<_>>();
343 if !vec.is_empty() {
344 vars = Some(vec);
345 }
346 }
347
348 *s = ts.finalizer.take().map(Stmt::from).unwrap_or_default();
349
350 if let Some(vars) = vars {
351 *s = Stmt::Block(BlockStmt {
352 stmts: vec![
353 Stmt::Decl(Decl::Var(Box::new(VarDecl {
354 span: DUMMY_SP,
355 ctxt: Default::default(),
356 kind: VarDeclKind::Var,
357 declare: false,
358 decls: vars,
359 }))),
360 take(s),
361 ],
362 ..Default::default()
363 });
364 }
365 }
366
367 pub(super) fn optimize_meaningless_try(&mut self, s: &mut Stmt) {
368 let Stmt::Try(ts) = s else {
369 return;
370 };
371
372 if ts.handler.is_none() && ts.finalizer.is_empty() {
375 report_change!("conditionals: Optimizing meaningless try block");
376 self.changed = true;
377 *s = take(&mut ts.block).into();
378 }
379 }
380}