1use petgraph::{graph::NodeIndex, Graph};
2use serde::{Deserialize, Serialize};
3use std::{collections::HashMap, path::PathBuf};
4
5use spinne_logger::Logger;
6
7#[derive(Debug, Serialize, Deserialize)]
8pub struct Component {
9 pub name: String,
10 pub file_path: PathBuf,
11 pub prop_usage: HashMap<String, usize>,
12}
13
14#[derive(Debug)]
17pub struct ComponentGraph {
18 pub graph: Graph<Component, ()>,
19}
20
21#[derive(Serialize, Deserialize, Debug)]
22pub struct SerializableComponentGraph {
23 pub nodes: Vec<Component>,
24 pub edges: Vec<(usize, usize)>,
25}
26
27impl ComponentGraph {
29 pub fn new() -> Self {
31 Self {
32 graph: Graph::new(),
33 }
34 }
35
36 pub fn has_component(&self, key: &str, file_path: &PathBuf) -> bool {
39 self.graph
40 .node_indices()
41 .any(|i| self.graph[i].name == key && self.graph[i].file_path == *file_path)
42 }
43
44 pub fn has_edge(&self, from: NodeIndex, to: NodeIndex) -> bool {
46 self.graph.contains_edge(from, to)
47 }
48
49 pub fn get_component(&self, key: &str, file_path: &PathBuf) -> Option<NodeIndex> {
51 self.graph
52 .node_indices()
53 .find(|i| self.graph[*i].name == key && self.graph[*i].file_path == *file_path)
54 }
55
56 pub fn add_component(&mut self, key: String, file_path: PathBuf) -> NodeIndex {
60 if !self.has_component(&key, &file_path) {
61 Logger::debug(
62 &format!("Adding new component: {:?}, {:?}", key, file_path),
63 2,
64 );
65 let node_index = self.graph.add_node(Component {
66 name: key,
67 file_path,
68 prop_usage: HashMap::new(),
69 });
70 node_index
71 } else {
72 Logger::debug(
73 &format!("Called add_component for existing component: {:?}", key),
74 2,
75 );
76 self.graph
77 .node_indices()
78 .find(|i| self.graph[*i].name == key && self.graph[*i].file_path == file_path)
79 .unwrap()
80 }
81 }
82
83 pub fn add_child(&mut self, parent: (&str, &PathBuf), child: (&str, &PathBuf)) {
85 let parent_index = self.get_or_add_component(parent.0, parent.1.clone());
86 let child_index = self.get_or_add_component(child.0, child.1.clone());
87
88 Logger::debug(
89 &format!("Adding child edge: {:?} -> {:?}", parent.0, child.0),
90 2,
91 );
92 self.graph.add_edge(parent_index, child_index, ());
93 }
94
95 fn get_or_add_component(&mut self, name: &str, file_path: PathBuf) -> NodeIndex {
98 match self.get_component(name, &file_path) {
99 Some(index) => index,
100 None => self.add_component(name.to_string(), file_path),
101 }
102 }
103
104 pub fn add_prop_usage(&mut self, component: &str, file_path: &PathBuf, prop: String) {
107 if let Some(node_index) = self.get_component(component, file_path) {
108 let component = &mut self.graph[node_index];
109
110 Logger::debug(
111 &format!("Adding prop usage: {:?} -> {:?}", component, prop),
112 2,
113 );
114 *component.prop_usage.entry(prop).or_insert(0) += 1;
115 }
116 }
117
118 pub fn print_graph(&self) {
120 println!("{:?}", self.graph);
121 }
122
123 pub fn to_serializable(&self) -> SerializableComponentGraph {
126 let nodes: Vec<Component> = self
127 .graph
128 .node_indices()
129 .map(|i| Component {
130 name: self.graph[i].name.clone(),
131 file_path: self.graph[i].file_path.clone(),
132 prop_usage: self.graph[i].prop_usage.clone(),
133 })
134 .collect();
135
136 let edges: Vec<(usize, usize)> = self
137 .graph
138 .edge_indices()
139 .map(|e| {
140 let (a, b) = self.graph.edge_endpoints(e).unwrap();
141 (a.index(), b.index())
142 })
143 .collect();
144
145 SerializableComponentGraph { nodes, edges }
146 }
147
148 pub fn from_serializable(serializable: SerializableComponentGraph) -> Self {
151 let mut graph = Graph::new();
152 let node_indices: Vec<NodeIndex> = serializable
153 .nodes
154 .into_iter()
155 .map(|component| graph.add_node(component))
156 .collect();
157
158 for (from, to) in serializable.edges {
159 graph.add_edge(node_indices[from], node_indices[to], ());
160 }
161
162 ComponentGraph { graph }
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_new() {
172 let graph = ComponentGraph::new();
173 assert_eq!(graph.graph.node_count(), 0);
174 assert_eq!(graph.graph.edge_count(), 0);
175 }
176
177 #[test]
178 fn test_add_component() {
179 let mut graph = ComponentGraph::new();
180 let file_path = PathBuf::from("/path/to/Component.tsx");
181 let key = "MyComponent".to_string();
182
183 let node_index = graph.add_component(key.clone(), file_path.clone());
184
185 assert_eq!(graph.graph.node_count(), 1);
186 assert_eq!(graph.graph.edge_count(), 0);
187 assert!(graph.has_component(&key, &file_path));
188 assert_eq!(graph.graph[node_index].name, key);
189 assert_eq!(graph.graph[node_index].file_path, file_path);
190 }
191
192 #[test]
193 fn test_add_duplicate_component() {
194 let mut graph = ComponentGraph::new();
195 let file_path = PathBuf::from("/path/to/Component.tsx");
196 let key = "MyComponent".to_string();
197
198 let node_index = graph.add_component(key.clone(), file_path.clone());
199 let second_node_index = graph.add_component(key.clone(), file_path.clone());
200
201 assert_eq!(graph.graph.node_count(), 1);
202 assert_eq!(graph.graph.edge_count(), 0);
203 assert!(graph.has_component(&key, &file_path));
204 assert_eq!(second_node_index, node_index);
205 }
206
207 #[test]
208 fn test_add_child() {
209 let mut graph = ComponentGraph::new();
210 let parent_key = "ParentComponent".to_string();
211 let child_key = "ChildComponent".to_string();
212
213 let parent_node_index = graph.add_component(
214 parent_key.clone(),
215 PathBuf::from("/path/to/ParentComponent.tsx"),
216 );
217 let child_node_index = graph.add_component(
218 child_key.clone(),
219 PathBuf::from("/path/to/ChildComponent.tsx"),
220 );
221
222 graph.add_child(
223 (&parent_key, &PathBuf::from("/path/to/ParentComponent.tsx")),
224 (&child_key, &PathBuf::from("/path/to/ChildComponent.tsx")),
225 );
226
227 assert_eq!(graph.graph.node_count(), 2);
228 assert_eq!(graph.graph.edge_count(), 1);
229 assert!(graph.has_component(&parent_key, &PathBuf::from("/path/to/ParentComponent.tsx")));
230 assert!(graph.has_component(&child_key, &PathBuf::from("/path/to/ChildComponent.tsx")));
231 assert!(graph
232 .graph
233 .contains_edge(parent_node_index, child_node_index));
234 }
235
236 #[test]
237 fn test_complex_graph_structure() {
238 let mut graph = ComponentGraph::new();
239
240 let app_path = PathBuf::from("/path/to/App.tsx");
242 let app = graph.add_component("App".to_string(), app_path.clone());
243 let header =
244 graph.add_component("Header".to_string(), PathBuf::from("/path/to/Header.tsx"));
245
246 let footer =
247 graph.add_component("Footer".to_string(), PathBuf::from("/path/to/Footer.tsx"));
248 let content =
249 graph.add_component("Content".to_string(), PathBuf::from("/path/to/Content.tsx"));
250
251 graph.add_child(
253 ("App", &app_path),
254 ("Header", &PathBuf::from("/path/to/Header.tsx")),
255 );
256 graph.add_child(
257 ("App", &app_path),
258 ("Footer", &PathBuf::from("/path/to/Footer.tsx")),
259 );
260 graph.add_child(
261 ("App", &app_path),
262 ("Content", &PathBuf::from("/path/to/Content.tsx")),
263 );
264
265 assert_eq!(graph.graph.node_count(), 4);
267 assert_eq!(graph.graph.edge_count(), 3);
268
269 let app_component = &graph.graph[app];
270 assert_eq!(app_component.name, "App");
271 assert_eq!(app_component.file_path, app_path);
272
273 let header_component = &graph.graph[header];
274 assert_eq!(header_component.name, "Header");
275 assert_eq!(
276 header_component.file_path,
277 PathBuf::from("/path/to/Header.tsx")
278 );
279
280 let footer_component = &graph.graph[footer];
281 assert_eq!(footer_component.name, "Footer");
282 assert_eq!(
283 footer_component.file_path,
284 PathBuf::from("/path/to/Footer.tsx")
285 );
286
287 let content_component = &graph.graph[content];
288 assert_eq!(content_component.name, "Content");
289 assert_eq!(
290 content_component.file_path,
291 PathBuf::from("/path/to/Content.tsx")
292 );
293
294 assert!(graph.graph.contains_edge(app, header));
295 assert!(graph.graph.contains_edge(app, footer));
296 assert!(graph.graph.contains_edge(app, content));
297 }
298
299 #[test]
300 fn test_cyclic_dependency() {
301 let mut graph = ComponentGraph::new();
302
303 let component_a = graph.add_component(
304 "ComponentA".to_string(),
305 PathBuf::from("/path/to/ComponentA.tsx"),
306 );
307 let component_b = graph.add_component(
308 "ComponentB".to_string(),
309 PathBuf::from("/path/to/ComponentB.tsx"),
310 );
311
312 graph.add_child(
313 ("ComponentA", &PathBuf::from("/path/to/ComponentA.tsx")),
314 ("ComponentB", &PathBuf::from("/path/to/ComponentB.tsx")),
315 );
316 graph.add_child(
317 ("ComponentB", &PathBuf::from("/path/to/ComponentB.tsx")),
318 ("ComponentA", &PathBuf::from("/path/to/ComponentA.tsx")),
319 );
320
321 assert_eq!(graph.graph.node_count(), 2);
322
323 assert_eq!(graph.graph.edge_count(), 2);
324 assert!(graph.graph.contains_edge(component_a, component_b));
325 assert!(graph.graph.contains_edge(component_b, component_a));
326 }
327
328 #[test]
329 fn test_to_serializable() {
330 let mut graph = ComponentGraph::new();
331 graph.add_component(
332 "ComponentA".to_string(),
333 PathBuf::from("/path/to/ComponentA.tsx"),
334 );
335 graph.add_component(
336 "ComponentB".to_string(),
337 PathBuf::from("/path/to/ComponentB.tsx"),
338 );
339
340 graph.add_child(
341 ("ComponentA", &PathBuf::from("/path/to/ComponentA.tsx")),
342 ("ComponentB", &PathBuf::from("/path/to/ComponentB.tsx")),
343 );
344
345 let serializable = graph.to_serializable();
346 assert_eq!(serializable.nodes.len(), 2);
347 assert_eq!(serializable.edges.len(), 1);
348 }
349}