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::{EdgeKind, NodeKind, Visibility},
10};
11use tree_sitter::{Node as TsNode, Parser};
12
13use super::{capture_definition, LanguageParser, ParseResult};
14
15pub struct GoParser {
16 language: tree_sitter::Language,
17}
18
19impl GoParser {
20 pub fn new() -> Self {
21 Self {
22 language: tree_sitter_go::LANGUAGE.into(),
23 }
24 }
25}
26
27impl Default for GoParser {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33impl LanguageParser for GoParser {
34 fn extensions(&self) -> &[&str] {
35 &["go"]
36 }
37
38 fn parse(&self, path: &Path, source: &str) -> Result<ParseResult> {
39 let mut parser = Parser::new();
40 parser
41 .set_language(&self.language)
42 .map_err(|e| GitCortexError::Parse {
43 file: path.to_owned(),
44 message: e.to_string(),
45 })?;
46
47 let tree = parser
48 .parse(source, None)
49 .ok_or_else(|| GitCortexError::Parse {
50 file: path.to_owned(),
51 message: "tree-sitter returned no parse tree".into(),
52 })?;
53
54 let mut visitor = FileVisitor::new(path, source, tree.root_node());
55 visitor.collect_names(tree.root_node());
56 visitor.visit_source_file(tree.root_node());
57 visitor.collect_imports(tree.root_node());
58 visitor.collect_interface_assertions(tree.root_node());
59
60 Ok(ParseResult {
61 nodes: visitor.nodes,
62 edges: visitor.edges,
63 deferred_calls: visitor.deferred_calls,
64 deferred_uses: visitor.deferred_uses,
65 deferred_implements: visitor.deferred_implements,
66 deferred_imports: visitor.deferred_imports,
67 deferred_inherits: visitor.deferred_inherits,
68 deferred_throws: Vec::new(),
69 deferred_annotated: Vec::new(),
70 })
71 }
72}
73
74struct FileVisitor<'src> {
77 source: &'src [u8],
78 file: PathBuf,
79 package_id: NodeId,
81 nodes: Vec<Node>,
82 edges: Vec<Edge>,
83 type_index: HashMap<String, NodeId>,
85 fn_index: HashMap<String, NodeId>,
87 deferred_calls: Vec<(NodeId, String)>,
88 deferred_uses: Vec<(NodeId, String)>,
89 deferred_implements: Vec<(NodeId, String)>,
90 deferred_imports: Vec<(NodeId, String)>,
91 deferred_inherits: Vec<(NodeId, String)>,
92}
93
94impl<'src> FileVisitor<'src> {
95 fn new(file: &Path, source: &'src str, root: TsNode<'_>) -> Self {
96 let package_id = NodeId::new();
97 let package_name = {
99 let mut c = root.walk();
100 let pkg_clause: Vec<TsNode<'_>> = root.named_children(&mut c).collect();
101 let name = pkg_clause
102 .iter()
103 .find(|n| n.kind() == "package_clause")
104 .and_then(|pc| {
105 let mut cc = pc.walk();
106 let ids: Vec<TsNode<'_>> = pc.named_children(&mut cc).collect();
107 ids.into_iter()
108 .find(|n| n.kind() == "package_identifier")
109 .map(|n| n.utf8_text(source.as_bytes()).unwrap_or("main").to_owned())
110 })
111 .unwrap_or_else(|| {
112 file.file_stem()
113 .and_then(|s| s.to_str())
114 .unwrap_or("main")
115 .to_owned()
116 });
117 name
118 };
119 let package_node = Node {
120 id: package_id.clone(),
121 qualified_name: package_name.clone(),
122 kind: NodeKind::Module,
123 name: package_name,
124 file: file.to_owned(),
125 span: Span {
126 start_line: 1,
127 end_line: 1,
128 },
129 metadata: NodeMetadata {
130 loc: source.lines().count() as u32,
131 visibility: Visibility::Pub,
132 is_async: false,
133 is_unsafe: false,
134 ..Default::default()
135 },
136 };
137 let nodes = vec![package_node];
138 Self {
139 source: source.as_bytes(),
140 file: file.to_owned(),
141 package_id,
142 nodes,
143 edges: Vec::new(),
144 type_index: HashMap::new(),
145 fn_index: HashMap::new(),
146 deferred_calls: Vec::new(),
147 deferred_uses: Vec::new(),
148 deferred_implements: Vec::new(),
149 deferred_imports: Vec::new(),
150 deferred_inherits: Vec::new(),
151 }
152 }
153
154 fn text<'t>(&self, node: TsNode<'t>) -> &'src str {
155 node.utf8_text(self.source).unwrap_or("")
156 }
157
158 fn span(node: TsNode<'_>) -> Span {
159 Span {
160 start_line: node.start_position().row as u32 + 1,
161 end_line: node.end_position().row as u32 + 1,
162 }
163 }
164
165 fn visibility(name: &str) -> Visibility {
167 if name
168 .chars()
169 .next()
170 .map(|c| c.is_uppercase())
171 .unwrap_or(false)
172 {
173 Visibility::Pub
174 } else {
175 Visibility::Private
176 }
177 }
178
179 fn qualified(scope: &[String], name: &str) -> String {
180 if scope.is_empty() {
181 name.to_owned()
182 } else {
183 format!("{}.{name}", scope.join("."))
184 }
185 }
186
187 fn make_node(
188 &self,
189 id: NodeId,
190 kind: NodeKind,
191 name: String,
192 scope: &[String],
193 ts_node: TsNode<'_>,
194 ) -> Node {
195 Node {
196 id,
197 qualified_name: Self::qualified(scope, &name),
198 kind,
199 name: name.clone(),
200 file: self.file.clone(),
201 span: Self::span(ts_node),
202 metadata: NodeMetadata {
203 loc: (ts_node.end_position().row - ts_node.start_position().row + 1) as u32,
204 visibility: Self::visibility(&name),
205 is_async: false,
206 is_unsafe: false,
207 definition: capture_definition(self.source, ts_node),
208 ..Default::default()
209 },
210 }
211 }
212
213 fn collect_names(&mut self, node: TsNode<'_>) {
216 let mut cursor = node.walk();
217 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
218 for child in children {
219 match child.kind() {
220 "function_declaration" => {
221 if let Some(name_node) = child.child_by_field_name("name") {
222 let name = self.text(name_node).to_owned();
223 self.fn_index.entry(name).or_default();
224 }
225 }
226 "method_declaration" => {
227 if let Some(name_node) = child.child_by_field_name("name") {
228 let name = self.text(name_node).to_owned();
229 self.fn_index.entry(name).or_default();
230 }
231 }
232 "type_declaration" => {
233 self.collect_type_decl_names(child);
234 }
235 _ => {}
236 }
237 }
238 }
239
240 fn collect_type_decl_names(&mut self, decl: TsNode<'_>) {
241 let mut cursor = decl.walk();
242 for spec in decl.named_children(&mut cursor) {
243 if spec.kind() != "type_spec" {
244 continue;
245 }
246 if let Some(name_node) = spec.child_by_field_name("name") {
247 let name = self.text(name_node).to_owned();
248 if let Some(type_node) = spec.child_by_field_name("type") {
249 if matches!(type_node.kind(), "struct_type" | "interface_type") {
250 self.type_index.entry(name).or_default();
251 }
252 }
253 }
254 }
255 }
256
257 fn visit_source_file(&mut self, node: TsNode<'_>) {
260 let mut cursor = node.walk();
261 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
262 for child in children {
263 self.visit_top_level(child);
264 }
265 }
266
267 fn visit_top_level(&mut self, node: TsNode<'_>) {
268 match node.kind() {
269 "function_declaration" => self.visit_function(node, &[]),
270 "method_declaration" => self.visit_method(node),
271 "type_declaration" => self.visit_type_decl(node),
272 "const_declaration" => self.visit_const_decl(node),
273 _ => {}
274 }
275 }
276
277 fn visit_function(&mut self, node: TsNode<'_>, scope: &[String]) {
278 let Some(name_node) = node.child_by_field_name("name") else {
279 return;
280 };
281 let name = self.text(name_node).to_owned();
282 let id = self
283 .fn_index
284 .get(&name)
285 .cloned()
286 .unwrap_or_else(NodeId::new);
287 let mut graph_node =
288 self.make_node(id.clone(), NodeKind::Function, name.clone(), scope, node);
289
290 if name == "init" || name == "main" {
292 graph_node.metadata.is_static = true;
293 }
294
295 graph_node.metadata.generic_bounds = self.collect_generic_bounds(node);
298
299 self.nodes.push(graph_node);
300
301 self.extract_fn_type_uses(node, &id);
302
303 if let Some(body) = node.child_by_field_name("body") {
304 self.collect_calls(body, &id);
305 }
306 }
307
308 fn visit_method(&mut self, node: TsNode<'_>) {
309 let Some(name_node) = node.child_by_field_name("name") else {
310 return;
311 };
312 let name = self.text(name_node).to_owned();
313
314 let receiver_type = self.receiver_type(node);
315 let scope: Vec<String> = receiver_type.into_iter().collect();
316
317 let container_id = scope.first().and_then(|t| self.type_index.get(t).cloned());
318 let id = self
319 .fn_index
320 .get(&name)
321 .cloned()
322 .unwrap_or_else(NodeId::new);
323 let graph_node = self.make_node(id.clone(), NodeKind::Method, name, &scope, node);
324
325 if let Some(cid) = container_id {
326 self.edges.push(Edge {
327 src: cid,
328 dst: id.clone(),
329 kind: EdgeKind::Contains,
330 });
331 }
332 self.nodes.push(graph_node);
333
334 self.extract_fn_type_uses(node, &id);
335
336 if let Some(body) = node.child_by_field_name("body") {
337 self.collect_calls(body, &id);
338 }
339 }
340
341 fn receiver_type(&self, method_node: TsNode<'_>) -> Option<String> {
343 let recv = method_node.child_by_field_name("receiver")?;
344 let mut cursor = recv.walk();
345 for param in recv.named_children(&mut cursor) {
346 if param.kind() != "parameter_declaration" {
347 continue;
348 }
349 if let Some(type_node) = param.child_by_field_name("type") {
350 return match type_node.kind() {
351 "type_identifier" => Some(self.text(type_node).to_owned()),
352 "pointer_type" => {
353 let mut c = type_node.walk();
354 let result = type_node
355 .named_children(&mut c)
356 .find(|n| n.kind() == "type_identifier")
357 .map(|n| self.text(n).to_owned());
358 result
359 }
360 _ => None,
361 };
362 }
363 }
364 None
365 }
366
367 fn visit_type_decl(&mut self, decl: TsNode<'_>) {
368 let mut cursor = decl.walk();
369 let specs: Vec<TsNode<'_>> = decl.named_children(&mut cursor).collect();
370 for spec in specs {
371 if spec.kind() != "type_spec" {
372 continue;
373 }
374 let Some(name_node) = spec.child_by_field_name("name") else {
375 continue;
376 };
377 let name = self.text(name_node).to_owned();
378 let Some(type_node) = spec.child_by_field_name("type") else {
379 continue;
380 };
381
382 match type_node.kind() {
383 "struct_type" => {
384 let id = self
385 .type_index
386 .get(&name)
387 .cloned()
388 .unwrap_or_else(NodeId::new);
389 let mut graph_node =
390 self.make_node(id.clone(), NodeKind::Struct, name, &[], spec);
391 graph_node.metadata.generic_bounds = self.collect_generic_bounds(spec);
393 self.nodes.push(graph_node);
394 self.extract_struct_field_uses(type_node, &id);
396 }
397 "interface_type" => {
398 let id = self
399 .type_index
400 .get(&name)
401 .cloned()
402 .unwrap_or_else(NodeId::new);
403 let mut graph_node =
404 self.make_node(id.clone(), NodeKind::Interface, name, &[], spec);
405 graph_node.metadata.generic_bounds = self.collect_generic_bounds(spec);
407 self.nodes.push(graph_node);
408 self.extract_interface_methods(type_node, &id);
410 }
411 _ => {
412 let id = NodeId::new();
413 let graph_node = self.make_node(id, NodeKind::TypeAlias, name, &[], spec);
414 self.nodes.push(graph_node);
415 }
416 }
417 }
418 }
419
420 fn visit_const_decl(&mut self, node: TsNode<'_>) {
421 let mut cursor = node.walk();
422 for spec in node.named_children(&mut cursor) {
423 if spec.kind() != "const_spec" {
424 continue;
425 }
426 let Some(name_node) = spec.child_by_field_name("name") else {
427 continue;
428 };
429 let name = self.text(name_node).to_owned();
430 let id = NodeId::new();
431 let mut graph_node = self.make_node(id, NodeKind::Constant, name, &[], spec);
432 graph_node.metadata.is_const = true;
433 self.nodes.push(graph_node);
434 }
435 }
436
437 fn collect_imports(&mut self, node: TsNode<'_>) {
440 let mut cursor = node.walk();
441 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
442 for child in children {
443 if child.kind() != "import_declaration" {
444 continue;
445 }
446 let mut c = child.walk();
448 let decl_children: Vec<TsNode<'_>> = child.named_children(&mut c).collect();
449 for dc in decl_children {
450 match dc.kind() {
451 "import_spec" => self.record_import_spec(dc),
452 "import_spec_list" => {
453 let mut cc = dc.walk();
454 let specs: Vec<TsNode<'_>> = dc.named_children(&mut cc).collect();
455 for spec in specs {
456 if spec.kind() == "import_spec" {
457 self.record_import_spec(spec);
458 }
459 }
460 }
461 _ => {}
462 }
463 }
464 }
465 }
466
467 fn record_import_spec(&mut self, spec: TsNode<'_>) {
468 let alias = spec
470 .child_by_field_name("name")
471 .map(|n| self.text(n).to_owned());
472
473 if alias.as_deref() == Some("_") {
475 return;
476 }
477
478 let pkg_name = if let Some(alias) = alias {
479 alias
480 } else if let Some(path_node) = spec.child_by_field_name("path") {
481 let raw = self.text(path_node).trim_matches('"').trim_matches('\'');
483 raw.split('/').next_back().unwrap_or(raw).to_owned()
484 } else {
485 return;
486 };
487
488 self.deferred_imports
489 .push((self.package_id.clone(), pkg_name));
490 }
491
492 fn collect_interface_assertions(&mut self, node: TsNode<'_>) {
496 let mut cursor = node.walk();
497 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
498 for child in children {
499 if child.kind() != "var_declaration" {
500 continue;
501 }
502 let mut c = child.walk();
503 let specs: Vec<TsNode<'_>> = child.named_children(&mut c).collect();
504 for spec in specs {
505 if spec.kind() != "var_spec" {
506 continue;
507 }
508 let mut cc = spec.walk();
509 let spec_children: Vec<TsNode<'_>> = spec.named_children(&mut cc).collect();
510 if spec_children.len() < 3 {
512 continue;
513 }
514 if spec_children[0].kind() != "identifier" || self.text(spec_children[0]) != "_" {
515 continue;
516 }
517 if spec_children[1].kind() != "type_identifier" {
518 continue;
519 }
520 let interface_name = self.text(spec_children[1]).to_owned();
521 let value = spec_children[2];
523 let mut candidates = Vec::new();
524 self.collect_candidate_type_names(value, &mut candidates);
525 for struct_name in candidates {
526 if let Some(struct_id) = self.type_index.get(&struct_name).cloned() {
527 self.deferred_implements
528 .push((struct_id, interface_name.clone()));
529 }
530 }
531 }
532 }
533 }
534
535 fn collect_candidate_type_names(&self, node: TsNode<'_>, out: &mut Vec<String>) {
537 match node.kind() {
538 "identifier" | "type_identifier" => {
539 let name = self.text(node).to_owned();
540 if name != "nil" && !is_builtin_go_type(&name) {
541 out.push(name);
542 }
543 }
544 _ => {
545 let mut c = node.walk();
546 for child in node.named_children(&mut c) {
547 self.collect_candidate_type_names(child, out);
548 }
549 }
550 }
551 }
552
553 fn extract_fn_type_uses(&mut self, fn_node: TsNode<'_>, fn_id: &NodeId) {
557 if let Some(params) = fn_node.child_by_field_name("parameters") {
559 let mut c = params.walk();
560 let param_list: Vec<TsNode<'_>> = params.named_children(&mut c).collect();
561 for param in param_list {
562 if param.kind() == "parameter_declaration"
563 || param.kind() == "variadic_parameter_declaration"
564 {
565 if let Some(type_node) = param.child_by_field_name("type") {
566 for name in self.collect_type_idents(type_node) {
567 self.deferred_uses.push((fn_id.clone(), name));
568 }
569 }
570 }
571 }
572 }
573 if let Some(result) = fn_node.child_by_field_name("result") {
575 match result.kind() {
576 "parameter_list" => {
577 let mut c = result.walk();
578 let ret_params: Vec<TsNode<'_>> = result.named_children(&mut c).collect();
579 for rp in ret_params {
580 if rp.kind() == "parameter_declaration" {
581 if let Some(type_node) = rp.child_by_field_name("type") {
582 for name in self.collect_type_idents(type_node) {
583 self.deferred_uses.push((fn_id.clone(), name));
584 }
585 }
586 }
587 }
588 }
589 _ => {
591 for name in self.collect_type_idents(result) {
592 self.deferred_uses.push((fn_id.clone(), name));
593 }
594 }
595 }
596 }
597 }
598
599 fn extract_struct_field_uses(&mut self, struct_type: TsNode<'_>, struct_id: &NodeId) {
602 let mut tw = struct_type.walk();
603 let top: Vec<TsNode<'_>> = struct_type.named_children(&mut tw).collect();
604 let Some(field_list) = top
605 .iter()
606 .find(|n| n.kind() == "field_declaration_list")
607 .copied()
608 else {
609 return;
610 };
611 let mut c = field_list.walk();
612 let fields: Vec<TsNode<'_>> = field_list.named_children(&mut c).collect();
613 for field in fields {
614 if field.kind() == "field_declaration" {
615 let has_name = field.child_by_field_name("name").is_some();
619 if let Some(type_node) = field.child_by_field_name("type") {
620 let type_names = self.collect_type_idents(type_node);
621 for name in &type_names {
622 self.deferred_uses.push((struct_id.clone(), name.clone()));
623 }
624 if !has_name {
626 for name in type_names {
627 self.deferred_inherits.push((struct_id.clone(), name));
628 }
629 }
630 }
631 }
632 }
633 }
634
635 fn extract_interface_methods(&mut self, interface_type: TsNode<'_>, iface_id: &NodeId) {
637 let mut c = interface_type.walk();
638 let children: Vec<TsNode<'_>> = interface_type.named_children(&mut c).collect();
639 for child in children {
640 if child.kind() == "method_elem" {
642 let mut cc = child.walk();
643 let method_children: Vec<TsNode<'_>> = child.named_children(&mut cc).collect();
644 let Some(name_node) = method_children
646 .iter()
647 .find(|n| n.kind() == "field_identifier")
648 else {
649 continue;
650 };
651 let name = self.text(*name_node).to_owned();
652 let id = NodeId::new();
653 let graph_node = self.make_node(id.clone(), NodeKind::Method, name, &[], child);
654 self.edges.push(Edge {
655 src: iface_id.clone(),
656 dst: id.clone(),
657 kind: EdgeKind::Contains,
658 });
659 self.nodes.push(graph_node);
660 }
661 }
662 }
663
664 fn collect_generic_bounds(&self, node: TsNode<'_>) -> Vec<String> {
669 let Some(type_params) = node.child_by_field_name("type_parameters") else {
670 return Vec::new();
671 };
672 let mut bounds = Vec::new();
673 let mut cursor = type_params.walk();
674 for child in type_params.named_children(&mut cursor) {
675 if child.kind() == "type_parameter_declaration" {
678 let name = child
679 .child_by_field_name("name")
680 .map(|n| self.text(n))
681 .unwrap_or("");
682 let constraint = child
683 .child_by_field_name("type")
684 .map(|n| self.text(n))
685 .unwrap_or("");
686 if !name.is_empty() {
687 let bound = if constraint.is_empty() {
688 name.to_owned()
689 } else {
690 format!("{name} {constraint}")
691 };
692 bounds.push(bound);
693 }
694 }
695 }
696 bounds
697 }
698
699 fn collect_type_idents(&self, node: TsNode<'_>) -> Vec<String> {
701 let mut names = Vec::new();
702 self.walk_type_idents(node, &mut names);
703 names
704 }
705
706 fn walk_type_idents(&self, node: TsNode<'_>, out: &mut Vec<String>) {
707 match node.kind() {
708 "type_identifier" => {
709 let name = self.text(node).to_owned();
710 if !is_builtin_go_type(&name) {
711 out.push(name);
712 }
713 }
714 _ => {
715 let mut c = node.walk();
716 for child in node.named_children(&mut c) {
717 self.walk_type_idents(child, out);
718 }
719 }
720 }
721 }
722
723 fn collect_calls(&mut self, node: TsNode<'_>, caller_id: &NodeId) {
726 let mut cursor = node.walk();
727 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
728 for child in children {
729 if child.kind() == "call_expression" {
730 if let Some(callee) = self.callee_name(child) {
731 self.record_call(caller_id.clone(), callee);
732 }
733 if let Some(args) = child.child_by_field_name("arguments") {
734 self.collect_calls(args, caller_id);
735 }
736 } else if child.kind() == "go_statement" {
737 if let Some(call) = child.named_child(0) {
739 if call.kind() == "call_expression" {
740 if let Some(callee) = self.callee_name(call) {
741 self.deferred_calls.push((caller_id.clone(), callee));
743 }
744 }
745 }
746 } else {
747 self.collect_calls(child, caller_id);
748 }
749 }
750 }
751
752 fn callee_name(&self, call_expr: TsNode<'_>) -> Option<String> {
753 let func = call_expr.child_by_field_name("function")?;
754 match func.kind() {
755 "identifier" => Some(self.text(func).to_owned()),
756 "selector_expression" => func
757 .child_by_field_name("field")
758 .map(|n| self.text(n).to_owned()),
759 _ => None,
760 }
761 }
762
763 fn record_call(&mut self, caller_id: NodeId, callee_name: String) {
764 if callee_name.is_empty() {
765 return;
766 }
767 if let Some(callee_id) = self.fn_index.get(&callee_name).cloned() {
768 let edge = Edge {
769 src: caller_id,
770 dst: callee_id,
771 kind: EdgeKind::Calls,
772 };
773 if !self.edges.contains(&edge) {
774 self.edges.push(edge);
775 }
776 } else if !self
777 .deferred_calls
778 .iter()
779 .any(|(c, n)| c == &caller_id && n == &callee_name)
780 {
781 self.deferred_calls.push((caller_id, callee_name));
782 }
783 }
784}
785
786fn is_builtin_go_type(name: &str) -> bool {
788 matches!(
789 name,
790 "bool"
791 | "byte"
792 | "complex64"
793 | "complex128"
794 | "error"
795 | "float32"
796 | "float64"
797 | "int"
798 | "int8"
799 | "int16"
800 | "int32"
801 | "int64"
802 | "rune"
803 | "string"
804 | "uint"
805 | "uint8"
806 | "uint16"
807 | "uint32"
808 | "uint64"
809 | "uintptr"
810 | "any"
811 | "comparable"
812 )
813}
814
815#[cfg(test)]
818mod tests {
819 use super::GoParser;
820 use crate::parser::LanguageParser;
821 use gitcortex_core::schema::{EdgeKind, NodeKind};
822 use std::path::Path;
823
824 fn parse(
825 src: &str,
826 ) -> (
827 Vec<gitcortex_core::graph::Node>,
828 Vec<gitcortex_core::graph::Edge>,
829 ) {
830 let r = GoParser::new().parse(Path::new("test.go"), src).unwrap();
831 (r.nodes, r.edges)
832 }
833
834 #[allow(clippy::type_complexity)]
835 fn parse_full(
836 src: &str,
837 ) -> (
838 Vec<gitcortex_core::graph::Node>,
839 Vec<gitcortex_core::graph::Edge>,
840 Vec<(gitcortex_core::graph::NodeId, String)>,
841 Vec<(gitcortex_core::graph::NodeId, String)>,
842 Vec<(gitcortex_core::graph::NodeId, String)>,
843 Vec<(gitcortex_core::graph::NodeId, String)>,
844 ) {
845 let r = GoParser::new().parse(Path::new("test.go"), src).unwrap();
846 (
847 r.nodes,
848 r.edges,
849 r.deferred_calls,
850 r.deferred_uses,
851 r.deferred_implements,
852 r.deferred_imports,
853 )
854 }
855
856 #[test]
857 fn parses_function() {
858 let src = "package main\nfunc Greet(name string) string { return name }";
859 let (nodes, _) = parse(src);
860 let fns: Vec<_> = nodes
861 .iter()
862 .filter(|n| n.kind == NodeKind::Function)
863 .collect();
864 assert_eq!(fns.len(), 1);
865 assert_eq!(fns[0].name, "Greet");
866 }
867
868 #[test]
869 fn parses_struct_and_method() {
870 let src = "package main\ntype Person struct { Name string }\nfunc (p *Person) Greet() string { return p.Name }";
871 let (nodes, edges) = parse(src);
872 let structs: Vec<_> = nodes
873 .iter()
874 .filter(|n| n.kind == NodeKind::Struct)
875 .collect();
876 let methods: Vec<_> = nodes
877 .iter()
878 .filter(|n| n.kind == NodeKind::Method)
879 .collect();
880 assert_eq!(structs.len(), 1);
881 assert_eq!(methods.len(), 1);
882 let contains: Vec<_> = edges
883 .iter()
884 .filter(|e| e.kind == EdgeKind::Contains)
885 .collect();
886 assert!(!contains.is_empty());
887 }
888
889 #[test]
890 fn parses_interface() {
891 let src = "package main\ntype Greeter interface { Greet() string }";
892 let (nodes, _) = parse(src);
893 let ifaces: Vec<_> = nodes
894 .iter()
895 .filter(|n| n.kind == NodeKind::Interface)
896 .collect();
897 assert_eq!(ifaces.len(), 1);
898 assert_eq!(ifaces[0].name, "Greeter");
899 }
900
901 #[test]
902 fn go_visibility_is_uppercase() {
903 let src = "package main\nfunc Exported() {}\nfunc unexported() {}";
904 let (nodes, _) = parse(src);
905 use gitcortex_core::schema::Visibility;
906 let exp = nodes.iter().find(|n| n.name == "Exported").unwrap();
907 let unexp = nodes.iter().find(|n| n.name == "unexported").unwrap();
908 assert_eq!(exp.metadata.visibility, Visibility::Pub);
909 assert_eq!(unexp.metadata.visibility, Visibility::Private);
910 }
911
912 #[test]
913 fn detects_call_edges() {
914 let src = "package main\nfunc Caller() { Callee() }\nfunc Callee() {}";
915 let (_, edges) = parse(src);
916 let calls: Vec<_> = edges.iter().filter(|e| e.kind == EdgeKind::Calls).collect();
917 assert_eq!(calls.len(), 1);
918 }
919
920 #[test]
921 fn package_node_is_emitted() {
922 let src = "package mypackage\nfunc Foo() {}";
923 let (nodes, _) = parse(src);
924 let modules: Vec<_> = nodes
925 .iter()
926 .filter(|n| n.kind == NodeKind::Module)
927 .collect();
928 assert_eq!(modules.len(), 1);
929 assert_eq!(modules[0].name, "mypackage");
930 }
931
932 #[test]
933 fn detects_import_declaration() {
934 let src = "package main\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n)\nfunc main() {}";
935 let (_, _, _, _, _, imports) = parse_full(src);
936 assert!(
937 imports.iter().any(|(_, n)| n == "fmt"),
938 "expected import 'fmt', got: {imports:?}"
939 );
940 assert!(
941 imports.iter().any(|(_, n)| n == "exec"),
942 "expected import 'exec' (last segment of os/exec), got: {imports:?}"
943 );
944 }
945
946 #[test]
947 fn detects_fn_type_uses() {
948 let src = "package main\ntype Request struct{}\ntype Response struct{}\nfunc Handle(req *Request) *Response { return nil }";
949 let (_, _, _, uses, _, _) = parse_full(src);
950 assert!(
951 uses.iter().any(|(_, n)| n == "Request"),
952 "expected Uses edge to Request, got: {uses:?}"
953 );
954 assert!(
955 uses.iter().any(|(_, n)| n == "Response"),
956 "expected Uses edge to Response, got: {uses:?}"
957 );
958 }
959
960 #[test]
961 fn detects_interface_assertion() {
962 let src = "package main\ntype Greeter interface { Greet() string }\ntype Person struct{}\nvar _ Greeter = (*Person)(nil)";
963 let (_, _, _, _, implements, _) = parse_full(src);
964 assert!(
965 implements.iter().any(|(_, n)| n == "Greeter"),
966 "expected Implements edge to Greeter, got: {implements:?}"
967 );
968 }
969
970 #[test]
971 fn captures_interface_methods() {
972 let src = "package main\ntype Greeter interface { Greet() string\nGetName() string }";
973 let (nodes, edges) = parse(src);
974 let methods: Vec<_> = nodes
975 .iter()
976 .filter(|n| n.kind == NodeKind::Method)
977 .collect();
978 assert_eq!(methods.len(), 2, "expected 2 interface method specs");
979 let contains: Vec<_> = edges
980 .iter()
981 .filter(|e| e.kind == EdgeKind::Contains)
982 .collect();
983 assert_eq!(
984 contains.len(),
985 2,
986 "expected 2 Contains edges from interface to methods"
987 );
988 }
989}