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