swc_graph_analyzer/
lib.rs1use std::{fmt::Debug, hash::Hash, marker::PhantomData};
2
3use auto_impl::auto_impl;
4use rustc_hash::FxHashSet;
5use swc_fast_graph::digraph::FastDiGraphMap;
6
7#[auto_impl(&, Box, Rc, Arc)]
8pub trait DepGraph {
9 type ModuleId: Debug + Copy + Eq + Hash + Ord;
10
11 fn deps_of(&self, module_id: Self::ModuleId) -> Vec<Self::ModuleId>;
12}
13
14pub struct GraphAnalyzer<G>
16where
17 G: DepGraph,
18{
19 tracked: FxHashSet<(G::ModuleId, G::ModuleId)>,
21 dep_graph: G,
22 data: GraphResult<G>,
23}
24
25impl<G> GraphAnalyzer<G>
26where
27 G: DepGraph,
28{
29 pub fn new(dep_graph: G) -> Self {
30 Self {
31 dep_graph,
32 data: GraphResult {
33 all: Default::default(),
34 graph: Default::default(),
35 cycles: Default::default(),
36 _marker: Default::default(),
37 },
38 tracked: Default::default(),
39 }
40 }
41
42 pub fn load(&mut self, entry: G::ModuleId) {
43 self.add_to_graph(
44 entry,
45 &mut vec![entry],
46 &mut State {
47 visited: Default::default(),
48 },
49 );
50 }
51
52 fn add_to_graph(
53 &mut self,
54 module_id: G::ModuleId,
55 path: &mut Vec<G::ModuleId>,
56 state: &mut State<G::ModuleId>,
57 ) {
58 let visited = state.visited.contains(&module_id);
59 let cycle_rpos = if visited {
62 path.iter().rposition(|v| *v == module_id)
63 } else {
64 None
65 };
66
67 if let Some(rpos) = cycle_rpos {
68 let cycle = path[rpos..].to_vec();
69 tracing::debug!("Found cycle: {:?}", cycle);
70 self.data.cycles.push(cycle);
71 }
72
73 let prev_last = *path.last().unwrap();
74 if !self.tracked.insert((prev_last, module_id)) {
76 return;
77 }
78
79 path.push(module_id);
80
81 if !visited {
82 state.visited.push(module_id);
83 }
84 if !self.data.all.contains(&module_id) {
85 self.data.all.push(module_id);
86 }
87 self.data.graph.add_node(module_id);
88
89 for dep_module_id in self.dep_graph.deps_of(module_id) {
90 tracing::debug!("Dep: {:?} -> {:?}", module_id, dep_module_id);
91
92 self.data.graph.add_edge(module_id, dep_module_id, ());
93
94 self.add_to_graph(dep_module_id, path, state);
95 }
96
97 let res = path.pop();
98 debug_assert_eq!(res, Some(module_id));
99 }
100
101 pub fn into_result(self) -> GraphResult<G> {
102 self.data
103 }
104}
105
106struct State<I> {
107 visited: Vec<I>,
108}
109
110pub struct GraphResult<G>
111where
112 G: DepGraph,
113{
114 pub all: Vec<G::ModuleId>,
115 pub graph: FastDiGraphMap<G::ModuleId, ()>,
116 pub cycles: Vec<Vec<G::ModuleId>>,
117
118 _marker: PhantomData<G>,
119}