1use std::{
2 collections::HashMap,
3 path::{Path, PathBuf},
4};
5
6use gitcortex_core::{
7 error::{GitCortexError, Result},
8 graph::{Edge, Node, NodeId, NodeMetadata, Span},
9 schema::{EdgeConfidence, EdgeKind, NodeKind, Visibility},
10};
11use tree_sitter::{Node as TsNode, Parser};
12
13use super::{capture_definition, LanguageParser, ParseResult};
14
15pub struct RustParser {
18 language: tree_sitter::Language,
19}
20
21impl RustParser {
22 pub fn new() -> Self {
23 Self {
24 language: tree_sitter_rust::LANGUAGE.into(),
25 }
26 }
27}
28
29impl Default for RustParser {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl LanguageParser for RustParser {
36 fn extensions(&self) -> &[&str] {
37 &["rs"]
38 }
39
40 fn parse(&self, path: &Path, source: &str) -> Result<ParseResult> {
41 let mut parser = Parser::new();
42 parser
43 .set_language(&self.language)
44 .map_err(|e| GitCortexError::Parse {
45 file: path.to_owned(),
46 message: e.to_string(),
47 })?;
48
49 let tree = parser
50 .parse(source, None)
51 .ok_or_else(|| GitCortexError::Parse {
52 file: path.to_owned(),
53 message: "tree-sitter returned no parse tree".into(),
54 })?;
55
56 let mut visitor = FileVisitor::new(path, source);
57 visitor.collect_names(tree.root_node());
60 visitor.visit_items(tree.root_node(), &[], None);
62 visitor.collect_imports(tree.root_node());
64
65 Ok(ParseResult {
66 nodes: visitor.nodes,
67 edges: visitor.edges,
68 deferred_calls: visitor.deferred_calls,
69 deferred_uses: visitor.deferred_uses,
70 deferred_implements: visitor.deferred_implements,
71 deferred_imports: visitor.deferred_imports,
72 deferred_inherits: Vec::new(),
73 deferred_throws: Vec::new(),
74 deferred_annotated: visitor.deferred_annotated,
75 })
76 }
77}
78
79struct FileVisitor<'src> {
82 source: &'src [u8],
83 file: PathBuf,
84 module_id: NodeId,
87 nodes: Vec<Node>,
88 edges: Vec<Edge>,
89 type_index: HashMap<String, NodeId>,
90 fn_index: HashMap<String, NodeId>,
91 deferred_calls: Vec<(NodeId, String, u32)>,
92 deferred_uses: Vec<(NodeId, String)>,
93 deferred_implements: Vec<(NodeId, String)>,
94 deferred_imports: Vec<(NodeId, String)>,
95 deferred_annotated: Vec<(NodeId, String)>,
96}
97
98impl<'src> FileVisitor<'src> {
99 fn new(file: &Path, source: &'src str) -> Self {
100 let module_id = NodeId::new();
103 let module_name = file
104 .file_stem()
105 .and_then(|s| s.to_str())
106 .unwrap_or("crate")
107 .to_owned();
108 let module_node = Node {
109 id: module_id.clone(),
110 qualified_name: module_name.clone(),
111 kind: NodeKind::Module,
112 name: module_name,
113 file: file.to_owned(),
114 span: Span {
115 start_line: 1,
116 end_line: 1,
117 },
118 metadata: NodeMetadata {
119 loc: source.lines().count() as u32,
120 visibility: Visibility::Pub,
121 ..Default::default()
122 },
123 };
124 Self {
125 source: source.as_bytes(),
126 file: file.to_owned(),
127 module_id,
128 nodes: vec![module_node],
129 edges: Vec::new(),
130 type_index: HashMap::new(),
131 fn_index: HashMap::new(),
132 deferred_calls: Vec::new(),
133 deferred_uses: Vec::new(),
134 deferred_implements: Vec::new(),
135 deferred_imports: Vec::new(),
136 deferred_annotated: Vec::new(),
137 }
138 }
139
140 fn text<'t>(&self, node: TsNode<'t>) -> &'src str {
143 node.utf8_text(self.source).unwrap_or("")
144 }
145
146 fn field_text(&self, node: TsNode<'_>, field: &str) -> Option<String> {
147 node.child_by_field_name(field)
148 .and_then(|n| n.utf8_text(self.source).ok())
149 .map(str::to_owned)
150 }
151
152 fn span(node: TsNode<'_>) -> Span {
153 Span {
154 start_line: node.start_position().row as u32 + 1,
155 end_line: node.end_position().row as u32 + 1,
156 }
157 }
158
159 fn visibility(&self, node: TsNode<'_>) -> Visibility {
160 let mut cursor = node.walk();
161 for child in node.children(&mut cursor) {
162 if child.kind() == "visibility_modifier" {
163 let t = self.text(child);
164 return if t.contains("crate") {
165 Visibility::PubCrate
166 } else {
167 Visibility::Pub
168 };
169 }
170 }
171 Visibility::Private
172 }
173
174 fn is_async(&self, node: TsNode<'_>) -> bool {
175 let mut cursor = node.walk();
176 let result = node.children(&mut cursor).any(|c| c.kind() == "async");
177 result
178 }
179
180 fn is_unsafe(&self, node: TsNode<'_>) -> bool {
181 let mut cursor = node.walk();
182 let result = node.children(&mut cursor).any(|c| c.kind() == "unsafe");
183 result
184 }
185
186 fn is_const(&self, node: TsNode<'_>) -> bool {
187 let mut cursor = node.walk();
188 let result = node.children(&mut cursor).any(|c| c.kind() == "const");
189 result
190 }
191
192 fn collect_generic_bounds(&self, node: TsNode<'_>) -> Vec<String> {
195 let Some(type_params) = node.child_by_field_name("type_parameters") else {
196 return Vec::new();
197 };
198 let mut bounds = Vec::new();
199 let mut cursor = type_params.walk();
200 for child in type_params.named_children(&mut cursor) {
201 if child.kind() == "constrained_type_parameter" {
202 let bound_text = self.text(child).to_owned();
203 if !bound_text.is_empty() {
204 bounds.push(bound_text);
205 }
206 }
207 }
208 bounds
209 }
210
211 fn collect_attributes(&self, node: TsNode<'_>) -> Vec<String> {
215 let mut attrs = Vec::new();
216 let mut cursor = node.walk();
217 let Some(parent) = node.parent() else {
222 return attrs;
223 };
224 let mut found = false;
225 let mut pending: Vec<String> = Vec::new();
226 for sibling in parent.named_children(&mut cursor) {
227 if sibling.id() == node.id() {
228 found = true;
229 break;
230 }
231 if sibling.kind() == "attribute_item" {
232 if let Some(attr_name) = self.extract_attribute_name(sibling) {
233 pending.push(attr_name);
234 }
235 } else {
236 pending.clear();
239 }
240 }
241 if found {
242 attrs.extend(pending);
243 }
244 attrs
245 }
246
247 fn extract_attribute_name(&self, attr_item: TsNode<'_>) -> Option<String> {
251 let mut cursor = attr_item.walk();
255 for child in attr_item.named_children(&mut cursor) {
256 if child.kind() == "attribute" {
257 let mut inner = child.walk();
259 let path_node = child.named_children(&mut inner).next();
260 if let Some(p) = path_node {
261 return Some(self.text(p).to_owned());
262 }
263 }
264 }
265 None
266 }
267
268 fn qualified(scope: &[String], name: &str) -> String {
269 if scope.is_empty() {
270 format!("crate::{name}")
271 } else {
272 format!("crate::{}::{name}", scope.join("::"))
273 }
274 }
275
276 fn make_node(
277 &self,
278 id: NodeId,
279 kind: NodeKind,
280 name: String,
281 scope: &[String],
282 ts_node: TsNode<'_>,
283 ) -> Node {
284 Node {
285 id,
286 qualified_name: Self::qualified(scope, &name),
287 kind,
288 name,
289 file: self.file.clone(),
290 span: Self::span(ts_node),
291 metadata: NodeMetadata {
292 loc: (ts_node.end_position().row - ts_node.start_position().row + 1) as u32,
293 visibility: self.visibility(ts_node),
294 is_async: self.is_async(ts_node),
295 is_unsafe: self.is_unsafe(ts_node),
296 is_const: self.is_const(ts_node),
297 generic_bounds: self.collect_generic_bounds(ts_node),
298 definition: capture_definition(self.source, ts_node),
299 ..Default::default()
300 },
301 }
302 }
303
304 fn type_name(&self, node: TsNode<'_>) -> Option<String> {
305 match node.kind() {
306 "type_identifier" => Some(self.text(node).to_owned()),
307 "generic_type" => node
308 .child_by_field_name("type")
309 .map(|n| self.text(n).to_owned()),
310 "scoped_type_identifier" => node
311 .child_by_field_name("name")
312 .map(|n| self.text(n).to_owned()),
313 "reference_type" => node
314 .child_by_field_name("type")
315 .and_then(|n| self.type_name(n)),
316 "mutable_specifier" => None,
317 _ => Some(self.text(node).to_owned()),
318 }
319 }
320
321 fn collect_names(&mut self, node: TsNode<'_>) {
324 let mut cursor = node.walk();
325 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
326 for child in children {
327 match child.kind() {
328 "struct_item" | "enum_item" | "trait_item" => {
329 if let Some(name) = self.field_text(child, "name") {
330 self.type_index.entry(name).or_default();
331 }
332 }
333 "function_item" => {
334 if let Some(name) = self.field_text(child, "name") {
335 self.fn_index.entry(name).or_default();
336 }
337 }
338 "impl_item" => {
339 }
343 "mod_item" => {
344 if let Some(body) = child.child_by_field_name("body") {
345 self.collect_names(body);
346 }
347 }
348 _ => {}
349 }
350 }
351 }
352
353 fn visit_items(&mut self, parent: TsNode<'_>, scope: &[String], container_id: Option<NodeId>) {
356 let mut cursor = parent.walk();
357 let children: Vec<TsNode<'_>> = parent.named_children(&mut cursor).collect();
358 for child in children {
359 self.visit_item(child, scope, container_id.clone());
360 }
361 }
362
363 fn visit_item(&mut self, node: TsNode<'_>, scope: &[String], container_id: Option<NodeId>) {
364 match node.kind() {
365 "function_item" => self.visit_function(node, scope, container_id, NodeKind::Function),
366 "struct_item" => self.visit_type_item(node, scope, container_id, NodeKind::Struct),
367 "enum_item" => self.visit_type_item(node, scope, container_id, NodeKind::Enum),
368 "trait_item" => self.visit_trait(node, scope, container_id),
369 "impl_item" => self.visit_impl(node, scope),
370 "mod_item" => self.visit_mod(node, scope, container_id),
371 "const_item" | "static_item" => self.visit_const(node, scope, container_id),
372 "type_item" => self.visit_type_alias(node, scope, container_id),
373 "macro_definition" => self.visit_macro_def(node, scope, container_id),
374 _ => {}
375 }
376 }
377
378 fn visit_function(
379 &mut self,
380 node: TsNode<'_>,
381 scope: &[String],
382 container_id: Option<NodeId>,
383 kind: NodeKind,
384 ) {
385 let Some(name) = self.field_text(node, "name") else {
386 return;
387 };
388 let id = if kind == NodeKind::Method {
392 NodeId::new()
393 } else {
394 self.fn_index
395 .get(&name)
396 .cloned()
397 .unwrap_or_else(NodeId::new)
398 };
399 let mut graph_node = self.make_node(id.clone(), kind, name, scope, node);
400
401 if let Some(body) = node.child_by_field_name("body") {
402 graph_node.metadata.lld.complexity = Some(super::cyclomatic_complexity(
403 body,
404 &super::complexity::rust_decision,
405 ));
406 }
407
408 if let Some(cid) = container_id {
409 self.edges.push(Edge {
410 src: cid,
411 dst: id.clone(),
412 kind: EdgeKind::Contains,
413 line: None,
414 confidence: EdgeConfidence::Extracted,
415 });
416 }
417
418 self.collect_uses_edges(node, &id);
420
421 for attr_name in self.collect_attributes(node) {
423 self.deferred_annotated.push((id.clone(), attr_name));
424 }
425
426 self.nodes.push(graph_node);
427
428 if let Some(body) = node.child_by_field_name("body") {
430 self.collect_calls(body, &id);
431 }
432 }
433
434 fn collect_uses_edges(&mut self, fn_node: TsNode<'_>, fn_id: &NodeId) {
437 let mut type_names: Vec<String> = Vec::new();
438
439 if let Some(params) = fn_node.child_by_field_name("parameters") {
440 let mut cursor = params.walk();
441 for param in params.named_children(&mut cursor) {
442 if param.kind() == "parameter" {
443 if let Some(type_node) = param.child_by_field_name("type") {
444 if let Some(tname) = self.type_name(type_node) {
445 type_names.push(tname);
446 }
447 }
448 }
449 }
450 }
451
452 if let Some(ret_type) = fn_node.child_by_field_name("return_type") {
453 if let Some(tname) = self.type_name(ret_type) {
454 type_names.push(tname);
455 }
456 }
457
458 for tname in type_names {
459 if let Some(tid) = self.type_index.get(&tname).cloned() {
460 self.edges.push(Edge {
461 src: fn_id.clone(),
462 dst: tid,
463 kind: EdgeKind::Uses,
464 line: None,
465 confidence: EdgeConfidence::Extracted,
466 });
467 } else if !tname.is_empty()
468 && !is_primitive(&tname)
469 && !self
470 .deferred_uses
471 .iter()
472 .any(|(id, n)| id == fn_id && n == &tname)
473 {
474 self.deferred_uses.push((fn_id.clone(), tname));
475 }
476 }
477 }
478
479 fn collect_calls(&mut self, node: TsNode<'_>, caller_id: &NodeId) {
485 match node.kind() {
486 "call_expression" => {
487 if let Some(callee) = self.callee_name(node) {
488 self.record_call(caller_id.clone(), callee, Self::span(node).start_line);
489 }
490 if let Some(args) = node.child_by_field_name("arguments") {
491 self.collect_calls(args, caller_id);
492 }
493 if let Some(func) = node.child_by_field_name("function") {
498 if let Some(value) = func.child_by_field_name("value") {
499 self.collect_calls(value, caller_id);
500 }
501 }
502 }
503 "method_call_expression" => {
504 if let Some(name_node) = node.child_by_field_name("name") {
507 let method = self.text(name_node).to_owned();
508 self.record_call(caller_id.clone(), method, Self::span(node).start_line);
509 }
510 if let Some(args) = node.child_by_field_name("arguments") {
511 self.collect_calls(args, caller_id);
512 }
513 if let Some(recv) = node.child_by_field_name("receiver") {
514 self.collect_calls(recv, caller_id);
515 }
516 }
517 _ => {
518 let mut cursor = node.walk();
519 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
520 for child in children {
521 self.collect_calls(child, caller_id);
522 }
523 }
524 }
525 }
526
527 fn callee_name(&self, call_expr: TsNode<'_>) -> Option<String> {
529 let func = call_expr.child_by_field_name("function")?;
530 match func.kind() {
531 "identifier" => Some(self.text(func).to_owned()),
532 "scoped_identifier" => func
533 .child_by_field_name("name")
534 .and_then(|n| n.utf8_text(self.source).ok())
535 .map(str::to_owned),
536 "field_expression" => func
537 .child_by_field_name("field")
538 .and_then(|n| n.utf8_text(self.source).ok())
539 .map(str::to_owned),
540 _ => None,
541 }
542 }
543
544 fn record_call(&mut self, caller_id: NodeId, callee_name: String, line: u32) {
547 if callee_name.is_empty() {
548 return;
549 }
550 if let Some(callee_id) = self.fn_index.get(&callee_name).cloned() {
551 let edge = Edge::call(caller_id, callee_id, line);
552 if !self.edges.contains(&edge) {
553 self.edges.push(edge);
554 }
555 } else if !self
556 .deferred_calls
557 .iter()
558 .any(|(c, n, _)| c == &caller_id && n == &callee_name)
559 {
560 self.deferred_calls.push((caller_id, callee_name, line));
561 }
562 }
563
564 fn visit_type_item(
565 &mut self,
566 node: TsNode<'_>,
567 scope: &[String],
568 container_id: Option<NodeId>,
569 kind: NodeKind,
570 ) {
571 let Some(name) = self.field_text(node, "name") else {
572 return;
573 };
574 let id = self
575 .type_index
576 .get(&name)
577 .cloned()
578 .unwrap_or_else(NodeId::new);
579 let graph_node = self.make_node(id.clone(), kind, name, scope, node);
580 if let Some(cid) = container_id {
581 self.edges.push(Edge {
582 src: cid,
583 dst: id.clone(),
584 kind: EdgeKind::Contains,
585 line: None,
586 confidence: EdgeConfidence::Extracted,
587 });
588 }
589 for attr_name in self.collect_attributes(node) {
590 self.deferred_annotated.push((id.clone(), attr_name));
591 }
592 self.nodes.push(graph_node);
593 }
594
595 fn visit_trait(&mut self, node: TsNode<'_>, scope: &[String], container_id: Option<NodeId>) {
596 let Some(name) = self.field_text(node, "name") else {
597 return;
598 };
599 let id = self
600 .type_index
601 .get(&name)
602 .cloned()
603 .unwrap_or_else(NodeId::new);
604 let graph_node = self.make_node(id.clone(), NodeKind::Trait, name.clone(), scope, node);
605 if let Some(cid) = container_id {
606 self.edges.push(Edge {
607 src: cid,
608 dst: id.clone(),
609 kind: EdgeKind::Contains,
610 line: None,
611 confidence: EdgeConfidence::Extracted,
612 });
613 }
614 for attr_name in self.collect_attributes(node) {
615 self.deferred_annotated.push((id.clone(), attr_name));
616 }
617 self.nodes.push(graph_node);
618
619 if let Some(body) = node.child_by_field_name("body") {
620 let mut new_scope = scope.to_vec();
621 new_scope.push(name);
622 self.visit_items(body, &new_scope, Some(id));
623 }
624 }
625
626 fn visit_impl(&mut self, node: TsNode<'_>, scope: &[String]) {
627 let type_node = node.child_by_field_name("type");
628 let type_name = type_node.and_then(|n| self.type_name(n));
629 let Some(type_name) = type_name else { return };
630 let type_id = self.type_index.get(&type_name).cloned();
631
632 if let Some(trait_node) = node.child_by_field_name("trait") {
633 if let Some(trait_name) = self.type_name(trait_node) {
634 let trait_id = self.type_index.get(&trait_name).cloned();
635 match (type_id.clone(), trait_id) {
636 (Some(tid), Some(trid)) => {
637 self.edges.push(Edge {
638 src: tid,
639 dst: trid,
640 kind: EdgeKind::Implements,
641 line: None,
642 confidence: EdgeConfidence::Extracted,
643 });
644 }
645 (Some(tid), None)
646 if !is_primitive(&trait_name)
647 && !self
648 .deferred_implements
649 .iter()
650 .any(|(id, n)| id == &tid && n == &trait_name) =>
651 {
652 self.deferred_implements.push((tid, trait_name));
653 }
654 _ => {}
655 }
656 }
657 }
658
659 if let Some(body) = node.child_by_field_name("body") {
660 let mut cursor = body.walk();
661 let children: Vec<TsNode<'_>> = body.named_children(&mut cursor).collect();
662 let mut impl_scope = scope.to_vec();
663 impl_scope.push(type_name);
664
665 for child in children {
666 if child.kind() == "function_item" {
667 self.visit_function(child, &impl_scope, type_id.clone(), NodeKind::Method);
668 }
669 }
670 }
671 }
672
673 fn visit_mod(&mut self, node: TsNode<'_>, scope: &[String], container_id: Option<NodeId>) {
674 let Some(name) = self.field_text(node, "name") else {
675 return;
676 };
677 let id = NodeId::new();
678 let graph_node = self.make_node(id.clone(), NodeKind::Module, name.clone(), scope, node);
679 if let Some(cid) = container_id {
680 self.edges.push(Edge {
681 src: cid,
682 dst: id.clone(),
683 kind: EdgeKind::Contains,
684 line: None,
685 confidence: EdgeConfidence::Extracted,
686 });
687 }
688 self.nodes.push(graph_node);
689
690 if let Some(body) = node.child_by_field_name("body") {
691 let mut new_scope = scope.to_vec();
692 new_scope.push(name);
693 self.visit_items(body, &new_scope, Some(id));
694 }
695 }
696
697 fn visit_const(&mut self, node: TsNode<'_>, scope: &[String], container_id: Option<NodeId>) {
698 let Some(name) = self.field_text(node, "name") else {
699 return;
700 };
701 let id = NodeId::new();
702 let graph_node = self.make_node(id.clone(), NodeKind::Constant, name, scope, node);
703 if let Some(cid) = container_id {
704 self.edges.push(Edge {
705 src: cid,
706 dst: id.clone(),
707 kind: EdgeKind::Contains,
708 line: None,
709 confidence: EdgeConfidence::Extracted,
710 });
711 }
712 self.nodes.push(graph_node);
713 }
714
715 fn visit_type_alias(
716 &mut self,
717 node: TsNode<'_>,
718 scope: &[String],
719 container_id: Option<NodeId>,
720 ) {
721 let Some(name) = self.field_text(node, "name") else {
722 return;
723 };
724 let id = NodeId::new();
725 let graph_node = self.make_node(id.clone(), NodeKind::TypeAlias, name, scope, node);
726 if let Some(cid) = container_id {
727 self.edges.push(Edge {
728 src: cid,
729 dst: id.clone(),
730 kind: EdgeKind::Contains,
731 line: None,
732 confidence: EdgeConfidence::Extracted,
733 });
734 }
735 self.nodes.push(graph_node);
736 }
737
738 fn visit_macro_def(
739 &mut self,
740 node: TsNode<'_>,
741 scope: &[String],
742 container_id: Option<NodeId>,
743 ) {
744 let Some(name) = self.field_text(node, "name") else {
745 return;
746 };
747 let id = NodeId::new();
748 let graph_node = self.make_node(id.clone(), NodeKind::Macro, name, scope, node);
749 if let Some(cid) = container_id {
750 self.edges.push(Edge {
751 src: cid,
752 dst: id.clone(),
753 kind: EdgeKind::Contains,
754 line: None,
755 confidence: EdgeConfidence::Extracted,
756 });
757 }
758 self.nodes.push(graph_node);
759 }
760
761 fn collect_imports(&mut self, root: TsNode<'_>) {
764 let mut cursor = root.walk();
765 for child in root.named_children(&mut cursor) {
766 if child.kind() == "use_declaration" {
767 if let Some(arg) = child.child_by_field_name("argument") {
769 self.collect_import_leaves(arg);
770 }
771 } else if child.kind() == "mod_item" {
772 if let Some(body) = child.child_by_field_name("body") {
773 self.collect_imports(body);
774 }
775 }
776 }
777 }
778
779 fn collect_import_leaves(&mut self, node: TsNode<'_>) {
780 match node.kind() {
781 "identifier" | "type_identifier" => {
782 let name = self.text(node).to_owned();
783 if !name.is_empty()
784 && !is_primitive(&name)
785 && name != "self"
786 && name != "super"
787 && name != "crate"
788 {
789 self.deferred_imports.push((self.module_id.clone(), name));
792 }
793 }
794 "use_list" => {
795 let mut cursor = node.walk();
796 for child in node.named_children(&mut cursor) {
797 self.collect_import_leaves(child);
798 }
799 }
800 "scoped_identifier" | "scoped_use_list" => {
801 let mut cursor = node.walk();
803 for child in node.named_children(&mut cursor) {
804 self.collect_import_leaves(child);
805 }
806 }
807 "use_as_clause" => {
808 if let Some(alias) = node.child_by_field_name("alias") {
810 self.collect_import_leaves(alias);
811 }
812 }
813 _ => {}
814 }
815 }
816}
817
818fn is_primitive(name: &str) -> bool {
819 matches!(
820 name,
821 "bool"
822 | "char"
823 | "str"
824 | "i8"
825 | "i16"
826 | "i32"
827 | "i64"
828 | "i128"
829 | "isize"
830 | "u8"
831 | "u16"
832 | "u32"
833 | "u64"
834 | "u128"
835 | "usize"
836 | "f32"
837 | "f64"
838 | "String"
839 | "Vec"
840 | "Option"
841 | "Result"
842 | "Box"
843 | "Rc"
844 | "Arc"
845 | "Cell"
846 | "RefCell"
847 | "Cow"
848 | "HashMap"
849 | "HashSet"
850 | "BTreeMap"
851 | "BTreeSet"
852 | "PathBuf"
853 | "Path"
854 | "OsString"
855 | "OsStr"
856 | "Send"
857 | "Sync"
858 | "Sized"
859 | "Clone"
860 | "Copy"
861 | "Debug"
862 | "Display"
863 | "Default"
864 | "PartialEq"
865 | "Eq"
866 | "PartialOrd"
867 | "Ord"
868 | "Hash"
869 | "Iterator"
870 | "Into"
871 | "From"
872 | "AsRef"
873 | "AsMut"
874 | "Deref"
875 | "DerefMut"
876 | "Error"
877 | "Write"
878 | "Read"
879 | "Seek"
880 | "Self"
881 | "()"
882 | "_"
883 )
884}
885
886#[cfg(test)]
889mod tests {
890 use std::path::Path;
891
892 use gitcortex_core::{
893 graph::{Edge, Node},
894 schema::{EdgeKind, NodeKind},
895 };
896
897 use super::RustParser;
898 use crate::parser::LanguageParser;
899
900 fn parse(src: &str) -> (Vec<Node>, Vec<Edge>) {
901 let r = RustParser::new().parse(Path::new("test.rs"), src).unwrap();
902 (r.nodes, r.edges)
903 }
904
905 #[test]
906 fn parses_free_function() {
907 let (nodes, _) = parse("pub fn greet(name: &str) -> String { name.into() }");
908 let fns: Vec<_> = nodes
910 .iter()
911 .filter(|n| n.kind == NodeKind::Function)
912 .collect();
913 assert_eq!(fns.len(), 1);
914 assert_eq!(fns[0].name, "greet");
915 }
916
917 #[test]
918 fn parses_struct() {
919 let (nodes, _) = parse("pub struct Person { pub name: String }");
920 let structs: Vec<_> = nodes
921 .iter()
922 .filter(|n| n.kind == NodeKind::Struct)
923 .collect();
924 assert_eq!(structs.len(), 1);
925 assert_eq!(structs[0].name, "Person");
926 }
927
928 #[test]
929 fn parses_trait_impl_and_method() {
930 let src = r#"
931pub trait Greet { fn greet(&self) -> String; }
932pub struct Person { pub name: String }
933impl Greet for Person {
934 fn greet(&self) -> String { self.name.clone() }
935}
936"#;
937 let (nodes, edges) = parse(src);
938
939 let traits: Vec<_> = nodes.iter().filter(|n| n.kind == NodeKind::Trait).collect();
940 let structs: Vec<_> = nodes
941 .iter()
942 .filter(|n| n.kind == NodeKind::Struct)
943 .collect();
944 let methods: Vec<_> = nodes
945 .iter()
946 .filter(|n| n.kind == NodeKind::Method)
947 .collect();
948 let impl_edges: Vec<_> = edges
949 .iter()
950 .filter(|e| e.kind == EdgeKind::Implements)
951 .collect();
952
953 assert_eq!(traits.len(), 1, "expected Greet trait");
954 assert_eq!(structs.len(), 1, "expected Person struct");
955 assert_eq!(methods.len(), 1, "expected greet method");
956 assert_eq!(impl_edges.len(), 1, "expected Implements edge");
957 }
958
959 #[test]
960 fn parses_module_with_items() {
961 let src = r#"
962pub mod utils {
963 pub fn helper() {}
964 pub struct Config {}
965}
966"#;
967 let (nodes, edges) = parse(src);
968
969 let mods: Vec<_> = nodes
970 .iter()
971 .filter(|n| n.kind == NodeKind::Module)
972 .collect();
973 let fns: Vec<_> = nodes
974 .iter()
975 .filter(|n| n.kind == NodeKind::Function)
976 .collect();
977 let contains: Vec<_> = edges
978 .iter()
979 .filter(|e| e.kind == EdgeKind::Contains)
980 .collect();
981
982 assert!(
984 mods.iter().any(|n| n.name == "utils"),
985 "expected utils module"
986 );
987 assert_eq!(fns.len(), 1, "expected helper function");
988 assert!(!contains.is_empty(), "expected Contains edges");
989 }
990
991 #[test]
992 fn qualified_name_includes_module_path() {
993 let src = r#"
994pub mod inner {
995 pub fn foo() {}
996}
997"#;
998 let (nodes, _) = parse(src);
999 let foo = nodes.iter().find(|n| n.name == "foo").unwrap();
1000 assert_eq!(foo.qualified_name, "crate::inner::foo");
1001 }
1002
1003 #[test]
1004 fn detects_intra_file_calls() {
1005 let src = r#"
1006pub fn caller() { callee(); }
1007pub fn callee() {}
1008"#;
1009 let (_, edges) = parse(src);
1010 let calls: Vec<_> = edges.iter().filter(|e| e.kind == EdgeKind::Calls).collect();
1011 assert_eq!(calls.len(), 1, "expected one Calls edge");
1012 }
1013
1014 #[test]
1015 fn detects_uses_edges_for_param_types() {
1016 let src = r#"
1017pub struct Config {}
1018pub fn run(cfg: Config) {}
1019"#;
1020 let (_, edges) = parse(src);
1021 let uses: Vec<_> = edges.iter().filter(|e| e.kind == EdgeKind::Uses).collect();
1022 assert_eq!(uses.len(), 1, "expected one Uses edge from run to Config");
1023 }
1024
1025 #[test]
1026 fn deferred_calls_capture_unknown_callees() {
1027 let src = r#"
1028pub fn caller() { external_fn(); }
1029"#;
1030 let result = RustParser::new().parse(Path::new("test.rs"), src).unwrap();
1031 assert_eq!(result.deferred_calls.len(), 1);
1032 assert_eq!(result.deferred_calls[0].1, "external_fn");
1033 }
1034}