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