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