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