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::{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 ..Default::default()
208 },
209 }
210 }
211
212 fn collect_names(&mut self, node: TsNode<'_>) {
215 let mut cursor = node.walk();
216 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
217 for child in children {
218 match child.kind() {
219 "function_declaration" => {
220 if let Some(name_node) = child.child_by_field_name("name") {
221 let name = self.text(name_node).to_owned();
222 self.fn_index.entry(name).or_default();
223 }
224 }
225 "method_declaration" => {
226 if let Some(name_node) = child.child_by_field_name("name") {
227 let name = self.text(name_node).to_owned();
228 self.fn_index.entry(name).or_default();
229 }
230 }
231 "type_declaration" => {
232 self.collect_type_decl_names(child);
233 }
234 _ => {}
235 }
236 }
237 }
238
239 fn collect_type_decl_names(&mut self, decl: TsNode<'_>) {
240 let mut cursor = decl.walk();
241 for spec in decl.named_children(&mut cursor) {
242 if spec.kind() != "type_spec" {
243 continue;
244 }
245 if let Some(name_node) = spec.child_by_field_name("name") {
246 let name = self.text(name_node).to_owned();
247 if let Some(type_node) = spec.child_by_field_name("type") {
248 if matches!(type_node.kind(), "struct_type" | "interface_type") {
249 self.type_index.entry(name).or_default();
250 }
251 }
252 }
253 }
254 }
255
256 fn visit_source_file(&mut self, node: TsNode<'_>) {
259 let mut cursor = node.walk();
260 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
261 for child in children {
262 self.visit_top_level(child);
263 }
264 }
265
266 fn visit_top_level(&mut self, node: TsNode<'_>) {
267 match node.kind() {
268 "function_declaration" => self.visit_function(node, &[]),
269 "method_declaration" => self.visit_method(node),
270 "type_declaration" => self.visit_type_decl(node),
271 "const_declaration" => self.visit_const_decl(node),
272 _ => {}
273 }
274 }
275
276 fn visit_function(&mut self, node: TsNode<'_>, scope: &[String]) {
277 let Some(name_node) = node.child_by_field_name("name") else {
278 return;
279 };
280 let name = self.text(name_node).to_owned();
281 let id = self
282 .fn_index
283 .get(&name)
284 .cloned()
285 .unwrap_or_else(NodeId::new);
286 let mut graph_node =
287 self.make_node(id.clone(), NodeKind::Function, name.clone(), scope, node);
288
289 if name == "init" || name == "main" {
291 graph_node.metadata.is_static = true;
292 }
293
294 graph_node.metadata.generic_bounds = self.collect_generic_bounds(node);
297
298 self.nodes.push(graph_node);
299
300 self.extract_fn_type_uses(node, &id);
301
302 if let Some(body) = node.child_by_field_name("body") {
303 self.collect_calls(body, &id);
304 }
305 }
306
307 fn visit_method(&mut self, node: TsNode<'_>) {
308 let Some(name_node) = node.child_by_field_name("name") else {
309 return;
310 };
311 let name = self.text(name_node).to_owned();
312
313 let receiver_type = self.receiver_type(node);
314 let scope: Vec<String> = receiver_type.into_iter().collect();
315
316 let container_id = scope.first().and_then(|t| self.type_index.get(t).cloned());
317 let id = self
318 .fn_index
319 .get(&name)
320 .cloned()
321 .unwrap_or_else(NodeId::new);
322 let graph_node = self.make_node(id.clone(), NodeKind::Method, name, &scope, node);
323
324 if let Some(cid) = container_id {
325 self.edges.push(Edge {
326 src: cid,
327 dst: id.clone(),
328 kind: EdgeKind::Contains,
329 });
330 }
331 self.nodes.push(graph_node);
332
333 self.extract_fn_type_uses(node, &id);
334
335 if let Some(body) = node.child_by_field_name("body") {
336 self.collect_calls(body, &id);
337 }
338 }
339
340 fn receiver_type(&self, method_node: TsNode<'_>) -> Option<String> {
342 let recv = method_node.child_by_field_name("receiver")?;
343 let mut cursor = recv.walk();
344 for param in recv.named_children(&mut cursor) {
345 if param.kind() != "parameter_declaration" {
346 continue;
347 }
348 if let Some(type_node) = param.child_by_field_name("type") {
349 return match type_node.kind() {
350 "type_identifier" => Some(self.text(type_node).to_owned()),
351 "pointer_type" => {
352 let mut c = type_node.walk();
353 let result = type_node
354 .named_children(&mut c)
355 .find(|n| n.kind() == "type_identifier")
356 .map(|n| self.text(n).to_owned());
357 result
358 }
359 _ => None,
360 };
361 }
362 }
363 None
364 }
365
366 fn visit_type_decl(&mut self, decl: TsNode<'_>) {
367 let mut cursor = decl.walk();
368 let specs: Vec<TsNode<'_>> = decl.named_children(&mut cursor).collect();
369 for spec in specs {
370 if spec.kind() != "type_spec" {
371 continue;
372 }
373 let Some(name_node) = spec.child_by_field_name("name") else {
374 continue;
375 };
376 let name = self.text(name_node).to_owned();
377 let Some(type_node) = spec.child_by_field_name("type") else {
378 continue;
379 };
380
381 match type_node.kind() {
382 "struct_type" => {
383 let id = self
384 .type_index
385 .get(&name)
386 .cloned()
387 .unwrap_or_else(NodeId::new);
388 let mut graph_node =
389 self.make_node(id.clone(), NodeKind::Struct, name, &[], spec);
390 graph_node.metadata.generic_bounds = self.collect_generic_bounds(spec);
392 self.nodes.push(graph_node);
393 self.extract_struct_field_uses(type_node, &id);
395 }
396 "interface_type" => {
397 let id = self
398 .type_index
399 .get(&name)
400 .cloned()
401 .unwrap_or_else(NodeId::new);
402 let mut graph_node =
403 self.make_node(id.clone(), NodeKind::Trait, name, &[], spec);
404 graph_node.metadata.generic_bounds = self.collect_generic_bounds(spec);
406 self.nodes.push(graph_node);
407 self.extract_interface_methods(type_node, &id);
409 }
410 _ => {
411 let id = NodeId::new();
412 let graph_node = self.make_node(id, NodeKind::TypeAlias, name, &[], spec);
413 self.nodes.push(graph_node);
414 }
415 }
416 }
417 }
418
419 fn visit_const_decl(&mut self, node: TsNode<'_>) {
420 let mut cursor = node.walk();
421 for spec in node.named_children(&mut cursor) {
422 if spec.kind() != "const_spec" {
423 continue;
424 }
425 let Some(name_node) = spec.child_by_field_name("name") else {
426 continue;
427 };
428 let name = self.text(name_node).to_owned();
429 let id = NodeId::new();
430 let mut graph_node = self.make_node(id, NodeKind::Constant, name, &[], spec);
431 graph_node.metadata.is_const = true;
432 self.nodes.push(graph_node);
433 }
434 }
435
436 fn collect_imports(&mut self, node: TsNode<'_>) {
439 let mut cursor = node.walk();
440 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
441 for child in children {
442 if child.kind() != "import_declaration" {
443 continue;
444 }
445 let mut c = child.walk();
447 let decl_children: Vec<TsNode<'_>> = child.named_children(&mut c).collect();
448 for dc in decl_children {
449 match dc.kind() {
450 "import_spec" => self.record_import_spec(dc),
451 "import_spec_list" => {
452 let mut cc = dc.walk();
453 let specs: Vec<TsNode<'_>> = dc.named_children(&mut cc).collect();
454 for spec in specs {
455 if spec.kind() == "import_spec" {
456 self.record_import_spec(spec);
457 }
458 }
459 }
460 _ => {}
461 }
462 }
463 }
464 }
465
466 fn record_import_spec(&mut self, spec: TsNode<'_>) {
467 let alias = spec
469 .child_by_field_name("name")
470 .map(|n| self.text(n).to_owned());
471
472 if alias.as_deref() == Some("_") {
474 return;
475 }
476
477 let pkg_name = if let Some(alias) = alias {
478 alias
479 } else if let Some(path_node) = spec.child_by_field_name("path") {
480 let raw = self.text(path_node).trim_matches('"').trim_matches('\'');
482 raw.split('/').next_back().unwrap_or(raw).to_owned()
483 } else {
484 return;
485 };
486
487 self.deferred_imports
488 .push((self.package_id.clone(), pkg_name));
489 }
490
491 fn collect_interface_assertions(&mut self, node: TsNode<'_>) {
495 let mut cursor = node.walk();
496 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
497 for child in children {
498 if child.kind() != "var_declaration" {
499 continue;
500 }
501 let mut c = child.walk();
502 let specs: Vec<TsNode<'_>> = child.named_children(&mut c).collect();
503 for spec in specs {
504 if spec.kind() != "var_spec" {
505 continue;
506 }
507 let mut cc = spec.walk();
508 let spec_children: Vec<TsNode<'_>> = spec.named_children(&mut cc).collect();
509 if spec_children.len() < 3 {
511 continue;
512 }
513 if spec_children[0].kind() != "identifier" || self.text(spec_children[0]) != "_" {
514 continue;
515 }
516 if spec_children[1].kind() != "type_identifier" {
517 continue;
518 }
519 let interface_name = self.text(spec_children[1]).to_owned();
520 let value = spec_children[2];
522 let mut candidates = Vec::new();
523 self.collect_candidate_type_names(value, &mut candidates);
524 for struct_name in candidates {
525 if let Some(struct_id) = self.type_index.get(&struct_name).cloned() {
526 self.deferred_implements
527 .push((struct_id, interface_name.clone()));
528 }
529 }
530 }
531 }
532 }
533
534 fn collect_candidate_type_names(&self, node: TsNode<'_>, out: &mut Vec<String>) {
536 match node.kind() {
537 "identifier" | "type_identifier" => {
538 let name = self.text(node).to_owned();
539 if name != "nil" && !is_builtin_go_type(&name) {
540 out.push(name);
541 }
542 }
543 _ => {
544 let mut c = node.walk();
545 for child in node.named_children(&mut c) {
546 self.collect_candidate_type_names(child, out);
547 }
548 }
549 }
550 }
551
552 fn extract_fn_type_uses(&mut self, fn_node: TsNode<'_>, fn_id: &NodeId) {
556 if let Some(params) = fn_node.child_by_field_name("parameters") {
558 let mut c = params.walk();
559 let param_list: Vec<TsNode<'_>> = params.named_children(&mut c).collect();
560 for param in param_list {
561 if param.kind() == "parameter_declaration"
562 || param.kind() == "variadic_parameter_declaration"
563 {
564 if let Some(type_node) = param.child_by_field_name("type") {
565 for name in self.collect_type_idents(type_node) {
566 self.deferred_uses.push((fn_id.clone(), name));
567 }
568 }
569 }
570 }
571 }
572 if let Some(result) = fn_node.child_by_field_name("result") {
574 match result.kind() {
575 "parameter_list" => {
576 let mut c = result.walk();
577 let ret_params: Vec<TsNode<'_>> = result.named_children(&mut c).collect();
578 for rp in ret_params {
579 if rp.kind() == "parameter_declaration" {
580 if let Some(type_node) = rp.child_by_field_name("type") {
581 for name in self.collect_type_idents(type_node) {
582 self.deferred_uses.push((fn_id.clone(), name));
583 }
584 }
585 }
586 }
587 }
588 _ => {
590 for name in self.collect_type_idents(result) {
591 self.deferred_uses.push((fn_id.clone(), name));
592 }
593 }
594 }
595 }
596 }
597
598 fn extract_struct_field_uses(&mut self, struct_type: TsNode<'_>, struct_id: &NodeId) {
601 let mut tw = struct_type.walk();
602 let top: Vec<TsNode<'_>> = struct_type.named_children(&mut tw).collect();
603 let Some(field_list) = top
604 .iter()
605 .find(|n| n.kind() == "field_declaration_list")
606 .copied()
607 else {
608 return;
609 };
610 let mut c = field_list.walk();
611 let fields: Vec<TsNode<'_>> = field_list.named_children(&mut c).collect();
612 for field in fields {
613 if field.kind() == "field_declaration" {
614 let has_name = field.child_by_field_name("name").is_some();
618 if let Some(type_node) = field.child_by_field_name("type") {
619 let type_names = self.collect_type_idents(type_node);
620 for name in &type_names {
621 self.deferred_uses.push((struct_id.clone(), name.clone()));
622 }
623 if !has_name {
625 for name in type_names {
626 self.deferred_inherits.push((struct_id.clone(), name));
627 }
628 }
629 }
630 }
631 }
632 }
633
634 fn extract_interface_methods(&mut self, interface_type: TsNode<'_>, iface_id: &NodeId) {
636 let mut c = interface_type.walk();
637 let children: Vec<TsNode<'_>> = interface_type.named_children(&mut c).collect();
638 for child in children {
639 if child.kind() == "method_elem" {
641 let mut cc = child.walk();
642 let method_children: Vec<TsNode<'_>> = child.named_children(&mut cc).collect();
643 let Some(name_node) = method_children
645 .iter()
646 .find(|n| n.kind() == "field_identifier")
647 else {
648 continue;
649 };
650 let name = self.text(*name_node).to_owned();
651 let id = NodeId::new();
652 let graph_node = self.make_node(id.clone(), NodeKind::Method, name, &[], child);
653 self.edges.push(Edge {
654 src: iface_id.clone(),
655 dst: id.clone(),
656 kind: EdgeKind::Contains,
657 });
658 self.nodes.push(graph_node);
659 }
660 }
661 }
662
663 fn collect_generic_bounds(&self, node: TsNode<'_>) -> Vec<String> {
668 let Some(type_params) = node.child_by_field_name("type_parameters") else {
669 return Vec::new();
670 };
671 let mut bounds = Vec::new();
672 let mut cursor = type_params.walk();
673 for child in type_params.named_children(&mut cursor) {
674 if child.kind() == "type_parameter_declaration" {
677 let name = child
678 .child_by_field_name("name")
679 .map(|n| self.text(n))
680 .unwrap_or("");
681 let constraint = child
682 .child_by_field_name("type")
683 .map(|n| self.text(n))
684 .unwrap_or("");
685 if !name.is_empty() {
686 let bound = if constraint.is_empty() {
687 name.to_owned()
688 } else {
689 format!("{name} {constraint}")
690 };
691 bounds.push(bound);
692 }
693 }
694 }
695 bounds
696 }
697
698 fn collect_type_idents(&self, node: TsNode<'_>) -> Vec<String> {
700 let mut names = Vec::new();
701 self.walk_type_idents(node, &mut names);
702 names
703 }
704
705 fn walk_type_idents(&self, node: TsNode<'_>, out: &mut Vec<String>) {
706 match node.kind() {
707 "type_identifier" => {
708 let name = self.text(node).to_owned();
709 if !is_builtin_go_type(&name) {
710 out.push(name);
711 }
712 }
713 _ => {
714 let mut c = node.walk();
715 for child in node.named_children(&mut c) {
716 self.walk_type_idents(child, out);
717 }
718 }
719 }
720 }
721
722 fn collect_calls(&mut self, node: TsNode<'_>, caller_id: &NodeId) {
725 let mut cursor = node.walk();
726 let children: Vec<TsNode<'_>> = node.named_children(&mut cursor).collect();
727 for child in children {
728 if child.kind() == "call_expression" {
729 if let Some(callee) = self.callee_name(child) {
730 self.record_call(caller_id.clone(), callee);
731 }
732 if let Some(args) = child.child_by_field_name("arguments") {
733 self.collect_calls(args, caller_id);
734 }
735 } else if child.kind() == "go_statement" {
736 if let Some(call) = child.named_child(0) {
738 if call.kind() == "call_expression" {
739 if let Some(callee) = self.callee_name(call) {
740 self.deferred_calls.push((caller_id.clone(), callee));
742 }
743 }
744 }
745 } else {
746 self.collect_calls(child, caller_id);
747 }
748 }
749 }
750
751 fn callee_name(&self, call_expr: TsNode<'_>) -> Option<String> {
752 let func = call_expr.child_by_field_name("function")?;
753 match func.kind() {
754 "identifier" => Some(self.text(func).to_owned()),
755 "selector_expression" => func
756 .child_by_field_name("field")
757 .map(|n| self.text(n).to_owned()),
758 _ => None,
759 }
760 }
761
762 fn record_call(&mut self, caller_id: NodeId, callee_name: String) {
763 if callee_name.is_empty() {
764 return;
765 }
766 if let Some(callee_id) = self.fn_index.get(&callee_name).cloned() {
767 let edge = Edge {
768 src: caller_id,
769 dst: callee_id,
770 kind: EdgeKind::Calls,
771 };
772 if !self.edges.contains(&edge) {
773 self.edges.push(edge);
774 }
775 } else if !self
776 .deferred_calls
777 .iter()
778 .any(|(c, n)| c == &caller_id && n == &callee_name)
779 {
780 self.deferred_calls.push((caller_id, callee_name));
781 }
782 }
783}
784
785fn is_builtin_go_type(name: &str) -> bool {
787 matches!(
788 name,
789 "bool"
790 | "byte"
791 | "complex64"
792 | "complex128"
793 | "error"
794 | "float32"
795 | "float64"
796 | "int"
797 | "int8"
798 | "int16"
799 | "int32"
800 | "int64"
801 | "rune"
802 | "string"
803 | "uint"
804 | "uint8"
805 | "uint16"
806 | "uint32"
807 | "uint64"
808 | "uintptr"
809 | "any"
810 | "comparable"
811 )
812}
813
814#[cfg(test)]
817mod tests {
818 use super::GoParser;
819 use crate::parser::LanguageParser;
820 use gitcortex_core::schema::{EdgeKind, NodeKind};
821 use std::path::Path;
822
823 fn parse(
824 src: &str,
825 ) -> (
826 Vec<gitcortex_core::graph::Node>,
827 Vec<gitcortex_core::graph::Edge>,
828 ) {
829 let r = GoParser::new().parse(Path::new("test.go"), src).unwrap();
830 (r.nodes, r.edges)
831 }
832
833 #[allow(clippy::type_complexity)]
834 fn parse_full(
835 src: &str,
836 ) -> (
837 Vec<gitcortex_core::graph::Node>,
838 Vec<gitcortex_core::graph::Edge>,
839 Vec<(gitcortex_core::graph::NodeId, String)>,
840 Vec<(gitcortex_core::graph::NodeId, String)>,
841 Vec<(gitcortex_core::graph::NodeId, String)>,
842 Vec<(gitcortex_core::graph::NodeId, String)>,
843 ) {
844 let r = GoParser::new().parse(Path::new("test.go"), src).unwrap();
845 (
846 r.nodes,
847 r.edges,
848 r.deferred_calls,
849 r.deferred_uses,
850 r.deferred_implements,
851 r.deferred_imports,
852 )
853 }
854
855 #[test]
856 fn parses_function() {
857 let src = "package main\nfunc Greet(name string) string { return name }";
858 let (nodes, _) = parse(src);
859 let fns: Vec<_> = nodes
860 .iter()
861 .filter(|n| n.kind == NodeKind::Function)
862 .collect();
863 assert_eq!(fns.len(), 1);
864 assert_eq!(fns[0].name, "Greet");
865 }
866
867 #[test]
868 fn parses_struct_and_method() {
869 let src = "package main\ntype Person struct { Name string }\nfunc (p *Person) Greet() string { return p.Name }";
870 let (nodes, edges) = parse(src);
871 let structs: Vec<_> = nodes
872 .iter()
873 .filter(|n| n.kind == NodeKind::Struct)
874 .collect();
875 let methods: Vec<_> = nodes
876 .iter()
877 .filter(|n| n.kind == NodeKind::Method)
878 .collect();
879 assert_eq!(structs.len(), 1);
880 assert_eq!(methods.len(), 1);
881 let contains: Vec<_> = edges
882 .iter()
883 .filter(|e| e.kind == EdgeKind::Contains)
884 .collect();
885 assert!(!contains.is_empty());
886 }
887
888 #[test]
889 fn parses_interface() {
890 let src = "package main\ntype Greeter interface { Greet() string }";
891 let (nodes, _) = parse(src);
892 let traits: Vec<_> = nodes.iter().filter(|n| n.kind == NodeKind::Trait).collect();
893 assert_eq!(traits.len(), 1);
894 assert_eq!(traits[0].name, "Greeter");
895 }
896
897 #[test]
898 fn go_visibility_is_uppercase() {
899 let src = "package main\nfunc Exported() {}\nfunc unexported() {}";
900 let (nodes, _) = parse(src);
901 use gitcortex_core::schema::Visibility;
902 let exp = nodes.iter().find(|n| n.name == "Exported").unwrap();
903 let unexp = nodes.iter().find(|n| n.name == "unexported").unwrap();
904 assert_eq!(exp.metadata.visibility, Visibility::Pub);
905 assert_eq!(unexp.metadata.visibility, Visibility::Private);
906 }
907
908 #[test]
909 fn detects_call_edges() {
910 let src = "package main\nfunc Caller() { Callee() }\nfunc Callee() {}";
911 let (_, edges) = parse(src);
912 let calls: Vec<_> = edges.iter().filter(|e| e.kind == EdgeKind::Calls).collect();
913 assert_eq!(calls.len(), 1);
914 }
915
916 #[test]
917 fn package_node_is_emitted() {
918 let src = "package mypackage\nfunc Foo() {}";
919 let (nodes, _) = parse(src);
920 let modules: Vec<_> = nodes
921 .iter()
922 .filter(|n| n.kind == NodeKind::Module)
923 .collect();
924 assert_eq!(modules.len(), 1);
925 assert_eq!(modules[0].name, "mypackage");
926 }
927
928 #[test]
929 fn detects_import_declaration() {
930 let src = "package main\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n)\nfunc main() {}";
931 let (_, _, _, _, _, imports) = parse_full(src);
932 assert!(
933 imports.iter().any(|(_, n)| n == "fmt"),
934 "expected import 'fmt', got: {imports:?}"
935 );
936 assert!(
937 imports.iter().any(|(_, n)| n == "exec"),
938 "expected import 'exec' (last segment of os/exec), got: {imports:?}"
939 );
940 }
941
942 #[test]
943 fn detects_fn_type_uses() {
944 let src = "package main\ntype Request struct{}\ntype Response struct{}\nfunc Handle(req *Request) *Response { return nil }";
945 let (_, _, _, uses, _, _) = parse_full(src);
946 assert!(
947 uses.iter().any(|(_, n)| n == "Request"),
948 "expected Uses edge to Request, got: {uses:?}"
949 );
950 assert!(
951 uses.iter().any(|(_, n)| n == "Response"),
952 "expected Uses edge to Response, got: {uses:?}"
953 );
954 }
955
956 #[test]
957 fn detects_interface_assertion() {
958 let src = "package main\ntype Greeter interface { Greet() string }\ntype Person struct{}\nvar _ Greeter = (*Person)(nil)";
959 let (_, _, _, _, implements, _) = parse_full(src);
960 assert!(
961 implements.iter().any(|(_, n)| n == "Greeter"),
962 "expected Implements edge to Greeter, got: {implements:?}"
963 );
964 }
965
966 #[test]
967 fn captures_interface_methods() {
968 let src = "package main\ntype Greeter interface { Greet() string\nGetName() string }";
969 let (nodes, edges) = parse(src);
970 let methods: Vec<_> = nodes
971 .iter()
972 .filter(|n| n.kind == NodeKind::Method)
973 .collect();
974 assert_eq!(methods.len(), 2, "expected 2 interface method specs");
975 let contains: Vec<_> = edges
976 .iter()
977 .filter(|e| e.kind == EdgeKind::Contains)
978 .collect();
979 assert_eq!(
980 contains.len(),
981 2,
982 "expected 2 Contains edges from interface to methods"
983 );
984 }
985}