1use std::{cmp::Ordering, f64};
2
3use swc_common::{util::take::Take, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_utils::{number::JsNumber, ExprCtx, ExprExt, IdentUsageFinder, Type, Value};
6use swc_ecma_visit::{
7 noop_visit_mut_type, noop_visit_type, Visit, VisitMut, VisitMutWith, VisitWith,
8};
9
10#[cfg(feature = "debug")]
11use crate::debug::dump;
12use crate::util::ModuleItemExt;
13
14#[cfg(test)]
15mod tests;
16
17pub(super) fn negate(expr_ctx: ExprCtx, e: &mut Expr, in_bool_ctx: bool, is_ret_val_ignored: bool) {
27 negate_inner(expr_ctx, e, in_bool_ctx, is_ret_val_ignored);
28}
29
30fn negate_inner(
31 expr_ctx: ExprCtx,
32 e: &mut Expr,
33 in_bool_ctx: bool,
34 is_ret_val_ignored: bool,
35) -> bool {
36 #[cfg(feature = "debug")]
37 let start_str = dump(&*e, false);
38
39 match e {
40 Expr::Bin(bin @ BinExpr { op: op!("=="), .. })
41 | Expr::Bin(bin @ BinExpr { op: op!("!="), .. })
42 | Expr::Bin(bin @ BinExpr { op: op!("==="), .. })
43 | Expr::Bin(bin @ BinExpr { op: op!("!=="), .. }) => {
44 bin.op = match bin.op {
45 op!("==") => {
46 op!("!=")
47 }
48 op!("!=") => {
49 op!("==")
50 }
51 op!("===") => {
52 op!("!==")
53 }
54 op!("!==") => {
55 op!("===")
56 }
57 _ => {
58 unreachable!()
59 }
60 };
61 report_change!("negate: binary");
62 return true;
63 }
64
65 Expr::Bin(BinExpr {
66 left,
67 right,
68 op: op @ op!("&&"),
69 ..
70 }) if is_ok_to_negate_rhs(expr_ctx, right) => {
71 trace_op!("negate: a && b => !a || !b");
72
73 let a = negate_inner(expr_ctx, left, in_bool_ctx || is_ret_val_ignored, false);
74 let b = negate_inner(expr_ctx, right, in_bool_ctx, is_ret_val_ignored);
75 *op = op!("||");
76 return a || b;
77 }
78
79 Expr::Bin(BinExpr {
80 left,
81 right,
82 op: op @ op!("||"),
83 ..
84 }) if is_ok_to_negate_rhs(expr_ctx, right) => {
85 trace_op!("negate: a || b => !a && !b");
86
87 let a = negate_inner(expr_ctx, left, in_bool_ctx || is_ret_val_ignored, false);
88 let b = negate_inner(expr_ctx, right, in_bool_ctx, is_ret_val_ignored);
89 *op = op!("&&");
90 return a || b;
91 }
92
93 Expr::Cond(CondExpr { cons, alt, .. })
94 if is_ok_to_negate_for_cond(cons) && is_ok_to_negate_for_cond(alt) =>
95 {
96 trace_op!("negate: cond");
97
98 let a = negate_inner(expr_ctx, cons, in_bool_ctx, false);
99 let b = negate_inner(expr_ctx, alt, in_bool_ctx, is_ret_val_ignored);
100 return a || b;
101 }
102
103 Expr::Seq(SeqExpr { exprs, .. }) => {
104 if let Some(last) = exprs.last_mut() {
105 trace_op!("negate: seq");
106
107 return negate_inner(expr_ctx, last, in_bool_ctx, is_ret_val_ignored);
108 }
109 }
110
111 _ => {}
112 }
113
114 let mut arg = Box::new(e.take());
115
116 if let Expr::Unary(UnaryExpr {
117 op: op!("!"), arg, ..
118 }) = &mut *arg
119 {
120 match &mut **arg {
121 Expr::Unary(UnaryExpr { op: op!("!"), .. }) => {
122 report_change!("negate: !!bool => !bool");
123 *e = *arg.take();
124 return true;
125 }
126 Expr::Bin(BinExpr { op: op!("in"), .. })
127 | Expr::Bin(BinExpr {
128 op: op!("instanceof"),
129 ..
130 }) => {
131 report_change!("negate: !bool => bool");
132 *e = *arg.take();
133 return true;
134 }
135 _ => {
136 if in_bool_ctx {
137 report_change!("negate: !expr => expr (in bool context)");
138 *e = *arg.take();
139 return true;
140 }
141
142 if is_ret_val_ignored {
143 report_change!("negate: !expr => expr (return value ignored)");
144 *e = *arg.take();
145 return true;
146 }
147 }
148 }
149 }
150
151 if is_ret_val_ignored {
152 log_abort!("negate: noop because it's ignored");
153 *e = *arg;
154
155 false
156 } else {
157 report_change!("negate: e => !e");
158
159 *e = UnaryExpr {
160 span: DUMMY_SP,
161 op: op!("!"),
162 arg,
163 }
164 .into();
165
166 dump_change_detail!("Negated `{}` as `{}`", start_str, dump(&*e, false));
167
168 true
169 }
170}
171
172pub(crate) fn is_ok_to_negate_for_cond(e: &Expr) -> bool {
173 !matches!(e, Expr::Update(..))
174}
175
176pub(crate) fn is_ok_to_negate_rhs(expr_ctx: ExprCtx, rhs: &Expr) -> bool {
177 match rhs {
178 Expr::Member(..) => true,
179 Expr::Bin(BinExpr {
180 op: op!("===") | op!("!==") | op!("==") | op!("!="),
181 ..
182 }) => true,
183
184 Expr::Call(..) | Expr::New(..) => false,
185
186 Expr::Update(..) => false,
187
188 Expr::Bin(BinExpr {
189 op: op!("&&") | op!("||"),
190 left,
191 right,
192 ..
193 }) => is_ok_to_negate_rhs(expr_ctx, left) && is_ok_to_negate_rhs(expr_ctx, right),
194
195 Expr::Bin(BinExpr { left, right, .. }) => {
196 is_ok_to_negate_rhs(expr_ctx, left) && is_ok_to_negate_rhs(expr_ctx, right)
197 }
198
199 Expr::Assign(e) => is_ok_to_negate_rhs(expr_ctx, &e.right),
200
201 Expr::Unary(UnaryExpr {
202 op: op!("!") | op!("delete"),
203 ..
204 }) => true,
205
206 Expr::Seq(e) => {
207 if let Some(last) = e.exprs.last() {
208 is_ok_to_negate_rhs(expr_ctx, last)
209 } else {
210 true
211 }
212 }
213
214 Expr::Cond(e) => {
215 is_ok_to_negate_rhs(expr_ctx, &e.cons) && is_ok_to_negate_rhs(expr_ctx, &e.alt)
216 }
217
218 _ => {
219 if !rhs.may_have_side_effects(expr_ctx) {
220 return true;
221 }
222
223 #[cfg(feature = "debug")]
224 {
225 tracing::warn!("unimplemented: is_ok_to_negate_rhs: `{}`", dump(rhs, false));
226 }
227
228 false
229 }
230 }
231}
232
233#[cfg_attr(
235 feature = "debug",
236 tracing::instrument(level = "debug", skip(e, expr_ctx))
237)]
238#[allow(clippy::let_and_return)]
239pub(crate) fn negate_cost(
240 expr_ctx: ExprCtx,
241 e: &Expr,
242 in_bool_ctx: bool,
243 is_ret_val_ignored: bool,
244) -> isize {
245 #[allow(clippy::only_used_in_recursion)]
246 #[cfg_attr(test, tracing::instrument(level = "debug", skip(e)))]
247 fn cost(
248 expr_ctx: ExprCtx,
249 e: &Expr,
250 in_bool_ctx: bool,
251 bin_op: Option<BinaryOp>,
252 is_ret_val_ignored: bool,
253 ) -> isize {
254 let cost = (|| {
255 match e {
256 Expr::Unary(UnaryExpr {
257 op: op!("!"), arg, ..
258 }) => {
259 if let Expr::Call(CallExpr {
261 callee: Callee::Expr(callee),
262 ..
263 }) = &**arg
264 {
265 if let Expr::Fn(..) = &**callee {
266 return 0;
267 }
268 }
269
270 match &**arg {
271 Expr::Bin(BinExpr {
272 op: op!("&&") | op!("||"),
273 ..
274 }) => {}
275 _ => {
276 if in_bool_ctx {
277 let c = -cost(expr_ctx, arg, true, None, is_ret_val_ignored);
278 return c.min(-1);
279 }
280 }
281 }
282
283 match &**arg {
284 Expr::Unary(UnaryExpr { op: op!("!"), .. }) => -1,
285
286 _ => {
287 if in_bool_ctx {
288 -1
289 } else {
290 1
291 }
292 }
293 }
294 }
295 Expr::Bin(BinExpr {
296 op: op!("===") | op!("!==") | op!("==") | op!("!="),
297 ..
298 }) => 0,
299
300 Expr::Bin(BinExpr {
301 op: op @ op!("||") | op @ op!("&&"),
302 left,
303 right,
304 ..
305 }) => {
306 let l_cost = cost(
307 expr_ctx,
308 left,
309 in_bool_ctx || is_ret_val_ignored,
310 Some(*op),
311 false,
312 );
313
314 if !is_ret_val_ignored && !is_ok_to_negate_rhs(expr_ctx, right) {
315 return l_cost + 3;
316 }
317 l_cost + cost(expr_ctx, right, in_bool_ctx, Some(*op), is_ret_val_ignored)
318 }
319
320 Expr::Cond(CondExpr { cons, alt, .. })
321 if is_ok_to_negate_for_cond(cons) && is_ok_to_negate_for_cond(alt) =>
322 {
323 cost(expr_ctx, cons, in_bool_ctx, bin_op, is_ret_val_ignored)
324 + cost(expr_ctx, alt, in_bool_ctx, bin_op, is_ret_val_ignored)
325 }
326
327 Expr::Cond(..)
328 | Expr::Update(..)
329 | Expr::Bin(BinExpr {
330 op: op!("in") | op!("instanceof"),
331 ..
332 }) => 3,
333
334 Expr::Assign(..) => {
335 if is_ret_val_ignored {
336 0
337 } else {
338 3
339 }
340 }
341
342 Expr::Seq(e) => {
343 if let Some(last) = e.exprs.last() {
344 return cost(expr_ctx, last, in_bool_ctx, bin_op, is_ret_val_ignored);
345 }
346
347 isize::from(!is_ret_val_ignored)
348 }
349
350 _ => isize::from(!is_ret_val_ignored),
351 }
352 })();
353
354 cost
355 }
356
357 let cost = cost(expr_ctx, e, in_bool_ctx, None, is_ret_val_ignored);
358
359 #[cfg(feature = "debug")]
360 trace_op!("negate_cost of `{}`: {}", dump(e, false), cost);
361
362 cost
363}
364
365pub(crate) fn is_pure_undefined(expr_ctx: ExprCtx, e: &Expr) -> bool {
366 match e {
367 Expr::Unary(UnaryExpr {
368 op: UnaryOp::Void,
369 arg,
370 ..
371 }) if !arg.may_have_side_effects(expr_ctx) => true,
372
373 _ => e.is_undefined(expr_ctx),
374 }
375}
376
377pub(crate) fn is_primitive(expr_ctx: ExprCtx, e: &Expr) -> Option<&Expr> {
378 if is_pure_undefined(expr_ctx, e) {
379 Some(e)
380 } else {
381 match e {
382 Expr::Lit(Lit::Regex(_)) => None,
383 Expr::Lit(_) => Some(e),
384 _ => None,
385 }
386 }
387}
388
389pub(crate) fn is_valid_identifier(s: &str, ascii_only: bool) -> bool {
390 if ascii_only && !s.is_ascii() {
391 return false;
392 }
393 s.starts_with(Ident::is_valid_start)
394 && s.chars().skip(1).all(Ident::is_valid_continue)
395 && !s.is_reserved()
396}
397
398pub(crate) fn is_directive(e: &Stmt) -> bool {
399 match e {
400 Stmt::Expr(s) => match &*s.expr {
401 Expr::Lit(Lit::Str(Str { value, .. })) => value.starts_with("use "),
402 _ => false,
403 },
404 _ => false,
405 }
406}
407
408pub(crate) fn is_pure_undefined_or_null(expr_ctx: ExprCtx, e: &Expr) -> bool {
409 is_pure_undefined(expr_ctx, e) || matches!(e, Expr::Lit(Lit::Null(..)))
410}
411
412pub(crate) fn eval_to_undefined(expr_ctx: ExprCtx, e: &Expr) -> bool {
413 match e {
414 Expr::Unary(UnaryExpr {
415 op: UnaryOp::Void, ..
416 }) => true,
417 Expr::Seq(s) => eval_to_undefined(expr_ctx, s.exprs.last().as_ref().unwrap()),
418 Expr::Cond(c) => {
419 eval_to_undefined(expr_ctx, &c.cons) && eval_to_undefined(expr_ctx, &c.alt)
420 }
421
422 _ => e.is_undefined(expr_ctx),
423 }
424}
425
426pub(crate) fn eval_as_number(expr_ctx: ExprCtx, e: &Expr) -> Option<f64> {
431 match e {
432 Expr::Bin(BinExpr {
433 op: op!(bin, "-"),
434 left,
435 right,
436 ..
437 }) => {
438 let l = eval_as_number(expr_ctx, left)?;
439 let r = eval_as_number(expr_ctx, right)?;
440
441 return Some(l - r);
442 }
443
444 Expr::Call(CallExpr {
445 callee: Callee::Expr(callee),
446 args,
447 ..
448 }) => {
449 for arg in args {
450 if arg.spread.is_some() || arg.expr.may_have_side_effects(expr_ctx) {
451 return None;
452 }
453 }
454
455 if let Expr::Member(MemberExpr {
456 obj,
457 prop: MemberProp::Ident(prop),
458 ..
459 }) = &**callee
460 {
461 match &**obj {
462 Expr::Ident(obj) if &*obj.sym == "Math" => match &*prop.sym {
463 "cos" => {
464 let v = eval_as_number(expr_ctx, &args.first()?.expr)?;
465
466 return Some(v.cos());
467 }
468 "sin" => {
469 let v = eval_as_number(expr_ctx, &args.first()?.expr)?;
470
471 return Some(v.sin());
472 }
473
474 "max" => {
475 let mut numbers = Vec::new();
476 for arg in args {
477 let v = eval_as_number(expr_ctx, &arg.expr)?;
478 if v.is_infinite() || v.is_nan() {
479 return None;
480 }
481 numbers.push(v);
482 }
483
484 return Some(
485 numbers
486 .into_iter()
487 .max_by(|&a, &b| cmp_num(a, b))
488 .unwrap_or(f64::NEG_INFINITY),
489 );
490 }
491
492 "min" => {
493 let mut numbers = Vec::new();
494 for arg in args {
495 let v = eval_as_number(expr_ctx, &arg.expr)?;
496 if v.is_infinite() || v.is_nan() {
497 return None;
498 }
499 numbers.push(v);
500 }
501
502 return Some(
503 numbers
504 .into_iter()
505 .min_by(|&a, &b| cmp_num(a, b))
506 .unwrap_or(f64::INFINITY),
507 );
508 }
509
510 "pow" => {
511 if args.len() != 2 {
512 return None;
513 }
514 let base: JsNumber = eval_as_number(expr_ctx, &args[0].expr)?.into();
515 let exponent: JsNumber =
516 eval_as_number(expr_ctx, &args[1].expr)?.into();
517
518 return Some(base.pow(exponent).into());
519 }
520
521 _ => {}
522 },
523 _ => {}
524 }
525 }
526 }
527
528 Expr::Member(MemberExpr {
529 obj,
530 prop: MemberProp::Ident(prop),
531 ..
532 }) => match &**obj {
533 Expr::Ident(obj) if &*obj.sym == "Math" => match &*prop.sym {
534 "PI" => return Some(f64::consts::PI),
535 "E" => return Some(f64::consts::E),
536 "LN10" => return Some(f64::consts::LN_10),
537 _ => {}
538 },
539 _ => {}
540 },
541
542 _ => {
543 if let Value::Known(v) = e.as_pure_number(expr_ctx) {
544 return Some(v);
545 }
546 }
547 }
548
549 None
550}
551
552pub(crate) fn is_ident_used_by<N>(id: &Ident, node: &N) -> bool
553where
554 N: for<'aa> VisitWith<IdentUsageFinder<'aa>>,
555{
556 IdentUsageFinder::find(id, node)
557}
558
559pub struct ExprReplacer<F>
560where
561 F: FnMut(&mut Expr),
562{
563 op: F,
564}
565
566impl<F> VisitMut for ExprReplacer<F>
567where
568 F: FnMut(&mut Expr),
569{
570 noop_visit_mut_type!(fail);
571
572 fn visit_mut_expr(&mut self, e: &mut Expr) {
573 e.visit_mut_children_with(self);
574
575 (self.op)(e);
576 }
577}
578
579pub fn replace_expr<N, F>(node: &mut N, op: F)
580where
581 N: VisitMutWith<ExprReplacer<F>>,
582 F: FnMut(&mut Expr),
583{
584 node.visit_mut_with(&mut ExprReplacer { op })
585}
586
587pub(super) fn is_fine_for_if_cons(s: &Stmt) -> bool {
588 match s {
589 Stmt::Decl(Decl::Fn(FnDecl {
590 ident: Ident { sym, .. },
591 ..
592 })) if &**sym == "undefined" => false,
593
594 Stmt::Decl(Decl::Var(v))
595 if matches!(
596 &**v,
597 VarDecl {
598 kind: VarDeclKind::Var,
599 ..
600 }
601 ) =>
602 {
603 true
604 }
605 Stmt::Decl(Decl::Fn(..)) => true,
606 Stmt::Decl(..) => false,
607 _ => true,
608 }
609}
610
611pub(super) fn drop_invalid_stmts<T>(stmts: &mut Vec<T>)
612where
613 T: ModuleItemExt,
614{
615 stmts.retain(|s| match s.as_module_decl() {
616 Ok(s) => match s {
617 ModuleDecl::ExportDecl(ExportDecl {
618 decl: Decl::Var(v), ..
619 }) => !v.decls.is_empty(),
620 _ => true,
621 },
622 Err(s) => match s {
623 Stmt::Empty(..) => false,
624 Stmt::Decl(Decl::Var(v)) => !v.decls.is_empty(),
625 _ => true,
626 },
627 });
628}
629
630#[derive(Debug, Default)]
631pub(super) struct UnreachableHandler {
632 vars: Vec<Ident>,
633 in_var_name: bool,
634 in_hoisted_var: bool,
635}
636
637impl UnreachableHandler {
638 pub fn preserve_vars(s: &mut Stmt) -> bool {
643 if s.is_empty() {
644 return false;
645 }
646 if let Stmt::Decl(Decl::Var(v)) = s {
647 let mut changed = false;
648 for decl in &mut v.decls {
649 if decl.init.is_some() {
650 decl.init = None;
651 changed = true;
652 }
653 }
654
655 return changed;
656 }
657
658 let mut v = Self::default();
659 s.visit_mut_with(&mut v);
660 if v.vars.is_empty() {
661 *s = EmptyStmt { span: DUMMY_SP }.into();
662 } else {
663 *s = VarDecl {
664 span: DUMMY_SP,
665 kind: VarDeclKind::Var,
666 declare: false,
667 decls: v
668 .vars
669 .into_iter()
670 .map(BindingIdent::from)
671 .map(Pat::Ident)
672 .map(|name| VarDeclarator {
673 span: DUMMY_SP,
674 name,
675 init: None,
676 definite: false,
677 })
678 .collect(),
679 ..Default::default()
680 }
681 .into()
682 }
683
684 true
685 }
686}
687
688impl VisitMut for UnreachableHandler {
689 noop_visit_mut_type!(fail);
690
691 fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {}
692
693 fn visit_mut_fn_decl(&mut self, n: &mut FnDecl) {
694 self.vars.push(n.ident.clone());
695
696 n.function.visit_mut_with(self);
697 }
698
699 fn visit_mut_function(&mut self, _: &mut Function) {}
700
701 fn visit_mut_pat(&mut self, n: &mut Pat) {
702 n.visit_mut_children_with(self);
703
704 if self.in_var_name && self.in_hoisted_var {
705 if let Pat::Ident(i) = n {
706 self.vars.push(i.id.clone());
707 }
708 }
709 }
710
711 fn visit_mut_var_decl(&mut self, n: &mut VarDecl) {
712 self.in_hoisted_var = n.kind == VarDeclKind::Var;
713 n.visit_mut_children_with(self);
714 }
715
716 fn visit_mut_var_declarator(&mut self, n: &mut VarDeclarator) {
717 self.in_var_name = true;
718 n.name.visit_mut_with(self);
719 self.in_var_name = false;
720 n.init.visit_mut_with(self);
721 }
722}
723
724pub(crate) fn contains_super<N>(body: &N) -> bool
726where
727 N: VisitWith<SuperFinder>,
728{
729 let mut visitor = SuperFinder { found: false };
730 body.visit_with(&mut visitor);
731 visitor.found
732}
733
734pub struct SuperFinder {
735 found: bool,
736}
737
738impl Visit for SuperFinder {
739 noop_visit_type!(fail);
740
741 fn visit_constructor(&mut self, _: &Constructor) {}
743
744 fn visit_function(&mut self, _: &Function) {}
746
747 fn visit_prop(&mut self, n: &Prop) {
748 n.visit_children_with(self);
749
750 if let Prop::Shorthand(Ident { sym, .. }) = n {
751 if &**sym == "arguments" {
752 self.found = true;
753 }
754 }
755 }
756
757 fn visit_super(&mut self, _: &Super) {
758 self.found = true;
759 }
760}
761
762fn cmp_num(a: f64, b: f64) -> Ordering {
763 if a == 0.0 && a.is_sign_negative() && b == 0.0 && b.is_sign_positive() {
764 return Ordering::Less;
765 }
766
767 if a == 0.0 && a.is_sign_positive() && b == 0.0 && b.is_sign_negative() {
768 return Ordering::Greater;
769 }
770
771 a.partial_cmp(&b).unwrap()
772}
773
774pub(crate) fn is_eq(op: BinaryOp) -> bool {
775 matches!(op, op!("==") | op!("===") | op!("!=") | op!("!=="))
776}
777
778pub(crate) fn can_absorb_negate(e: &Expr, expr_ctx: ExprCtx) -> bool {
779 match e {
780 Expr::Lit(_) => true,
781 Expr::Bin(BinExpr {
782 op: op!("&&") | op!("||"),
783 left,
784 right,
785 ..
786 }) => can_absorb_negate(left, expr_ctx) && can_absorb_negate(right, expr_ctx),
787 Expr::Bin(BinExpr { op, .. }) if is_eq(*op) => true,
788 Expr::Unary(UnaryExpr {
789 op: op!("!"), arg, ..
790 }) => arg.get_type(expr_ctx) == Value::Known(Type::Bool),
791 _ => false,
792 }
793}