1use crate::ast::{Edge, EdgeKind, Node, NodeId, NodeKind};
7use crate::error::Result;
8use dashmap::DashMap;
9use regex;
10use serde::{Deserialize, Serialize};
11use std::collections::{HashMap, HashSet, VecDeque};
12use std::path::PathBuf;
13use std::sync::Arc;
14
15#[derive(Debug)]
17pub struct GraphStore {
18 nodes: Arc<DashMap<NodeId, Node>>,
20 outgoing_edges: Arc<DashMap<NodeId, Vec<Edge>>>,
22 incoming_edges: Arc<DashMap<NodeId, Vec<Edge>>>,
24 file_index: Arc<DashMap<PathBuf, Vec<NodeId>>>,
26 symbol_index: Arc<DashMap<String, Vec<NodeId>>>,
28 kind_index: Arc<DashMap<NodeKind, Vec<NodeId>>>,
30}
31
32impl GraphStore {
33 pub fn new() -> Self {
35 Self {
36 nodes: Arc::new(DashMap::new()),
37 outgoing_edges: Arc::new(DashMap::new()),
38 incoming_edges: Arc::new(DashMap::new()),
39 file_index: Arc::new(DashMap::new()),
40 symbol_index: Arc::new(DashMap::new()),
41 kind_index: Arc::new(DashMap::new()),
42 }
43 }
44
45 pub fn add_node(&self, node: Node) {
47 let node_id = node.id;
48
49 self.file_index
51 .entry(node.file.clone())
52 .or_default()
53 .push(node_id);
54
55 self.symbol_index
57 .entry(node.name.clone())
58 .or_default()
59 .push(node_id);
60
61 self.kind_index.entry(node.kind).or_default().push(node_id);
63
64 self.nodes.insert(node_id, node);
66 }
67
68 pub fn add_edge(&self, edge: Edge) {
70 self.outgoing_edges
72 .entry(edge.source)
73 .or_default()
74 .push(edge.clone());
75
76 self.incoming_edges
78 .entry(edge.target)
79 .or_default()
80 .push(edge);
81 }
82
83 pub fn get_node(&self, node_id: &NodeId) -> Option<Node> {
85 self.nodes.get(node_id).map(|entry| entry.clone())
86 }
87
88 pub fn get_nodes_in_file(&self, file_path: &PathBuf) -> Vec<Node> {
90 if let Some(node_ids) = self.file_index.get(file_path) {
91 node_ids.iter().filter_map(|id| self.get_node(id)).collect()
92 } else {
93 Vec::new()
94 }
95 }
96
97 pub fn get_nodes_by_name(&self, name: &str) -> Vec<Node> {
99 if let Some(node_ids) = self.symbol_index.get(name) {
100 node_ids.iter().filter_map(|id| self.get_node(id)).collect()
101 } else {
102 Vec::new()
103 }
104 }
105
106 pub fn get_nodes_by_kind(&self, kind: NodeKind) -> Vec<Node> {
108 if let Some(node_ids) = self.kind_index.get(&kind) {
109 node_ids.iter().filter_map(|id| self.get_node(id)).collect()
110 } else {
111 Vec::new()
112 }
113 }
114
115 pub fn get_outgoing_edges(&self, node_id: &NodeId) -> Vec<Edge> {
117 self.outgoing_edges
118 .get(node_id)
119 .map(|edges| edges.clone())
120 .unwrap_or_default()
121 }
122
123 pub fn get_incoming_edges(&self, node_id: &NodeId) -> Vec<Edge> {
125 self.incoming_edges
126 .get(node_id)
127 .map(|edges| edges.clone())
128 .unwrap_or_default()
129 }
130
131 pub fn get_stats(&self) -> GraphStats {
133 GraphStats {
134 total_nodes: self.nodes.len(),
135 total_edges: self.outgoing_edges.iter().map(|entry| entry.len()).sum(),
136 total_files: self.file_index.len(),
137 nodes_by_kind: self
138 .kind_index
139 .iter()
140 .map(|entry| (*entry.key(), entry.len()))
141 .collect(),
142 }
143 }
144
145 pub fn clear(&self) {
147 self.nodes.clear();
148 self.outgoing_edges.clear();
149 self.incoming_edges.clear();
150 self.file_index.clear();
151 self.symbol_index.clear();
152 self.kind_index.clear();
153 }
154
155 pub fn remove_node(&self, node_id: &NodeId) -> Option<Node> {
157 if let Some((_, node)) = self.nodes.remove(node_id) {
158 if let Some(mut file_nodes) = self.file_index.get_mut(&node.file) {
160 file_nodes.retain(|id| id != node_id);
161 }
162
163 if let Some(mut symbol_nodes) = self.symbol_index.get_mut(&node.name) {
164 symbol_nodes.retain(|id| id != node_id);
165 }
166
167 if let Some(mut kind_nodes) = self.kind_index.get_mut(&node.kind) {
168 kind_nodes.retain(|id| id != node_id);
169 }
170
171 self.outgoing_edges.remove(node_id);
173 self.incoming_edges.remove(node_id);
174
175 for mut edges in self.outgoing_edges.iter_mut() {
177 edges.retain(|edge| edge.target != *node_id);
178 }
179
180 for mut edges in self.incoming_edges.iter_mut() {
181 edges.retain(|edge| edge.source != *node_id);
182 }
183
184 Some(node)
185 } else {
186 None
187 }
188 }
189
190 pub fn get_all_files(&self) -> Vec<PathBuf> {
192 self.file_index
193 .iter()
194 .map(|entry| entry.key().clone())
195 .collect()
196 }
197
198 pub fn iter_file_index(&self) -> impl Iterator<Item = (PathBuf, Vec<NodeId>)> + '_ {
200 self.file_index
201 .iter()
202 .map(|entry| (entry.key().clone(), entry.value().clone()))
203 }
204
205 pub fn iter_symbol_index(&self) -> impl Iterator<Item = (String, Vec<NodeId>)> + '_ {
207 self.symbol_index
208 .iter()
209 .map(|entry| (entry.key().clone(), entry.value().clone()))
210 }
211
212 pub fn get_nodes_by_file(&self, file_path: &PathBuf) -> Vec<NodeId> {
214 self.file_index
215 .get(file_path)
216 .map(|ids| ids.clone())
217 .unwrap_or_default()
218 }
219
220 pub fn get_node_ids_by_name(&self, name: &str) -> Vec<NodeId> {
222 self.symbol_index
223 .get(name)
224 .map(|ids| ids.clone())
225 .unwrap_or_default()
226 }
227}
228
229impl Default for GraphStore {
230 fn default() -> Self {
231 Self::new()
232 }
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct GraphStats {
238 pub total_nodes: usize,
240 pub total_edges: usize,
242 pub total_files: usize,
244 pub nodes_by_kind: HashMap<NodeKind, usize>,
246}
247
248pub struct GraphQuery {
250 graph: Arc<GraphStore>,
251}
252
253impl GraphQuery {
254 pub fn new(graph: Arc<GraphStore>) -> Self {
256 Self { graph }
257 }
258
259 pub fn find_path(
261 &self,
262 source: &NodeId,
263 target: &NodeId,
264 max_depth: Option<usize>,
265 ) -> Result<Option<PathResult>> {
266 let max_depth = max_depth.unwrap_or(10);
267
268 if source == target {
269 return Ok(Some(PathResult {
270 source: *source,
271 target: *target,
272 path: vec![*source],
273 distance: 0,
274 edges: Vec::new(),
275 }));
276 }
277
278 let mut queue = VecDeque::new();
279 let mut visited = HashSet::new();
280 let mut parent = HashMap::new();
281 let mut edge_map = HashMap::new();
282
283 queue.push_back((*source, 0));
284 visited.insert(*source);
285
286 while let Some((current, depth)) = queue.pop_front() {
287 if depth >= max_depth {
288 continue;
289 }
290
291 for edge in self.graph.get_outgoing_edges(¤t) {
292 if !visited.contains(&edge.target) {
293 visited.insert(edge.target);
294 parent.insert(edge.target, current);
295 edge_map.insert(edge.target, edge.clone());
296 queue.push_back((edge.target, depth + 1));
297
298 if edge.target == *target {
299 let mut path = Vec::new();
301 let mut edges = Vec::new();
302 let mut current_node = *target;
303
304 path.push(current_node);
306 while let Some(&prev) = parent.get(¤t_node) {
307 if let Some(edge) = edge_map.get(¤t_node) {
308 edges.push(edge.clone());
309 }
310 current_node = prev;
311 path.push(current_node);
312 }
313
314 path.reverse();
316 edges.reverse();
317
318 let distance = path.len() - 1; return Ok(Some(PathResult {
321 source: *source,
322 target: *target,
323 path,
324 distance,
325 edges,
326 }));
327 }
328 }
329 }
330 }
331
332 Ok(None)
333 }
334
335 pub fn find_references(&self, node_id: &NodeId) -> Result<Vec<SymbolReference>> {
337 let mut references = Vec::new();
338
339 for edge in self.graph.get_incoming_edges(node_id) {
340 if let Some(source_node) = self.graph.get_node(&edge.source) {
341 references.push(SymbolReference {
342 location: ReferenceLocation {
343 file: source_node.file.clone(),
344 span: source_node.span.clone(),
345 },
346 source_node,
347 edge_kind: edge.kind,
348 });
349 }
350 }
351
352 Ok(references)
353 }
354
355 pub fn find_dependencies(
357 &self,
358 node_id: &NodeId,
359 dependency_type: DependencyType,
360 ) -> Result<Vec<SymbolDependency>> {
361 let mut dependencies = Vec::new();
362
363 for edge in self.graph.get_outgoing_edges(node_id) {
364 let include_edge = match dependency_type {
365 DependencyType::Direct => true,
366 DependencyType::Calls => matches!(edge.kind, EdgeKind::Calls),
367 DependencyType::Imports => matches!(edge.kind, EdgeKind::Imports),
368 DependencyType::Reads => matches!(edge.kind, EdgeKind::Reads),
369 DependencyType::Writes => matches!(edge.kind, EdgeKind::Writes),
370 };
371
372 if include_edge {
373 if let Some(target_node) = self.graph.get_node(&edge.target) {
374 dependencies.push(SymbolDependency {
375 target_node,
376 edge_kind: edge.kind,
377 dependency_type: dependency_type.clone(),
378 });
379 }
380 }
381 }
382
383 Ok(dependencies)
384 }
385
386 pub fn search_symbols(
388 &self,
389 pattern: &str,
390 symbol_types: Option<Vec<NodeKind>>,
391 limit: Option<usize>,
392 ) -> Result<Vec<SymbolInfo>> {
393 let limit = limit.unwrap_or(50);
394 let mut results = Vec::new();
395
396 let regex_result = regex::Regex::new(pattern);
398 let use_regex = regex_result.is_ok();
399 let regex = regex_result.ok();
400
401 for entry in self.graph.symbol_index.iter() {
402 let symbol_name = entry.key();
403
404 let matches = if use_regex {
405 regex.as_ref().unwrap().is_match(symbol_name)
407 } else {
408 symbol_name.to_lowercase().contains(&pattern.to_lowercase())
410 };
411
412 if matches {
413 for node_id in entry.value() {
414 if let Some(node) = self.graph.get_node(node_id) {
415 if let Some(ref types) = symbol_types {
417 if !types.contains(&node.kind) {
418 continue;
419 }
420 }
421
422 results.push(SymbolInfo {
423 node,
424 references_count: self.graph.get_incoming_edges(node_id).len(),
425 dependencies_count: self.graph.get_outgoing_edges(node_id).len(),
426 });
427
428 if results.len() >= limit {
429 break;
430 }
431 }
432 }
433
434 if results.len() >= limit {
435 break;
436 }
437 }
438 }
439
440 Ok(results)
441 }
442
443 pub fn search_symbols_with_inheritance(
445 &self,
446 pattern: &str,
447 symbol_types: Option<Vec<NodeKind>>,
448 inheritance_filters: Option<Vec<InheritanceFilter>>,
449 limit: Option<usize>,
450 ) -> Result<Vec<SymbolInfo>> {
451 let mut results = self.search_symbols(pattern, symbol_types, limit)?;
452
453 if let Some(filters) = inheritance_filters {
454 results.retain(|symbol_info| {
455 filters
456 .iter()
457 .any(|filter| self.matches_inheritance_filter(&symbol_info.node, filter))
458 });
459 }
460
461 Ok(results)
462 }
463
464 fn matches_inheritance_filter(&self, node: &Node, filter: &InheritanceFilter) -> bool {
466 match filter {
467 InheritanceFilter::InheritsFrom(base_class) => {
468 self.inherits_from(&node.id, base_class).unwrap_or(false)
469 }
470 InheritanceFilter::HasMetaclass(metaclass) => {
471 self.has_metaclass(&node.id, metaclass).unwrap_or(false)
472 }
473 InheritanceFilter::UsesMixin(mixin) => {
474 self.uses_mixin(&node.id, mixin).unwrap_or(false)
475 }
476 }
477 }
478
479 pub fn get_inheritance_info(&self, node_id: &NodeId) -> Result<InheritanceInfo> {
481 let node = self
482 .graph
483 .get_node(node_id)
484 .ok_or_else(|| crate::error::Error::node_not_found(node_id.to_hex()))?;
485
486 if !matches!(node.kind, NodeKind::Class) {
487 return Ok(InheritanceInfo::default());
488 }
489
490 let base_classes = self.get_base_classes(node_id)?;
491 let subclasses = self.get_subclasses(node_id)?;
492 let metaclass = self.get_metaclass(node_id)?;
493 let mixins = self.get_mixins(node_id)?;
494 let mro = self.calculate_method_resolution_order(node_id)?;
495 let dynamic_attributes = self.get_dynamic_attributes(node_id)?;
496
497 Ok(InheritanceInfo {
498 class_name: node.name.clone(),
499 base_classes,
500 subclasses,
501 metaclass,
502 mixins,
503 method_resolution_order: mro,
504 dynamic_attributes,
505 is_metaclass: self.is_metaclass(node_id)?,
506 inheritance_chain: self.get_full_inheritance_chain(node_id)?,
507 })
508 }
509
510 pub fn get_base_classes(&self, node_id: &NodeId) -> Result<Vec<InheritanceRelation>> {
512 let mut base_classes = Vec::new();
513
514 for edge in self.graph.get_outgoing_edges(node_id) {
515 if matches!(edge.kind, EdgeKind::Extends) {
516 if let Some(parent_node) = self.graph.get_node(&edge.target) {
517 let is_metaclass = parent_node
518 .metadata
519 .get("is_metaclass")
520 .and_then(|v| v.as_bool())
521 .unwrap_or(false);
522
523 base_classes.push(InheritanceRelation {
524 class_name: parent_node.name.clone(),
525 node_id: parent_node.id,
526 relationship_type: if is_metaclass {
527 "metaclass".to_string()
528 } else {
529 "extends".to_string()
530 },
531 file: parent_node.file.clone(),
532 span: parent_node.span.clone(),
533 });
534 }
535 }
536 }
537
538 Ok(base_classes)
539 }
540
541 pub fn get_subclasses(&self, node_id: &NodeId) -> Result<Vec<InheritanceRelation>> {
543 let mut subclasses = Vec::new();
544
545 for edge in self.graph.get_incoming_edges(node_id) {
546 if matches!(edge.kind, EdgeKind::Extends) {
547 if let Some(child_node) = self.graph.get_node(&edge.source) {
548 subclasses.push(InheritanceRelation {
549 class_name: child_node.name.clone(),
550 node_id: child_node.id,
551 relationship_type: "extends".to_string(),
552 file: child_node.file.clone(),
553 span: child_node.span.clone(),
554 });
555 }
556 }
557 }
558
559 Ok(subclasses)
560 }
561
562 pub fn get_metaclass(&self, node_id: &NodeId) -> Result<Option<InheritanceRelation>> {
564 for edge in self.graph.get_outgoing_edges(node_id) {
565 if matches!(edge.kind, EdgeKind::Extends) {
566 if let Some(parent_node) = self.graph.get_node(&edge.target) {
567 let is_metaclass = parent_node
568 .metadata
569 .get("is_metaclass")
570 .and_then(|v| v.as_bool())
571 .unwrap_or(false);
572
573 if is_metaclass {
574 return Ok(Some(InheritanceRelation {
575 class_name: parent_node.name.clone(),
576 node_id: parent_node.id,
577 relationship_type: "metaclass".to_string(),
578 file: parent_node.file.clone(),
579 span: parent_node.span.clone(),
580 }));
581 }
582 }
583 }
584 }
585
586 Ok(None)
587 }
588
589 pub fn get_mixins(&self, node_id: &NodeId) -> Result<Vec<InheritanceRelation>> {
591 let mut mixins = Vec::new();
592
593 for edge in self.graph.get_outgoing_edges(node_id) {
594 if matches!(edge.kind, EdgeKind::Extends) {
595 if let Some(parent_node) = self.graph.get_node(&edge.target) {
596 if parent_node.name.ends_with("Mixin")
598 || parent_node.name.to_lowercase().contains("mixin")
599 {
600 mixins.push(InheritanceRelation {
601 class_name: parent_node.name.clone(),
602 node_id: parent_node.id,
603 relationship_type: "mixin".to_string(),
604 file: parent_node.file.clone(),
605 span: parent_node.span.clone(),
606 });
607 }
608 }
609 }
610 }
611
612 Ok(mixins)
613 }
614
615 pub fn calculate_method_resolution_order(&self, node_id: &NodeId) -> Result<Vec<String>> {
617 let mut mro = Vec::new();
618 let mut visited = HashSet::new();
619
620 self.collect_mro_recursive(node_id, &mut mro, &mut visited)?;
621
622 Ok(mro)
623 }
624
625 fn collect_mro_recursive(
627 &self,
628 node_id: &NodeId,
629 mro: &mut Vec<String>,
630 visited: &mut HashSet<NodeId>,
631 ) -> Result<()> {
632 if visited.contains(node_id) {
633 return Ok(());
634 }
635
636 visited.insert(*node_id);
637
638 if let Some(node) = self.graph.get_node(node_id) {
639 mro.push(node.name.clone());
640
641 for edge in self.graph.get_outgoing_edges(node_id) {
643 if matches!(edge.kind, EdgeKind::Extends) {
644 self.collect_mro_recursive(&edge.target, mro, visited)?;
645 }
646 }
647 }
648
649 Ok(())
650 }
651
652 pub fn get_dynamic_attributes(&self, node_id: &NodeId) -> Result<Vec<DynamicAttribute>> {
654 let mut attributes = Vec::new();
655
656 if let Some(metaclass) = self.get_metaclass(node_id)? {
658 let common_metaclass_attrs = vec![
660 "_registry",
661 "_instances",
662 "_subclasses",
663 "_handlers",
664 "_processors",
665 "_mixins",
666 "_plugins",
667 "_decorators",
668 "_metadata",
669 ];
670
671 for attr_name in common_metaclass_attrs {
672 attributes.push(DynamicAttribute {
673 name: attr_name.to_string(),
674 created_by: format!("metaclass:{}", metaclass.class_name),
675 attribute_type: "dynamic".to_string(),
676 });
677 }
678 }
679
680 Ok(attributes)
681 }
682
683 pub fn is_metaclass(&self, node_id: &NodeId) -> Result<bool> {
685 if let Some(node) = self.graph.get_node(node_id) {
686 if let Some(is_meta) = node.metadata.get("is_metaclass").and_then(|v| v.as_bool()) {
688 return Ok(is_meta);
689 }
690
691 let inherits_from_type = self.inherits_from(node_id, "type")?;
693 let name_suggests_metaclass =
694 node.name.contains("Meta") || node.name.ends_with("Metaclass");
695
696 Ok(inherits_from_type || name_suggests_metaclass)
697 } else {
698 Ok(false)
699 }
700 }
701
702 pub fn get_full_inheritance_chain(&self, node_id: &NodeId) -> Result<Vec<String>> {
704 let mut chain = Vec::new();
705 let mut visited = HashSet::new();
706
707 self.collect_inheritance_chain_recursive(node_id, &mut chain, &mut visited)?;
708
709 Ok(chain)
710 }
711
712 fn collect_inheritance_chain_recursive(
714 &self,
715 node_id: &NodeId,
716 chain: &mut Vec<String>,
717 visited: &mut HashSet<NodeId>,
718 ) -> Result<()> {
719 if visited.contains(node_id) {
720 return Ok(());
721 }
722
723 visited.insert(*node_id);
724
725 if let Some(node) = self.graph.get_node(node_id) {
726 chain.push(node.name.clone());
727
728 for edge in self.graph.get_outgoing_edges(node_id) {
730 if matches!(edge.kind, EdgeKind::Extends) {
731 self.collect_inheritance_chain_recursive(&edge.target, chain, visited)?;
732 }
733 }
734 }
735
736 Ok(())
737 }
738
739 pub fn inherits_from(&self, node_id: &NodeId, base_class_name: &str) -> Result<bool> {
741 let mut visited = HashSet::new();
742 self.inherits_from_recursive(node_id, base_class_name, &mut visited)
743 }
744
745 fn inherits_from_recursive(
747 &self,
748 node_id: &NodeId,
749 base_class_name: &str,
750 visited: &mut HashSet<NodeId>,
751 ) -> Result<bool> {
752 if visited.contains(node_id) {
753 return Ok(false);
754 }
755
756 visited.insert(*node_id);
757
758 for edge in self.graph.get_outgoing_edges(node_id) {
759 if matches!(edge.kind, EdgeKind::Extends) {
760 if let Some(parent_node) = self.graph.get_node(&edge.target) {
761 if parent_node.name == base_class_name {
762 return Ok(true);
763 }
764
765 if self.inherits_from_recursive(&edge.target, base_class_name, visited)? {
766 return Ok(true);
767 }
768 }
769 }
770 }
771
772 Ok(false)
773 }
774
775 pub fn has_metaclass(&self, node_id: &NodeId, metaclass_name: &str) -> Result<bool> {
777 if let Some(metaclass) = self.get_metaclass(node_id)? {
778 Ok(metaclass.class_name == metaclass_name)
779 } else {
780 Ok(false)
781 }
782 }
783
784 pub fn uses_mixin(&self, node_id: &NodeId, mixin_name: &str) -> Result<bool> {
786 let mixins = self.get_mixins(node_id)?;
787 Ok(mixins.iter().any(|m| m.class_name == mixin_name))
788 }
789}
790
791#[derive(Debug, Clone, Serialize, Deserialize)]
793pub struct PathResult {
794 pub source: NodeId,
796 pub target: NodeId,
798 pub path: Vec<NodeId>,
800 pub distance: usize,
802 pub edges: Vec<Edge>,
804}
805
806#[derive(Debug, Clone, Serialize, Deserialize)]
808pub struct SymbolInfo {
809 pub node: Node,
811 pub references_count: usize,
813 pub dependencies_count: usize,
815}
816
817#[derive(Debug, Clone, Serialize, Deserialize)]
819pub struct SymbolReference {
820 pub source_node: Node,
822 pub edge_kind: EdgeKind,
824 pub location: ReferenceLocation,
826}
827
828#[derive(Debug, Clone, Serialize, Deserialize)]
830pub struct ReferenceLocation {
831 pub file: PathBuf,
833 pub span: crate::ast::Span,
835}
836
837#[derive(Debug, Clone, Serialize, Deserialize)]
839pub struct SymbolDependency {
840 pub target_node: Node,
842 pub edge_kind: EdgeKind,
844 pub dependency_type: DependencyType,
846}
847
848#[derive(Debug, Clone, Serialize, Deserialize)]
850pub enum DependencyType {
851 Direct,
853 Calls,
855 Imports,
857 Reads,
859 Writes,
861}
862
863#[derive(Debug, Clone, Serialize, Deserialize)]
865pub enum InheritanceFilter {
866 InheritsFrom(String),
868 HasMetaclass(String),
870 UsesMixin(String),
872}
873
874#[derive(Debug, Clone, Serialize, Deserialize, Default)]
876pub struct InheritanceInfo {
877 pub class_name: String,
879 pub base_classes: Vec<InheritanceRelation>,
881 pub subclasses: Vec<InheritanceRelation>,
883 pub metaclass: Option<InheritanceRelation>,
885 pub mixins: Vec<InheritanceRelation>,
887 pub method_resolution_order: Vec<String>,
889 pub dynamic_attributes: Vec<DynamicAttribute>,
891 pub is_metaclass: bool,
893 pub inheritance_chain: Vec<String>,
895}
896
897#[derive(Debug, Clone, Serialize, Deserialize)]
899pub struct InheritanceRelation {
900 pub class_name: String,
902 pub node_id: NodeId,
904 pub relationship_type: String,
906 pub file: PathBuf,
908 pub span: crate::ast::Span,
910}
911
912#[derive(Debug, Clone, Serialize, Deserialize)]
914pub struct DynamicAttribute {
915 pub name: String,
917 pub created_by: String,
919 pub attribute_type: String,
921}
922
923#[cfg(test)]
924mod tests {
925 use super::*;
926 use crate::ast::{Language, Span};
927
928 fn create_test_node(name: &str, kind: NodeKind, file: &str) -> Node {
929 Node::new(
930 "test_repo",
931 kind,
932 name.to_string(),
933 Language::Python,
934 PathBuf::from(file),
935 Span::new(0, 10, 1, 1, 1, 11),
936 )
937 }
938
939 fn create_test_node_with_span(
940 name: &str,
941 kind: NodeKind,
942 file: &str,
943 start_byte: usize,
944 end_byte: usize,
945 ) -> Node {
946 Node::new(
947 "test_repo",
948 kind,
949 name.to_string(),
950 Language::Python,
951 PathBuf::from(file),
952 Span::new(start_byte, end_byte, 1, 1, 1, 11),
953 )
954 }
955
956 #[test]
957 fn test_graph_store_basic_operations() {
958 let graph = GraphStore::new();
959
960 let node1 = create_test_node("function1", NodeKind::Function, "test.py");
961 let node2 = create_test_node("function2", NodeKind::Function, "test.py");
962
963 graph.add_node(node1.clone());
964 graph.add_node(node2.clone());
965
966 assert_eq!(graph.get_node(&node1.id).unwrap().id, node1.id);
967 assert_eq!(graph.get_node(&node2.id).unwrap().id, node2.id);
968
969 let edge = Edge::new(node1.id, node2.id, EdgeKind::Calls);
970 graph.add_edge(edge.clone());
971
972 let outgoing = graph.get_outgoing_edges(&node1.id);
973 assert_eq!(outgoing.len(), 1, "Should have 1 items");
974 assert_eq!(outgoing[0], edge);
975
976 let incoming = graph.get_incoming_edges(&node2.id);
977 assert_eq!(incoming.len(), 1, "Should have 1 items");
978 assert_eq!(incoming[0], edge);
979 }
980
981 #[test]
982 fn test_graph_query_path_finding() {
983 let graph = Arc::new(GraphStore::new());
984 let query = GraphQuery::new(graph.clone());
985
986 let node1 = create_test_node_with_span("function1", NodeKind::Function, "test.py", 0, 10);
987 let node2 = create_test_node_with_span("function2", NodeKind::Function, "test.py", 20, 30);
988 let node3 = create_test_node_with_span("function3", NodeKind::Function, "test.py", 40, 50);
989
990 graph.add_node(node1.clone());
991 graph.add_node(node2.clone());
992 graph.add_node(node3.clone());
993
994 graph.add_edge(Edge::new(node1.id, node2.id, EdgeKind::Calls));
995 graph.add_edge(Edge::new(node2.id, node3.id, EdgeKind::Calls));
996
997 let path = query.find_path(&node1.id, &node3.id, None).unwrap();
998 assert!(path.is_some(), "Should have value");
999
1000 let path = path.unwrap();
1001 assert_eq!(path.distance, 2);
1002 assert_eq!(path.path, vec![node1.id, node2.id, node3.id]);
1003 }
1004
1005 #[test]
1006 fn test_symbol_search() {
1007 let graph = Arc::new(GraphStore::new());
1008 let query = GraphQuery::new(graph.clone());
1009
1010 let node1 = create_test_node("test_function", NodeKind::Function, "test.py");
1011 let node2 = create_test_node("another_function", NodeKind::Function, "test.py");
1012 let node3 = create_test_node("test_class", NodeKind::Class, "test.py");
1013
1014 graph.add_node(node1.clone());
1015 graph.add_node(node2.clone());
1016 graph.add_node(node3.clone());
1017
1018 let results = query.search_symbols("test", None, None).unwrap();
1019 assert_eq!(results.len(), 2, "Should have 2 items"); let results = query
1022 .search_symbols("test", Some(vec![NodeKind::Function]), None)
1023 .unwrap();
1024 assert_eq!(results.len(), 1, "Should have 1 items"); }
1026
1027 #[test]
1028 fn test_symbol_search_regex() {
1029 let graph = Arc::new(GraphStore::new());
1030 let query = GraphQuery::new(graph.clone());
1031
1032 let agent_node = create_test_node("Agent", NodeKind::Class, "agent.py");
1033 let user_agent_node = create_test_node("UserAgent", NodeKind::Class, "user_agent.py");
1034 let guild_manager_agent_node = create_test_node(
1035 "GuildManagerAgent",
1036 NodeKind::Class,
1037 "guild_manager_agent.py",
1038 );
1039 let other_node = create_test_node("ProcessAgent", NodeKind::Function, "process.py");
1040
1041 graph.add_node(agent_node.clone());
1042 graph.add_node(user_agent_node.clone());
1043 graph.add_node(guild_manager_agent_node.clone());
1044 graph.add_node(other_node.clone());
1045
1046 let results = query.search_symbols("^Agent$", None, None).unwrap();
1048 assert_eq!(results.len(), 1, "Should have 1 items"); assert_eq!(results[0].node.name, "Agent");
1050
1051 let results = query.search_symbols("Agent$", None, None).unwrap();
1053 assert_eq!(results.len(), 4, "Should have 4 items"); let results = query.search_symbols("^Guild", None, None).unwrap();
1057 assert_eq!(results.len(), 1, "Should have 1 items"); assert_eq!(results[0].node.name, "GuildManagerAgent");
1059
1060 let results = query.search_symbols("[invalid", None, None).unwrap();
1062 assert_eq!(results.len(), 0, "Should have 0 items"); let results = query.search_symbols("Agent", None, None).unwrap();
1066 assert_eq!(results.len(), 4, "Should have 4 items"); }
1068}