Skip to main content

code_analyze_core/languages/
go.rs

1// SPDX-FileCopyrightText: 2026 code-analyze-mcp contributors
2// SPDX-License-Identifier: Apache-2.0
3use tree_sitter::Node;
4
5/// Tree-sitter query for extracting Go elements (functions, methods, and types).
6pub const ELEMENT_QUERY: &str = r"
7(function_declaration
8  name: (identifier) @func_name) @function
9(method_declaration
10  name: (field_identifier) @method_name) @function
11(type_spec
12  name: (type_identifier) @type_name
13  type: (struct_type)) @class
14(type_spec
15  name: (type_identifier) @type_name
16  type: (interface_type)) @class
17";
18
19/// Tree-sitter query for extracting function calls.
20pub const CALL_QUERY: &str = r"
21(call_expression
22  function: (identifier) @call)
23(call_expression
24  function: (selector_expression field: (field_identifier) @call))
25";
26
27/// Tree-sitter query for extracting type references.
28pub const REFERENCE_QUERY: &str = r"
29(type_identifier) @type_ref
30";
31
32/// Tree-sitter query for extracting Go imports.
33pub const IMPORT_QUERY: &str = r"
34(import_declaration) @import_path
35";
36
37/// Tree-sitter query for extracting definition and use sites.
38pub const DEFUSE_QUERY: &str = r"
39(short_var_declaration left: (expression_list (identifier) @write.short))
40(assignment_statement left: (expression_list (identifier) @write.assign))
41(var_declaration (var_spec (identifier) @write.var))
42(inc_statement (identifier) @writeread.inc)
43(dec_statement (identifier) @writeread.dec)
44(identifier) @read.usage
45";
46
47/// Find method name for a receiver type.
48#[must_use]
49pub fn find_method_for_receiver(
50    node: &Node,
51    source: &str,
52    _depth: Option<usize>,
53) -> Option<String> {
54    if node.kind() != "method_declaration" && node.kind() != "function_declaration" {
55        return None;
56    }
57    node.child_by_field_name("name").and_then(|n| {
58        let start = n.start_byte();
59        let end = n.end_byte();
60        if end <= source.len() {
61            Some(source[start..end].to_string())
62        } else {
63            None
64        }
65    })
66}
67
68/// Extract inheritance information from a Go type node.
69#[must_use]
70pub fn extract_inheritance(node: &Node, source: &str) -> Vec<String> {
71    let mut inherits = Vec::new();
72
73    // Get the type field from type_spec
74    if let Some(type_field) = node.child_by_field_name("type") {
75        match type_field.kind() {
76            "struct_type" => {
77                // For struct embedding, walk children for field_declaration_list
78                for i in 0..type_field.named_child_count() {
79                    if let Some(field_list) =
80                        type_field.named_child(u32::try_from(i).unwrap_or(u32::MAX))
81                        && field_list.kind() == "field_declaration_list"
82                    {
83                        // Walk field_declaration_list for field_declaration without name
84                        for j in 0..field_list.named_child_count() {
85                            if let Some(field) =
86                                field_list.named_child(u32::try_from(j).unwrap_or(u32::MAX))
87                                && field.kind() == "field_declaration"
88                                && field.child_by_field_name("name").is_none()
89                            {
90                                // Embedded type has no name field
91                                if let Some(type_node) = field.child_by_field_name("type") {
92                                    let text =
93                                        &source[type_node.start_byte()..type_node.end_byte()];
94                                    inherits.push(text.to_string());
95                                }
96                            }
97                        }
98                    }
99                }
100            }
101            "interface_type" => {
102                // For interface embedding, walk children for type_elem
103                for i in 0..type_field.named_child_count() {
104                    if let Some(elem) = type_field.named_child(u32::try_from(i).unwrap_or(u32::MAX))
105                        && elem.kind() == "type_elem"
106                    {
107                        let text = &source[elem.start_byte()..elem.end_byte()];
108                        inherits.push(text.to_string());
109                    }
110                }
111            }
112            _ => {}
113        }
114    }
115
116    inherits
117}
118
119#[cfg(all(test, feature = "lang-go"))]
120mod tests {
121    use super::*;
122    use crate::DefUseKind;
123    use crate::parser::SemanticExtractor;
124    use tree_sitter::Parser;
125
126    fn parse_go(source: &str) -> tree_sitter::Tree {
127        let mut parser = Parser::new();
128        parser
129            .set_language(&tree_sitter_go::LANGUAGE.into())
130            .expect("failed to set Go language");
131        parser.parse(source, None).expect("failed to parse source")
132    }
133
134    #[test]
135    fn test_extract_inheritance_struct_no_embeds() {
136        // Arrange: struct with no embedded types
137        let source = "package p\ntype Foo struct { x int }";
138        let tree = parse_go(source);
139        let root = tree.root_node();
140        // find the type_spec node
141        let type_spec = (0..root.named_child_count())
142            .filter_map(|i| root.named_child(i as u32))
143            .find_map(|n| {
144                if n.kind() == "type_declaration" {
145                    (0..n.named_child_count())
146                        .filter_map(|j| n.named_child(j as u32))
147                        .find(|c| c.kind() == "type_spec")
148                } else {
149                    None
150                }
151            })
152            .expect("expected type_spec node");
153        // Act
154        let result = extract_inheritance(&type_spec, source);
155        // Assert
156        assert!(
157            result.is_empty(),
158            "expected no inherited types, got {:?}",
159            result
160        );
161    }
162
163    #[test]
164    fn test_find_method_for_receiver_wrong_kind() {
165        // Arrange: use a struct node (not a method or function declaration)
166        let source = "package p\ntype Bar struct {}";
167        let tree = parse_go(source);
168        let root = tree.root_node();
169        let node = root.named_child(0).expect("expected child");
170        // Act
171        let result = find_method_for_receiver(&node, source, None);
172        // Assert
173        assert_eq!(result, None);
174    }
175
176    #[test]
177    fn test_defuse_query_write_site() {
178        // Arrange
179        let src = "package p\nfunc main() { x := 1 }\n";
180        let sites = SemanticExtractor::extract_def_use_for_file(src, "go", "x", "test.go", None);
181        assert!(!sites.is_empty(), "defuse sites should not be empty");
182        let has_write = sites.iter().any(|s| matches!(s.kind, DefUseKind::Write));
183        assert!(has_write, "should contain a Write DefUseSite");
184    }
185}