swc_bundler/modules/sort/
stmt.rs

1use std::{collections::VecDeque, iter::from_fn, ops::Range};
2
3use indexmap::IndexSet;
4use petgraph::EdgeDirection::{Incoming as Dependants, Outgoing as Dependencies};
5use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
6use swc_common::{sync::Lrc, util::take::Take, SourceMap, SyntaxContext};
7use swc_ecma_ast::*;
8use swc_ecma_utils::find_pat_ids;
9use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
10
11use super::graph::Required;
12use crate::{id::Id, modules::sort::graph::StmtDepGraph, util::is_injected};
13
14pub(super) fn sort_stmts(
15    injected_ctxt: SyntaxContext,
16    modules: Vec<Vec<ModuleItem>>,
17    _cm: &Lrc<SourceMap>,
18) -> Vec<ModuleItem> {
19    let total_len: usize = modules.iter().map(|v| v.len()).sum();
20
21    let mut stmts = Vec::new();
22    let mut free = Vec::new();
23    let mut same_module_ranges = Vec::new();
24    let mut module_starts = Vec::new();
25
26    for module in modules {
27        let start = stmts.len();
28        module_starts.push(stmts.len());
29        let mut module_item_count = 0;
30
31        for stmt in module {
32            if is_injected_module_item(&stmt, injected_ctxt) {
33                free.push(stmt)
34            } else {
35                module_item_count += 1;
36                stmts.push(stmt)
37            }
38        }
39
40        same_module_ranges.push(start..start + module_item_count);
41    }
42    let non_free_count = stmts.len();
43    let free_range = non_free_count..non_free_count + free.len();
44    stmts.extend(free);
45
46    // print_hygiene(
47    //     &format!("before sort"),
48    //     cm,
49    //     &Module {
50    //         span: DUMMY_SP,
51    //         body: stmts.clone(),
52    //         shebang: None,
53    //     },
54    // );
55
56    let mut id_graph = calc_deps(&stmts);
57
58    tracing::debug!("Analyzed dependencies between statements");
59
60    let orders = iter(
61        &mut id_graph,
62        &same_module_ranges,
63        free_range,
64        &module_starts,
65        &stmts,
66    )
67    .collect::<Vec<_>>();
68
69    tracing::debug!("Sorted statements");
70
71    debug_assert_eq!(total_len, orders.len());
72
73    let mut new = Vec::with_capacity(stmts.len());
74    for idx in orders {
75        new.push(stmts[idx].take());
76    }
77
78    new
79}
80
81fn is_injected_module_item(stmt: &ModuleItem, injected_ctxt: SyntaxContext) -> bool {
82    match stmt {
83        ModuleItem::ModuleDecl(
84            ModuleDecl::Import(ImportDecl {
85                with: Some(with), ..
86            })
87            | ModuleDecl::ExportAll(ExportAll {
88                with: Some(with), ..
89            })
90            | ModuleDecl::ExportNamed(NamedExport {
91                with: Some(with), ..
92            }),
93        ) => is_injected(with),
94
95        ModuleItem::Stmt(Stmt::Decl(Decl::Var(v))) => v.ctxt == injected_ctxt,
96
97        _ => false,
98    }
99}
100
101fn iter<'a>(
102    graph: &'a mut StmtDepGraph,
103    same_module_ranges: &'a [Range<usize>],
104    free: Range<usize>,
105    module_starts: &[usize],
106    stmts: &'a [ModuleItem],
107) -> impl 'a + Iterator<Item = usize> {
108    let len = graph.node_count();
109
110    // dbg!(&same_module_ranges);
111    // dbg!(&free);
112    // dbg!(&module_starts);
113
114    let mut moves = FxHashSet::default();
115    let mut done = FxHashSet::default();
116    let mut stack = VecDeque::new();
117    stack.extend(module_starts.iter().copied());
118
119    for &start in module_starts {
120        let range = same_module_ranges
121            .iter()
122            .find(|range| range.contains(&start))
123            .cloned();
124        if let Some(range) = range {
125            for v in range {
126                stack.push_back(v);
127            }
128        }
129    }
130    for v in free.clone() {
131        stack.push_back(v);
132    }
133
134    from_fn(move || {
135        if done.len() == len {
136            return None;
137        }
138
139        // Check for first item.
140        'main: while let Some(idx) = stack.pop_front() {
141            if done.contains(&idx) {
142                // eprintln!("Done: {}", idx);
143                continue;
144            }
145            let is_free = free.contains(&idx);
146
147            // dbg!(idx, is_free);
148            // match &stmts[idx] {
149            //     ModuleItem::Stmt(Stmt::Decl(Decl::Var(var))) => {
150            //         let ids: Vec<Id> = find_ids(&var.decls);
151            //         eprintln!("(`{}`) Declare var: `{:?}`", idx, ids);
152            //     }
153            //     ModuleItem::Stmt(Stmt::Decl(Decl::Class(cls))) => {
154            //         eprintln!("(`{}`) Declare class: `{:?}`", idx, Id::from(&cls.ident));
155            //     }
156            //     ModuleItem::Stmt(Stmt::Decl(Decl::Fn(f))) => {
157            //         eprintln!("(`{}`) Declare fn: `{:?}`", idx, Id::from(&f.ident));
158            //     }
159            //     _ => eprintln!("(`{}`) Stmt", idx,),
160            // }
161
162            let current_range = same_module_ranges
163                .iter()
164                .find(|range| range.contains(&idx))
165                .cloned();
166
167            // dbg!(&current_range);
168
169            let can_ignore_deps = match &stmts[idx] {
170                ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
171                    decl: Decl::Fn(..),
172                    ..
173                }))
174                | ModuleItem::Stmt(Stmt::Decl(Decl::Fn(..))) => true,
175
176                ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
177                    decl: Decl::Class(cls),
178                    ..
179                }))
180                | ModuleItem::Stmt(Stmt::Decl(Decl::Class(cls)))
181                    if cls.class.super_class.is_none() =>
182                {
183                    true
184                }
185
186                _ => false,
187            };
188            let can_ignore_weak_deps = can_ignore_deps
189                || matches!(
190                    &stmts[idx],
191                    ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
192                        decl: Decl::Class(..),
193                        ..
194                    })) | ModuleItem::Stmt(Stmt::Decl(Decl::Class(..)))
195                );
196
197            // We
198            {
199                let deps = graph
200                    .neighbors_directed(idx, Dependencies)
201                    .filter(|dep| {
202                        let declared_in_same_module = match &current_range {
203                            Some(v) => v.contains(dep),
204                            None => false,
205                        };
206                        if declared_in_same_module {
207                            return false;
208                        }
209
210                        if !free.contains(&idx)
211                            && graph.has_a_path(*dep, idx)
212                            && !moves.insert((idx, *dep))
213                        {
214                            return false;
215                        }
216
217                        // Exclude emitted items
218                        !done.contains(dep)
219                    })
220                    .collect::<Vec<_>>();
221
222                // dbg!(&deps);
223
224                if !deps.is_empty() {
225                    let mut deps_to_push = Vec::new();
226                    for dep in deps.iter().rev().copied() {
227                        if deps_to_push.contains(&dep) {
228                            continue;
229                        }
230
231                        let can_ignore_dep = can_ignore_deps
232                            || (can_ignore_weak_deps
233                                && graph.edge_weight(idx, dep) == Some(Required::Maybe));
234
235                        if can_ignore_dep && graph.has_a_path(dep, idx) {
236                            // Just emit idx.
237                            continue;
238                        }
239
240                        deps_to_push.push(dep);
241                    }
242
243                    // dbg!(&deps_to_push);
244
245                    if !deps_to_push.is_empty() {
246                        // We should check idx again after emitting dependencies.
247                        stack.push_front(idx);
248
249                        for dep in deps_to_push {
250                            // eprintln!("[Move]{} => {}; kind = dep", idx, dep);
251                            stack.push_front(dep)
252                        }
253
254                        continue;
255                    }
256                }
257            }
258
259            if is_free {
260                let dependants = graph
261                    .neighbors_directed(idx, Dependants)
262                    .collect::<Vec<_>>();
263
264                for dependant in dependants {
265                    if !done.contains(&dependant) && free.contains(&dependant) {
266                        stack.push_front(dependant);
267                    }
268                }
269
270                graph.remove_node(idx);
271                done.insert(idx);
272                return Some(idx);
273            }
274
275            let current_range = match current_range {
276                Some(v) => v,
277                None => {
278                    let dependants = graph
279                        .neighbors_directed(idx, Dependants)
280                        .collect::<Vec<_>>();
281
282                    // dbg!(&dependants);
283
284                    // We only emit free items because we want to emit statements from same module
285                    // to emitted closely.
286                    for dependant in dependants {
287                        if !done.contains(&dependant) && free.contains(&dependant) {
288                            stack.push_front(dependant);
289                        }
290                    }
291
292                    // It's not within a module, so explicit ordering is not required.
293                    graph.remove_node(idx);
294                    done.insert(idx);
295                    return Some(idx);
296                }
297            };
298
299            // We should respect original order of statements within a module.
300            for preceding in current_range.clone() {
301                // We should select first statement in module which is not emitted yet.
302                if done.contains(&preceding) {
303                    continue;
304                }
305                // dbg!(preceding);
306                if preceding == idx {
307                    continue;
308                }
309                if preceding > idx {
310                    break;
311                }
312
313                if !moves.insert((idx, preceding)) {
314                    // idx = preceding;
315                    continue;
316                }
317
318                let dependants = graph
319                    .neighbors_directed(idx, Dependants)
320                    .collect::<Vec<_>>();
321
322                // dbg!(&dependants);
323
324                // We only emit free items because we want to emit statements from same module
325                // to emitted closely.
326                for dependant in dependants {
327                    if !done.contains(&dependant) && free.contains(&dependant) {
328                        stack.push_front(dependant);
329                    }
330                }
331
332                // We found a preceding statement which is not emitted yet.
333                stack.push_front(idx);
334                stack.push_front(preceding);
335                continue 'main;
336            }
337
338            graph.remove_node(idx);
339            done.insert(idx);
340            return Some(idx);
341        }
342
343        None
344    })
345}
346
347/// Using prototype should be treated as an initialization.
348#[derive(Default)]
349struct FieldInitFinder {
350    in_object_assign: bool,
351    in_rhs: bool,
352    accessed: FxHashSet<Id>,
353}
354
355impl FieldInitFinder {
356    fn check_lhs_of_assign(&mut self, lhs: &AssignTarget) {
357        if let AssignTarget::Simple(SimpleAssignTarget::Member(m)) = lhs {
358            match &*m.obj {
359                Expr::Ident(i) => {
360                    self.accessed.insert(i.into());
361                }
362                Expr::Member(..) => self.check_lhs_expr_of_assign(&m.obj),
363                _ => {}
364            }
365        }
366    }
367
368    fn check_lhs_expr_of_assign(&mut self, lhs: &Expr) {
369        if let Expr::Member(m) = lhs {
370            match &*m.obj {
371                Expr::Ident(i) => {
372                    self.accessed.insert(i.into());
373                }
374                Expr::Member(..) => self.check_lhs_expr_of_assign(&m.obj),
375                _ => {}
376            }
377        }
378    }
379}
380
381impl Visit for FieldInitFinder {
382    noop_visit_type!();
383
384    fn visit_assign_expr(&mut self, e: &AssignExpr) {
385        let old = self.in_rhs;
386        self.in_rhs = false;
387        e.left.visit_with(self);
388        self.check_lhs_of_assign(&e.left);
389
390        self.in_rhs = true;
391        e.right.visit_with(self);
392        self.in_rhs = old;
393    }
394
395    fn visit_pat(&mut self, e: &Pat) {
396        let old = self.in_rhs;
397        self.in_rhs = false;
398        e.visit_children_with(self);
399        self.in_rhs = old;
400    }
401
402    fn visit_call_expr(&mut self, e: &CallExpr) {
403        match &e.callee {
404            Callee::Super(_) | Callee::Import(_) => {}
405            Callee::Expr(callee) => {
406                if let Expr::Member(callee) = &**callee {
407                    if callee.obj.is_ident_ref_to("Object") {
408                        match &callee.prop {
409                            MemberProp::Ident(IdentName { sym: prop_sym, .. })
410                                if *prop_sym == *"assign" =>
411                            {
412                                let old = self.in_object_assign;
413                                self.in_object_assign = true;
414
415                                e.args.visit_with(self);
416                                self.in_object_assign = old;
417
418                                return;
419                            }
420                            _ => {}
421                        }
422                    }
423                }
424            }
425            #[cfg(swc_ast_unknown)]
426            _ => panic!("unable to access unknown nodes"),
427        }
428
429        e.visit_children_with(self);
430    }
431
432    fn visit_member_expr(&mut self, e: &MemberExpr) {
433        e.obj.visit_with(self);
434
435        if let MemberProp::Computed(c) = &e.prop {
436            c.visit_with(self);
437        }
438
439        if !self.in_rhs || self.in_object_assign {
440            if let Expr::Ident(obj) = &*e.obj {
441                match &e.prop {
442                    MemberProp::Ident(IdentName { sym: prop_sym, .. })
443                        if *prop_sym == *"prototype" =>
444                    {
445                        self.accessed.insert(obj.into());
446                    }
447                    _ => {}
448                }
449            }
450        }
451    }
452
453    fn visit_super_prop_expr(&mut self, e: &SuperPropExpr) {
454        if let SuperProp::Computed(c) = &e.prop {
455            c.visit_with(self);
456        }
457    }
458}
459
460/// Finds usage of `ident`
461struct InitializerFinder {
462    ident: Id,
463    found: bool,
464    in_complex: bool,
465}
466
467impl Visit for InitializerFinder {
468    noop_visit_type!();
469
470    fn visit_pat(&mut self, pat: &Pat) {
471        match pat {
472            Pat::Ident(i) if self.ident == i.id => {
473                self.found = true;
474            }
475
476            _ => {
477                pat.visit_children_with(self);
478            }
479        }
480    }
481
482    fn visit_ident(&mut self, i: &Ident) {
483        if self.in_complex && self.ident == *i {
484            self.found = true;
485        }
486    }
487
488    fn visit_expr_or_spread(&mut self, node: &ExprOrSpread) {
489        let in_complex = self.in_complex;
490        self.in_complex = true;
491        node.visit_children_with(self);
492        self.in_complex = in_complex;
493    }
494
495    fn visit_member_expr(&mut self, e: &MemberExpr) {
496        {
497            let in_complex = self.in_complex;
498            self.in_complex = true;
499            e.obj.visit_children_with(self);
500            self.in_complex = in_complex;
501        }
502
503        if let MemberProp::Computed(c) = &e.prop {
504            c.visit_with(self);
505        }
506    }
507
508    fn visit_super_prop_expr(&mut self, e: &SuperPropExpr) {
509        if let SuperProp::Computed(c) = &e.prop {
510            c.visit_with(self);
511        }
512    }
513}
514
515/// We do not care about variables created by current statement.
516/// But we care about modifications.
517#[derive(Default)]
518struct RequirementCalculator {
519    required_ids: IndexSet<(Id, Required), FxBuildHasher>,
520    /// While bundling, there can be two bindings with same name and syntax
521    /// context, in case of wrapped es modules. We exclude them from dependency
522    /// graph.
523    excluded: IndexSet<Id, FxBuildHasher>,
524
525    in_weak: bool,
526    in_var_decl: bool,
527    in_assign_lhs: bool,
528}
529
530macro_rules! weak {
531    ($name:ident, $T:ty) => {
532        fn $name(&mut self, f: &$T) {
533            let in_weak = self.in_weak;
534            self.in_weak = true;
535
536            f.visit_children_with(self);
537
538            self.in_weak = in_weak;
539        }
540    };
541}
542
543impl RequirementCalculator {
544    fn insert(&mut self, i: Id) {
545        self.required_ids.insert((
546            i,
547            if self.in_weak {
548                Required::Maybe
549            } else {
550                Required::Always
551            },
552        ));
553    }
554}
555
556impl Visit for RequirementCalculator {
557    noop_visit_type!();
558
559    weak!(visit_arrow_expr, ArrowExpr);
560
561    weak!(visit_function, Function);
562
563    weak!(visit_class_method, ClassMethod);
564
565    weak!(visit_private_method, PrivateMethod);
566
567    weak!(visit_method_prop, MethodProp);
568
569    fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) {
570        let orig = match &n.orig {
571            ModuleExportName::Ident(ident) => ident,
572            ModuleExportName::Str(..) => unimplemented!("module string names unimplemented"),
573            #[cfg(swc_ast_unknown)]
574            _ => panic!("unable to access unknown nodes"),
575        };
576        self.insert(orig.clone().into());
577    }
578
579    fn visit_assign_expr(&mut self, e: &AssignExpr) {
580        let old = self.in_assign_lhs;
581
582        self.in_assign_lhs = true;
583        e.left.visit_with(self);
584
585        self.in_assign_lhs = false;
586        e.right.visit_with(self);
587
588        self.in_assign_lhs = old;
589    }
590
591    fn visit_pat(&mut self, pat: &Pat) {
592        match pat {
593            Pat::Ident(i) => {
594                // We do not care about variables created by current statement.
595                if self.in_var_decl && !self.in_assign_lhs {
596                    return;
597                }
598                self.insert(i.id.clone().into());
599            }
600            _ => {
601                pat.visit_children_with(self);
602            }
603        }
604    }
605
606    fn visit_var_declarator(&mut self, var: &VarDeclarator) {
607        let in_var_decl = self.in_var_decl;
608        self.in_var_decl = true;
609
610        if self.in_weak {
611            let ids: Vec<Id> = find_pat_ids(&var.name);
612            self.excluded.extend(ids);
613        }
614
615        var.visit_children_with(self);
616
617        self.in_var_decl = in_var_decl;
618    }
619
620    fn visit_expr(&mut self, expr: &Expr) {
621        match expr {
622            Expr::Ident(i) => {
623                if self.in_var_decl && self.in_assign_lhs {
624                    return;
625                }
626                self.insert(i.into());
627            }
628            _ => {
629                expr.visit_children_with(self);
630            }
631        }
632    }
633
634    fn visit_prop(&mut self, prop: &Prop) {
635        match prop {
636            Prop::Shorthand(i) => {
637                self.insert(i.into());
638            }
639            _ => prop.visit_children_with(self),
640        }
641    }
642
643    fn visit_member_expr(&mut self, e: &MemberExpr) {
644        e.obj.visit_with(self);
645
646        if let MemberProp::Computed(c) = &e.prop {
647            c.visit_with(self);
648        }
649    }
650
651    fn visit_super_prop_expr(&mut self, e: &SuperPropExpr) {
652        if let SuperProp::Computed(c) = &e.prop {
653            c.visit_with(self);
654        }
655    }
656}
657
658fn calc_deps(new: &[ModuleItem]) -> StmtDepGraph {
659    tracing::debug!("Analyzing dependencies between statements");
660    let mut graph = StmtDepGraph::default();
661
662    let mut declared_by = FxHashMap::<Id, Vec<usize>>::default();
663    let mut uninitialized_ids = FxHashMap::<Id, usize>::default();
664
665    for (idx, item) in new.iter().enumerate() {
666        graph.add_node(idx);
667
668        // We start by calculating ids created by statements. Note that we don't need to
669        // analyze bodies of functions nor members of classes, because it's not
670        // evaluated until they are called.
671
672        match item {
673            // We only check declarations because ids are created by declarations.
674            ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, .. }))
675            | ModuleItem::Stmt(Stmt::Decl(decl)) => {
676                //
677                match decl {
678                    Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => {
679                        // eprintln!("Decl: `{}` declares {:?}`", idx, Id::from(ident));
680                        declared_by.entry(Id::from(ident)).or_default().push(idx);
681                    }
682                    Decl::Var(vars) => {
683                        for var in &vars.decls {
684                            //
685                            let ids: Vec<Id> = find_pat_ids(&var.name);
686                            for id in ids {
687                                if var.init.is_none() {
688                                    uninitialized_ids.insert(id.clone(), idx);
689                                }
690
691                                // eprintln!("Decl: `{}` declares {:?}`", idx, id);
692                                declared_by.entry(id).or_default().push(idx);
693                            }
694                        }
695                    }
696                    _ => {}
697                }
698            }
699            _ => {}
700        }
701
702        {
703            // Find extra initializations.
704            let mut v = FieldInitFinder::default();
705            item.visit_with(&mut v);
706
707            for id in v.accessed {
708                if let Some(declarator_indexes) = declared_by.get(&id) {
709                    // let idx_decl = match &item {
710                    //     ModuleItem::Stmt(Stmt::Decl(Decl::Var(var))) => {
711                    //         let ids: Vec<Id> = find_ids(&var.decls);
712                    //         format!("`{:?}`", ids)
713                    //     }
714                    //     ModuleItem::Stmt(Stmt::Decl(Decl::Class(c))) => {
715                    //         format!("{}{:?}", c.ident.sym, c.ident.ctxt)
716                    //     }
717                    //     ModuleItem::Stmt(Stmt::Decl(Decl::Fn(f))) => {
718                    //         format!("{}{:?}", f.ident.sym, f.ident.ctxt)
719                    //     }
720                    //     _ => String::from(""),
721                    // };
722
723                    for &declarator_index in declarator_indexes {
724                        if declarator_index != idx {
725                            graph.add_edge(idx, declarator_index, Required::Always);
726                            // eprintln!(
727                            //     "Field init: `{}` ({}) depends on `{}`:
728                            // {:?}",
729                            //     idx, idx_decl, declarator_index, &id
730                            // );
731                        }
732                    }
733
734                    // eprintln!("`{}` declares {:?}`", idx, id);
735                    declared_by.entry(id).or_default().push(idx);
736                }
737            }
738        }
739    }
740
741    // Handle uninitialized variables
742    //
743    // Compiled typescript enum is not initialized by declaration
744    //
745    // var Status;
746    // (function(Status){})(Status)
747    for (uninit_id, start_idx) in uninitialized_ids {
748        for (idx, item) in new.iter().enumerate().filter(|(idx, _)| *idx > start_idx) {
749            let mut finder = InitializerFinder {
750                ident: uninit_id.clone(),
751                found: false,
752                in_complex: false,
753            };
754            item.visit_with(&mut finder);
755            if finder.found {
756                declared_by.entry(uninit_id).or_default().push(idx);
757                break;
758            }
759        }
760    }
761
762    for (idx, item) in new.iter().enumerate() {
763        // We then calculate which ids a statement require to be executed.
764        // Again, we don't need to analyze non-top-level identifiers because they
765        // are not evaluated while loading module.
766
767        let mut visitor = RequirementCalculator::default();
768
769        item.visit_with(&mut visitor);
770
771        for (id, kind) in visitor.required_ids {
772            if visitor.excluded.contains(&id) {
773                continue;
774            }
775
776            if let Some(declarator_indexes) = declared_by.get(&id) {
777                for &declarator_index in declarator_indexes {
778                    if declarator_index != idx {
779                        // let idx_decl = match &item {
780                        //     ModuleItem::Stmt(Stmt::Decl(Decl::Var(var))) => {
781                        //         let ids: Vec<Id> = find_ids(&var.decls);
782                        //         format!("`{:?}`", ids)
783                        //     }
784                        //     ModuleItem::Stmt(Stmt::Decl(Decl::Class(c))) => {
785                        //         format!("{}{:?}", c.ident.sym, c.ident.ctxt)
786                        //     }
787                        //     ModuleItem::Stmt(Stmt::Decl(Decl::Fn(f))) => {
788                        //         format!("{}{:?}", f.ident.sym, f.ident.ctxt)
789                        //     }
790                        //     _ => String::from(""),
791                        // };
792
793                        graph.add_edge(idx, declarator_index, kind);
794                        // eprintln!(
795                        //     "`{}` ({}) depends on `{}`: {:?}",
796                        //     idx, idx_decl, declarator_index, &id
797                        // );
798                        if cfg!(debug_assertions) {
799                            assert!(graph
800                                .neighbors_directed(idx, Dependencies)
801                                .any(|x| x == declarator_index));
802                        }
803                    }
804                }
805            }
806        }
807    }
808
809    graph
810}
811
812#[cfg(test)]
813mod tests {
814    use swc_common::DUMMY_SP;
815    use swc_ecma_ast::*;
816
817    use super::{calc_deps, Dependencies};
818    use crate::{bundler::tests::suite, debug::print_hygiene};
819
820    fn assert_no_cycle(s: &str) {
821        suite().file("main.js", s).run(|t| {
822            let info = t.module("main.js");
823            let module = (*info.module).clone();
824
825            let graph = calc_deps(&module.body);
826
827            for i in 0..module.body.len() {
828                if let ModuleItem::Stmt(Stmt::Decl(Decl::Fn(..))) = &module.body[i] {
829                    continue;
830                }
831
832                let deps = graph
833                    .neighbors_directed(i, Dependencies)
834                    .collect::<Vec<_>>();
835
836                for dep in deps {
837                    if let ModuleItem::Stmt(Stmt::Decl(Decl::Fn(..))) = &module.body[dep] {
838                        continue;
839                    }
840                    print_hygiene(
841                        "first item",
842                        &t.cm,
843                        &Module {
844                            span: DUMMY_SP,
845                            body: vec![module.body[dep].clone()],
846                            shebang: None,
847                        },
848                    );
849                    print_hygiene(
850                        "second item",
851                        &t.cm,
852                        &Module {
853                            span: DUMMY_SP,
854                            body: vec![module.body[i].clone()],
855                            shebang: None,
856                        },
857                    );
858                    assert!(
859                        !graph.has_a_path(dep, i),
860                        "{dep} and {i} is dependant to each other",
861                    );
862                }
863            }
864
865            Ok(())
866        });
867    }
868
869    #[test]
870    fn no_cycle_1() {
871        assert_no_cycle(
872            r#"
873            function lexer(str) {
874                const tokens = [];
875                let i = 0;
876                while(i < str.length){
877                    const char = str[i];
878                    if (char === "*" || char === "+" || char === "?") {
879                        tokens.push({
880                            type: "MODIFIER",
881                            index: i,
882                            value: str[i++]
883                        });
884                        continue;
885                    }
886                    if (char === "\\") {
887                        tokens.push({
888                            type: "ESCAPED_CHAR",
889                            index: i++,
890                            value: str[i++]
891                        });
892                        continue;
893                    }
894                    if (char === "{") {
895                        tokens.push({
896                            type: "OPEN",
897                            index: i,
898                            value: str[i++]
899                        });
900                        continue;
901                    }
902                    if (char === "}") {
903                        tokens.push({
904                            type: "CLOSE",
905                            index: i,
906                            value: str[i++]
907                        });
908                        continue;
909                    }
910                    if (char === ":") {
911                        let name = "";
912                        let j = i + 1;
913                        while(j < str.length){
914                            const code = str.charCodeAt(j);
915                            if ((code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === 95) {
916                                name += str[j++];
917                                continue;
918                            }
919                            break;
920                        }
921                        if (!name) throw new TypeError(`Missing parameter name at ${i}`);
922                        tokens.push({
923                            type: "NAME",
924                            index: i,
925                            value: name
926                        });
927                        i = j;
928                        continue;
929                    }
930                    if (char === "(") {
931                        let count = 1;
932                        let pattern = "";
933                        let j = i + 1;
934                        if (str[j] === "?") {
935                            throw new TypeError(`Pattern cannot start with "?" at ${j}`);
936                        }
937                        while(j < str.length){
938                            if (str[j] === "\\") {
939                                pattern += str[j++] + str[j++];
940                                continue;
941                            }
942                            if (str[j] === ")") {
943                                count--;
944                                if (count === 0) {
945                                    j++;
946                                    break;
947                                }
948                            } else if (str[j] === "(") {
949                                count++;
950                                if (str[j + 1] !== "?") {
951                                    throw new TypeError(`Capturing groups are not allowed at ${j}`);
952                                }
953                            }
954                            pattern += str[j++];
955                        }
956                        if (count) throw new TypeError(`Unbalanced pattern at ${i}`);
957                        if (!pattern) throw new TypeError(`Missing pattern at ${i}`);
958                        tokens.push({
959                            type: "PATTERN",
960                            index: i,
961                            value: pattern
962                        });
963                        i = j;
964                        continue;
965                    }
966                    tokens.push({
967                        type: "CHAR",
968                        index: i,
969                        value: str[i++]
970                    });
971                }
972                tokens.push({
973                    type: "END",
974                    index: i,
975                    value: ""
976                });
977                return tokens;
978            }
979            function parse(str, options = {
980            }) {
981                const tokens = lexer(str);
982                const { prefixes ="./"  } = options;
983                const defaultPattern = `[^${escapeString(options.delimiter || "/#?")}]+?`;
984                const result = [];
985                let key = 0;
986                let i = 0;
987                let path = "";
988                const tryConsume = (type)=>{
989                    if (i < tokens.length && tokens[i].type === type) return tokens[i++].value;
990                };
991                const mustConsume = (type)=>{
992                    const value = tryConsume(type);
993                    if (value !== undefined) return value;
994                    const { type: nextType , index  } = tokens[i];
995                    throw new TypeError(`Unexpected ${nextType} at ${index}, expected ${type}`);
996                };
997                const consumeText = ()=>{
998                    let result1 = "";
999                    let value;
1000                    while((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))){
1001                        result1 += value;
1002                    }
1003                    return result1;
1004                };
1005                while(i < tokens.length){
1006                    const char = tryConsume("CHAR");
1007                    const name = tryConsume("NAME");
1008                    const pattern = tryConsume("PATTERN");
1009                    if (name || pattern) {
1010                        let prefix = char || "";
1011                        if (prefixes.indexOf(prefix) === -1) {
1012                            path += prefix;
1013                            prefix = "";
1014                        }
1015                        if (path) {
1016                            result.push(path);
1017                            path = "";
1018                        }
1019                        result.push({
1020                            name: name || key++,
1021                            prefix,
1022                            suffix: "",
1023                            pattern: pattern || defaultPattern,
1024                            modifier: tryConsume("MODIFIER") || ""
1025                        });
1026                        continue;
1027                    }
1028                    const value = char || tryConsume("ESCAPED_CHAR");
1029                    if (value) {
1030                        path += value;
1031                        continue;
1032                    }
1033                    if (path) {
1034                        result.push(path);
1035                        path = "";
1036                    }
1037                    const open = tryConsume("OPEN");
1038                    if (open) {
1039                        const prefix = consumeText();
1040                        const name1 = tryConsume("NAME") || "";
1041                        const pattern1 = tryConsume("PATTERN") || "";
1042                        const suffix = consumeText();
1043                        mustConsume("CLOSE");
1044                        result.push({
1045                            name: name1 || (pattern1 ? key++ : ""),
1046                            pattern: name1 && !pattern1 ? defaultPattern : pattern1,
1047                            prefix,
1048                            suffix,
1049                            modifier: tryConsume("MODIFIER") || ""
1050                        });
1051                        continue;
1052                    }
1053                    mustConsume("END");
1054                }
1055                return result;
1056            }
1057            const parse1 = parse;
1058            function compile(str, options) {
1059                return tokensToFunction(parse(str, options), options);
1060            }
1061            const compile1 = compile;
1062            function tokensToFunction(tokens, options = {
1063            }) {
1064                const reFlags = flags(options);
1065                const { encode =(x)=>x
1066                 , validate =true  } = options;
1067                const matches = tokens.map((token)=>{
1068                    if (typeof token === "object") {
1069                        return new RegExp(`^(?:${token.pattern})$`, reFlags);
1070                    }
1071                });
1072                return (data)=>{
1073                    let path = "";
1074                    for(let i = 0; i < tokens.length; i++){
1075                        const token = tokens[i];
1076                        if (typeof token === "string") {
1077                            path += token;
1078                            continue;
1079                        }
1080                        const value = data ? data[token.name] : undefined;
1081                        const optional = token.modifier === "?" || token.modifier === "*";
1082                        const repeat = token.modifier === "*" || token.modifier === "+";
1083                        if (Array.isArray(value)) {
1084                            if (!repeat) {
1085                                throw new TypeError(`Expected "${token.name}" to not repeat, but got an array`);
1086                            }
1087                            if (value.length === 0) {
1088                                if (optional) continue;
1089                                throw new TypeError(`Expected "${token.name}" to not be empty`);
1090                            }
1091                            for(let j = 0; j < value.length; j++){
1092                                const segment = encode(value[j], token);
1093                                if (validate && !(matches[i]).test(segment)) {
1094                                    throw new TypeError(`Expected all "${token.name}" to match "${token.pattern}", but got "${segment}"`);
1095                                }
1096                                path += token.prefix + segment + token.suffix;
1097                            }
1098                            continue;
1099                        }
1100                        if (typeof value === "string" || typeof value === "number") {
1101                            const segment = encode(String(value), token);
1102                            if (validate && !(matches[i]).test(segment)) {
1103                                throw new TypeError(`Expected "${token.name}" to match "${token.pattern}", but got "${segment}"`);
1104                            }
1105                            path += token.prefix + segment + token.suffix;
1106                            continue;
1107                        }
1108                        if (optional) continue;
1109                        const typeOfMessage = repeat ? "an array" : "a string";
1110                        throw new TypeError(`Expected "${token.name}" to be ${typeOfMessage}`);
1111                    }
1112                    return path;
1113                };
1114            }
1115            const tokensToFunction1 = tokensToFunction;
1116            function match(str, options) {
1117                const keys = [];
1118                const re = pathToRegexp(str, keys, options);
1119                return regexpToFunction(re, keys, options);
1120            }
1121            const match1 = match;
1122            function regexpToFunction(re, keys, options = {
1123            }) {
1124                const { decode =(x)=>x
1125                  } = options;
1126                return function(pathname) {
1127                    const m = re.exec(pathname);
1128                    if (!m) return false;
1129                    const { 0: path , index  } = m;
1130                    const params = Object.create(null);
1131                    for(let i = 1; i < m.length; i++){
1132                        if (m[i] === undefined) continue;
1133                        const key = keys[i - 1];
1134                        if (key.modifier === "*" || key.modifier === "+") {
1135                            params[key.name] = m[i].split(key.prefix + key.suffix).map((value)=>{
1136                                return decode(value, key);
1137                            });
1138                        } else {
1139                            params[key.name] = decode(m[i], key);
1140                        }
1141                    }
1142                    return {
1143                        path,
1144                        index,
1145                        params
1146                    };
1147                };
1148            }
1149            const regexpToFunction1 = regexpToFunction;
1150            function escapeString(str) {
1151                return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
1152            }
1153            function flags(options) {
1154                return options && options.sensitive ? "" : "i";
1155            }
1156            function regexpToRegexp(path, keys) {
1157                if (!keys) return path;
1158                const groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g;
1159                let index = 0;
1160                let execResult = groupsRegex.exec(path.source);
1161                while(execResult){
1162                    keys.push({
1163                        name: execResult[1] || index++,
1164                        prefix: "",
1165                        suffix: "",
1166                        modifier: "",
1167                        pattern: ""
1168                    });
1169                    execResult = groupsRegex.exec(path.source);
1170                }
1171                return path;
1172            }
1173            function arrayToRegexp(paths, keys, options) {
1174                const parts = paths.map((path)=>pathToRegexp(path, keys, options).source
1175                );
1176                return new RegExp(`(?:${parts.join("|")})`, flags(options));
1177            }
1178            function stringToRegexp(path, keys, options) {
1179                return tokensToRegexp(parse(path, options), keys, options);
1180            }
1181            function tokensToRegexp(tokens, keys, options = {
1182            }) {
1183                const { strict =false , start =true , end =true , encode =(x)=>x
1184                  } = options;
1185                const endsWith = `[${escapeString(options.endsWith || "")}]|$`;
1186                const delimiter = `[${escapeString(options.delimiter || "/#?")}]`;
1187                let route = start ? "^" : "";
1188                for (const token of tokens){
1189                    if (typeof token === "string") {
1190                        route += escapeString(encode(token));
1191                    } else {
1192                        const prefix = escapeString(encode(token.prefix));
1193                        const suffix = escapeString(encode(token.suffix));
1194                        if (token.pattern) {
1195                            if (keys) keys.push(token);
1196                            if (prefix || suffix) {
1197                                if (token.modifier === "+" || token.modifier === "*") {
1198                                    const mod = token.modifier === "*" ? "?" : "";
1199                                    route += `(?:${prefix}((?:${token.pattern})(?:${suffix}${prefix}(?:${token.pattern}))*)${suffix})${mod}`;
1200                                } else {
1201                                    route += `(?:${prefix}(${token.pattern})${suffix})${token.modifier}`;
1202                                }
1203                            } else {
1204                                route += `(${token.pattern})${token.modifier}`;
1205                            }
1206                        } else {
1207                            route += `(?:${prefix}${suffix})${token.modifier}`;
1208                        }
1209                    }
1210                }
1211                if (end) {
1212                    if (!strict) route += `${delimiter}?`;
1213                    route += !options.endsWith ? "$" : `(?=${endsWith})`;
1214                } else {
1215                    const endToken = tokens[tokens.length - 1];
1216                    const isEndDelimited = typeof endToken === "string" ? delimiter.indexOf(endToken[endToken.length - 1]) > -1 : endToken === undefined;
1217                    if (!strict) {
1218                        route += `(?:${delimiter}(?=${endsWith}))?`;
1219                    }
1220                    if (!isEndDelimited) {
1221                        route += `(?=${delimiter}|${endsWith})`;
1222                    }
1223                }
1224                return new RegExp(route, flags(options));
1225            }
1226            const tokensToRegexp1 = tokensToRegexp;
1227            function pathToRegexp(path, keys, options) {
1228                if (path instanceof RegExp) return regexpToRegexp(path, keys);
1229                if (Array.isArray(path)) return arrayToRegexp(path, keys, options);
1230                return stringToRegexp(path, keys, options);
1231            }
1232            const pathToRegexp1 = pathToRegexp;
1233        "#,
1234        );
1235    }
1236}