swc_ecma_minifier/util/
mod.rs

1#![allow(dead_code)]
2
3use std::time::Instant;
4
5use rustc_hash::FxHashSet;
6use swc_atoms::Atom;
7use swc_common::{util::take::Take, Span, Spanned, DUMMY_SP};
8use swc_ecma_ast::*;
9use swc_ecma_transforms_base::{fixer::fixer, hygiene::hygiene};
10use swc_ecma_utils::{stack_size::maybe_grow_default, DropSpan, ModuleItemLike, StmtLike, Value};
11use swc_ecma_visit::{noop_visit_type, visit_mut_pass, visit_obj_and_computed, Visit, VisitWith};
12
13pub(crate) mod base54;
14pub(crate) mod size;
15pub(crate) mod sort;
16
17pub(crate) fn make_number(span: Span, value: f64) -> Expr {
18    trace_op!("Creating a numeric literal");
19    Lit::Num(Number {
20        span,
21        value,
22        raw: None,
23    })
24    .into()
25}
26
27pub trait ModuleItemExt:
28    StmtLike + ModuleItemLike + From<Stmt> + Spanned + std::fmt::Debug
29{
30    fn as_module_decl(&self) -> Result<&ModuleDecl, &Stmt>;
31
32    fn from_module_item(item: ModuleItem) -> Self;
33
34    fn into_module_item(self) -> ModuleItem {
35        match self.into_module_decl() {
36            Ok(v) => v.into(),
37            Err(v) => v.into(),
38        }
39    }
40
41    fn into_module_decl(self) -> Result<ModuleDecl, Stmt>;
42}
43
44impl ModuleItemExt for Stmt {
45    fn as_module_decl(&self) -> Result<&ModuleDecl, &Stmt> {
46        Err(self)
47    }
48
49    fn from_module_item(item: ModuleItem) -> Self {
50        item.expect_stmt()
51    }
52
53    fn into_module_decl(self) -> Result<ModuleDecl, Stmt> {
54        Err(self)
55    }
56}
57
58impl ModuleItemExt for ModuleItem {
59    fn as_module_decl(&self) -> Result<&ModuleDecl, &Stmt> {
60        match self {
61            ModuleItem::ModuleDecl(v) => Ok(v),
62            ModuleItem::Stmt(v) => Err(v),
63        }
64    }
65
66    fn from_module_item(item: ModuleItem) -> Self {
67        item
68    }
69
70    fn into_module_decl(self) -> Result<ModuleDecl, Stmt> {
71        match self {
72            ModuleItem::ModuleDecl(v) => Ok(v),
73            ModuleItem::Stmt(v) => Err(v),
74        }
75    }
76}
77
78///
79/// - `!0` for true
80/// - `!1` for false
81pub(crate) fn make_bool(span: Span, value: bool) -> Expr {
82    trace_op!("Creating a boolean literal");
83
84    UnaryExpr {
85        span,
86        op: op!("!"),
87        arg: Lit::Num(Number {
88            span: DUMMY_SP,
89            value: if value { 0.0 } else { 1.0 },
90            raw: None,
91        })
92        .into(),
93    }
94    .into()
95}
96
97/// Additional methods for optimizing expressions.
98pub(crate) trait ExprOptExt: Sized {
99    fn as_expr(&self) -> &Expr;
100    fn as_mut(&mut self) -> &mut Expr;
101
102    fn first_expr_mut(&mut self) -> &mut Expr {
103        let expr = self.as_mut();
104        match expr {
105            Expr::Seq(seq) => seq
106                .exprs
107                .first_mut()
108                .expect("Sequence expressions should have at least one element")
109                .first_expr_mut(),
110            expr => expr,
111        }
112    }
113
114    /// This returns itself for normal expressions and returns last expressions
115    /// for sequence expressions.
116    fn value_mut(&mut self) -> &mut Expr {
117        let expr = self.as_mut();
118        match expr {
119            Expr::Seq(seq) => seq
120                .exprs
121                .last_mut()
122                .expect("Sequence expressions should have at least one element")
123                .value_mut(),
124            expr => expr,
125        }
126    }
127
128    fn force_seq(&mut self) -> &mut SeqExpr {
129        let expr = self.as_mut();
130        match expr {
131            Expr::Seq(seq) => seq,
132            _ => {
133                let inner = expr.take();
134                *expr = SeqExpr {
135                    span: DUMMY_SP,
136                    exprs: vec![Box::new(inner)],
137                }
138                .into();
139                expr.force_seq()
140            }
141        }
142    }
143
144    fn prepend_exprs(&mut self, mut exprs: Vec<Box<Expr>>) {
145        if exprs.is_empty() {
146            return;
147        }
148
149        let to = self.as_mut();
150        match to {
151            Expr::Seq(to) => {
152                exprs.append(&mut to.exprs);
153                to.exprs = exprs;
154            }
155            _ => {
156                let v = to.take();
157                exprs.push(Box::new(v));
158                *to = SeqExpr {
159                    span: DUMMY_SP,
160                    exprs,
161                }
162                .into();
163            }
164        }
165    }
166}
167
168impl ExprOptExt for Box<Expr> {
169    fn as_expr(&self) -> &Expr {
170        self
171    }
172
173    fn as_mut(&mut self) -> &mut Expr {
174        self
175    }
176}
177
178impl ExprOptExt for Expr {
179    fn as_expr(&self) -> &Expr {
180        self
181    }
182
183    fn as_mut(&mut self) -> &mut Expr {
184        self
185    }
186}
187
188pub(crate) fn contains_leaping_continue_with_label<N>(n: &N, label: Atom) -> bool
189where
190    N: VisitWith<LeapFinder>,
191{
192    let mut v = LeapFinder {
193        target_label: Some(label),
194        ..Default::default()
195    };
196    n.visit_with(&mut v);
197    v.found_continue_with_label
198}
199
200#[allow(unused)]
201pub(crate) fn contains_leaping_yield<N>(n: &N) -> bool
202where
203    N: VisitWith<LeapFinder>,
204{
205    let mut v = LeapFinder::default();
206    n.visit_with(&mut v);
207    v.found_yield
208}
209
210#[derive(Default)]
211pub(crate) struct LeapFinder {
212    found_await: bool,
213    found_yield: bool,
214    found_continue_with_label: bool,
215    target_label: Option<Atom>,
216}
217
218impl Visit for LeapFinder {
219    noop_visit_type!();
220
221    fn visit_await_expr(&mut self, n: &AwaitExpr) {
222        n.visit_children_with(self);
223
224        self.found_await = true;
225    }
226
227    fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}
228
229    fn visit_class_method(&mut self, _: &ClassMethod) {}
230
231    fn visit_constructor(&mut self, _: &Constructor) {}
232
233    fn visit_continue_stmt(&mut self, n: &ContinueStmt) {
234        n.visit_children_with(self);
235
236        if let Some(label) = &n.label {
237            self.found_continue_with_label |= self
238                .target_label
239                .as_ref()
240                .map_or(false, |l| *l == label.sym);
241        }
242    }
243
244    fn visit_function(&mut self, _: &Function) {}
245
246    fn visit_getter_prop(&mut self, _: &GetterProp) {}
247
248    fn visit_setter_prop(&mut self, _: &SetterProp) {}
249
250    fn visit_yield_expr(&mut self, n: &YieldExpr) {
251        n.visit_children_with(self);
252
253        self.found_yield = true;
254    }
255}
256
257/// This method returns true only if `T` is `var`. (Not `const` or `let`)
258pub(crate) fn is_hoisted_var_decl_without_init<T>(t: &T) -> bool
259where
260    T: StmtLike,
261{
262    let var = match t.as_stmt() {
263        Some(Stmt::Decl(Decl::Var(v)))
264            if matches!(
265                &**v,
266                VarDecl {
267                    kind: VarDeclKind::Var,
268                    ..
269                }
270            ) =>
271        {
272            v
273        }
274        _ => return false,
275    };
276    var.decls.iter().all(|decl| decl.init.is_none())
277}
278
279pub(crate) trait IsModuleItem {
280    fn is_module_item() -> bool;
281}
282
283impl IsModuleItem for Stmt {
284    fn is_module_item() -> bool {
285        false
286    }
287}
288
289impl IsModuleItem for ModuleItem {
290    fn is_module_item() -> bool {
291        true
292    }
293}
294
295pub trait ValueExt<T>: Into<Value<T>> {
296    fn opt(self) -> Option<T> {
297        match self.into() {
298            Value::Known(v) => Some(v),
299            _ => None,
300        }
301    }
302}
303
304impl<T> ValueExt<T> for Value<T> {}
305
306pub struct DeepThisExprVisitor {
307    found: bool,
308}
309
310impl Visit for DeepThisExprVisitor {
311    noop_visit_type!();
312
313    fn visit_this_expr(&mut self, _: &ThisExpr) {
314        self.found = true;
315    }
316}
317
318pub fn deeply_contains_this_expr<N>(body: &N) -> bool
319where
320    N: VisitWith<DeepThisExprVisitor>,
321{
322    let mut visitor = DeepThisExprVisitor { found: false };
323    body.visit_with(&mut visitor);
324    visitor.found
325}
326
327#[derive(Default)]
328pub(crate) struct IdentUsageCollector {
329    ids: FxHashSet<Id>,
330    ignore_nested: bool,
331}
332
333impl Visit for IdentUsageCollector {
334    noop_visit_type!();
335
336    visit_obj_and_computed!();
337
338    fn visit_block_stmt_or_expr(&mut self, n: &BlockStmtOrExpr) {
339        if self.ignore_nested {
340            return;
341        }
342
343        n.visit_children_with(self);
344    }
345
346    fn visit_constructor(&mut self, n: &Constructor) {
347        if self.ignore_nested {
348            return;
349        }
350
351        n.visit_children_with(self);
352    }
353
354    fn visit_function(&mut self, n: &Function) {
355        if self.ignore_nested {
356            return;
357        }
358
359        n.visit_children_with(self);
360    }
361
362    fn visit_getter_prop(&mut self, n: &GetterProp) {
363        if self.ignore_nested {
364            return;
365        }
366
367        n.visit_children_with(self);
368    }
369
370    fn visit_setter_prop(&mut self, n: &SetterProp) {
371        if self.ignore_nested {
372            return;
373        }
374
375        n.visit_children_with(self);
376    }
377
378    fn visit_ident(&mut self, n: &Ident) {
379        self.ids.insert(n.to_id());
380    }
381
382    fn visit_prop_name(&mut self, n: &PropName) {
383        if let PropName::Computed(..) = n {
384            n.visit_children_with(self);
385        }
386    }
387}
388
389#[derive(Default)]
390pub(crate) struct CapturedIdCollector {
391    ids: FxHashSet<Id>,
392    is_nested: bool,
393}
394
395impl Visit for CapturedIdCollector {
396    noop_visit_type!();
397
398    visit_obj_and_computed!();
399
400    fn visit_block_stmt_or_expr(&mut self, n: &BlockStmtOrExpr) {
401        let old = self.is_nested;
402        self.is_nested = true;
403        n.visit_children_with(self);
404        self.is_nested = old;
405    }
406
407    fn visit_constructor(&mut self, n: &Constructor) {
408        let old = self.is_nested;
409        self.is_nested = true;
410        n.visit_children_with(self);
411        self.is_nested = old;
412    }
413
414    fn visit_function(&mut self, n: &Function) {
415        let old = self.is_nested;
416        self.is_nested = true;
417        n.visit_children_with(self);
418        self.is_nested = old;
419    }
420
421    fn visit_ident(&mut self, n: &Ident) {
422        if self.is_nested {
423            self.ids.insert(n.to_id());
424        }
425    }
426
427    fn visit_prop_name(&mut self, n: &PropName) {
428        if let PropName::Computed(..) = n {
429            n.visit_children_with(self);
430        }
431    }
432}
433
434pub(crate) fn idents_captured_by<N>(n: &N) -> FxHashSet<Id>
435where
436    N: VisitWith<CapturedIdCollector>,
437{
438    let mut v = CapturedIdCollector {
439        is_nested: false,
440        ..Default::default()
441    };
442    n.visit_with(&mut v);
443    v.ids
444}
445
446pub(crate) fn idents_used_by<N>(n: &N) -> FxHashSet<Id>
447where
448    N: VisitWith<IdentUsageCollector>,
449{
450    let mut v = IdentUsageCollector {
451        ignore_nested: false,
452        ..Default::default()
453    };
454    n.visit_with(&mut v);
455    v.ids
456}
457
458pub(crate) fn idents_used_by_ignoring_nested<N>(n: &N) -> FxHashSet<Id>
459where
460    N: VisitWith<IdentUsageCollector>,
461{
462    let mut v = IdentUsageCollector {
463        ignore_nested: true,
464        ..Default::default()
465    };
466    n.visit_with(&mut v);
467    v.ids
468}
469
470pub fn now() -> Option<Instant> {
471    #[cfg(target_arch = "wasm32")]
472    {
473        None
474    }
475    #[cfg(not(target_arch = "wasm32"))]
476    {
477        Some(Instant::now())
478    }
479}
480
481pub(crate) fn contains_eval<N>(node: &N, include_with: bool) -> bool
482where
483    N: VisitWith<EvalFinder>,
484{
485    let mut v = EvalFinder {
486        found: false,
487        include_with,
488    };
489
490    node.visit_with(&mut v);
491    v.found
492}
493
494pub(crate) struct EvalFinder {
495    found: bool,
496    include_with: bool,
497}
498
499impl Visit for EvalFinder {
500    noop_visit_type!();
501
502    visit_obj_and_computed!();
503
504    fn visit_expr(&mut self, n: &Expr) {
505        maybe_grow_default(|| n.visit_children_with(self));
506    }
507
508    fn visit_ident(&mut self, i: &Ident) {
509        if i.sym == "eval" {
510            self.found = true;
511        }
512    }
513
514    fn visit_with_stmt(&mut self, s: &WithStmt) {
515        if self.include_with {
516            self.found = true;
517        } else {
518            s.visit_children_with(self);
519        }
520    }
521}
522
523#[allow(unused)]
524pub(crate) fn dump_program(p: &Program) -> String {
525    #[cfg(feature = "debug")]
526    {
527        force_dump_program(p)
528    }
529    #[cfg(not(feature = "debug"))]
530    {
531        String::new()
532    }
533}
534
535pub(crate) fn force_dump_program(p: &Program) -> String {
536    let _noop_sub = tracing::subscriber::set_default(tracing::subscriber::NoSubscriber::default());
537
538    crate::debug::dump(
539        &p.clone()
540            .apply(fixer(None))
541            .apply(hygiene())
542            .apply(visit_mut_pass(DropSpan {})),
543        true,
544    )
545}
546
547#[cfg(feature = "concurrent")]
548#[macro_export(local_inner_macros)]
549#[allow(clippy::crate_in_macro_def)]
550macro_rules! maybe_par {
551  ($prefix:ident.$name:ident.iter().$operator:ident($($rest:expr)*), $threshold:expr) => {
552      if $prefix.$name.len() >= $threshold {
553          use rayon::prelude::*;
554          $prefix.$name.par_iter().$operator($($rest)*)
555      } else {
556          $prefix.$name.iter().$operator($($rest)*)
557      }
558  };
559
560  ($prefix:ident.$name:ident.into_iter().$operator:ident($($rest:expr)*), $threshold:expr) => {
561      if $prefix.$name.len() >= $threshold {
562          use rayon::prelude::*;
563          $prefix.$name.into_par_iter().$operator($($rest)*)
564      } else {
565          $prefix.$name.into_iter().$operator($($rest)*)
566      }
567  };
568
569  ($name:ident.iter().$operator:ident($($rest:expr)*), $threshold:expr) => {
570      if $name.len() >= $threshold {
571          use rayon::prelude::*;
572          $name.par_iter().$operator($($rest)*)
573      } else {
574          $name.iter().$operator($($rest)*)
575      }
576  };
577
578  ($name:ident.into_iter().$operator:ident($($rest:expr)*), $threshold:expr) => {
579      if $name.len() >= $threshold {
580          use rayon::prelude::*;
581          $name.into_par_iter().$operator($($rest)*)
582      } else {
583          $name.into_iter().$operator($($rest)*)
584      }
585  };
586
587  ($name:ident.iter_mut().$operator:ident($($rest:expr)*), $threshold:expr) => {
588      if $name.len() >= $threshold {
589          use rayon::prelude::*;
590          $name.par_iter_mut().$operator($($rest)*)
591      } else {
592          $name.iter_mut().$operator($($rest)*)
593      }
594  };
595
596  ($name:ident.iter().$operator:ident($($rest:expr)*).$operator2:ident($($rest2:expr)*), $threshold:expr) => {
597      if $name.len() >= $threshold {
598          use rayon::prelude::*;
599          $name.par_iter().$operator($($rest)*).$operator2($($rest2)*)
600      } else {
601          $name.iter().$operator($($rest)*).$operator2($($rest2)*)
602      }
603  };
604
605  ($name:ident.into_iter().$operator:ident($($rest:expr)*).$operator2:ident($($rest2:expr)*), $threshold:expr) => {
606      if $name.len() >= $threshold {
607          use rayon::prelude::*;
608          $name.into_par_iter().$operator($($rest)*).$operator2($($rest2)*)
609      } else {
610          $name.into_iter().$operator($($rest)*).$operator2($($rest2)*)
611      }
612  };
613
614  ($name:ident.iter_mut().$operator:ident($($rest:expr)*).$operator2:ident($($rest2:expr)*), $threshold:expr) => {
615      if $name.len() >= $threshold {
616          use rayon::prelude::*;
617          $name.par_iter_mut().$operator($($rest)*).$operator2($($rest2)*)
618      } else {
619          $name.iter_mut().$operator($($rest)*).$operator2($($rest2)*)
620      }
621  };
622
623  ($name:ident.iter().$operator:ident($($rest:expr)*).$operator2:ident::<$t:ty>($($rest2:expr)*), $threshold:expr) => {
624      if $name.len() >= $threshold {
625          use rayon::prelude::*;
626          $name.par_iter().$operator($($rest)*).$operator2::<$t>($($rest2)*)
627      } else {
628          $name.iter().$operator($($rest)*).$operator2::<$t>($($rest2)*)
629      }
630  };
631
632  ($name:ident.iter().$operator:ident($($rest:expr)*).$operator2:ident($($rest2:expr)*).$operator3:ident($($rest3:expr)*), $threshold:expr) => {
633      if $name.len() >= $threshold {
634          use rayon::prelude::*;
635          $name.par_iter().$operator($($rest)*).$operator2($($rest2)*).$operator3($($rest3)*)
636      } else {
637          $name.iter().$operator($($rest)*).$operator2($($rest2)*).$operator3($($rest3)*)
638      }
639  };
640}
641
642#[cfg(not(feature = "concurrent"))]
643#[macro_export(local_inner_macros)]
644#[allow(clippy::crate_in_macro_def)]
645macro_rules! maybe_par {
646  ($prefix:ident.$name:ident.iter().$operator:ident($($rest:expr)*), $threshold:expr) => {
647    $prefix.$name.iter().$operator($($rest)*)
648  };
649
650  ($prefix:ident.$name:ident.into_iter().$operator:ident($($rest:expr)*), $threshold:expr) => {
651    $prefix.$name.into_iter().$operator($($rest)*)
652  };
653
654  ($name:ident.iter().$operator:ident($($rest:expr)*), $threshold:expr) => {
655    $name.iter().$operator($($rest)*)
656  };
657
658  ($name:ident.into_iter().$operator:ident($($rest:expr)*), $threshold:expr) => {
659    $name.into_iter().$operator($($rest)*)
660  };
661
662  ($name:ident.iter_mut().$operator:ident($($rest:expr)*), $threshold:expr) => {
663    $name.iter_mut().$operator($($rest)*)
664  };
665
666  ($name:ident.iter().$operator:ident($($rest:expr)*).$operator2:ident($($rest2:expr)*), $threshold:expr) => {
667    $name.iter().$operator($($rest)*).$operator2($($rest2)*)
668  };
669
670  ($name:ident.into_iter().$operator:ident($($rest:expr)*).$operator2:ident($($rest2:expr)*), $threshold:expr) => {
671    $name.into_iter().$operator($($rest)*).$operator2($($rest2)*)
672  };
673
674  ($name:ident.iter_mut().$operator:ident($($rest:expr)*).$operator2:ident($($rest2:expr)*), $threshold:expr) => {
675    $name.iter_mut().$operator($($rest)*).$operator2($($rest2)*)
676  };
677
678  ($name:ident.iter().$operator:ident($($rest:expr)*).$operator2:ident::<$t:ty>($($rest2:expr)*), $threshold:expr) => {
679    $name.iter().$operator($($rest)*).$operator2::<$t>($($rest2)*)
680  };
681
682  ($name:ident.iter().$operator:ident($($rest:expr)*).$operator2:ident($($rest2:expr)*).$operator3:ident($($rest3:expr)*), $threshold:expr) => {
683    $name.iter().$operator($($rest)*).$operator2($($rest2)*).$operator3($($rest3)*)
684  };
685}