Skip to main content

agentic_codebase/parse/
go.rs

1//! Go parsing using tree-sitter.
2//!
3//! Extracts functions, methods, types, imports, packages.
4
5use std::path::Path;
6
7use crate::types::{AcbResult, CodeUnitType, Language, Visibility};
8
9use super::treesitter::{get_node_text, node_to_span};
10use super::{LanguageParser, RawCodeUnit};
11
12/// Go language parser.
13pub struct GoParser;
14
15impl Default for GoParser {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20
21impl GoParser {
22    /// Create a new Go parser.
23    pub fn new() -> Self {
24        Self
25    }
26
27    fn extract_from_node(
28        &self,
29        node: tree_sitter::Node,
30        source: &str,
31        file_path: &Path,
32        units: &mut Vec<RawCodeUnit>,
33        next_id: &mut u64,
34        parent_qname: &str,
35    ) {
36        let mut cursor = node.walk();
37        for child in node.children(&mut cursor) {
38            match child.kind() {
39                "function_declaration" => {
40                    if let Some(unit) =
41                        self.extract_function(child, source, file_path, parent_qname, next_id)
42                    {
43                        units.push(unit);
44                    }
45                }
46                "method_declaration" => {
47                    if let Some(unit) =
48                        self.extract_method(child, source, file_path, parent_qname, next_id)
49                    {
50                        units.push(unit);
51                    }
52                }
53                "type_declaration" => {
54                    self.extract_type_decl(child, source, file_path, units, next_id, parent_qname);
55                }
56                "import_declaration" => {
57                    if let Some(unit) =
58                        self.extract_import(child, source, file_path, parent_qname, next_id)
59                    {
60                        units.push(unit);
61                    }
62                }
63                _ => {}
64            }
65        }
66    }
67
68    fn extract_function(
69        &self,
70        node: tree_sitter::Node,
71        source: &str,
72        file_path: &Path,
73        parent_qname: &str,
74        next_id: &mut u64,
75    ) -> Option<RawCodeUnit> {
76        let name_node = node.child_by_field_name("name")?;
77        let name = get_node_text(name_node, source).to_string();
78        let qname = go_qname(parent_qname, &name);
79        let span = node_to_span(node);
80
81        let vis = if name
82            .chars()
83            .next()
84            .map(|c| c.is_uppercase())
85            .unwrap_or(false)
86        {
87            Visibility::Public
88        } else {
89            Visibility::Private
90        };
91
92        let is_test = name.starts_with("Test") || name.starts_with("Benchmark");
93
94        let id = *next_id;
95        *next_id += 1;
96
97        let unit_type = if is_test {
98            CodeUnitType::Test
99        } else {
100            CodeUnitType::Function
101        };
102        let mut unit =
103            RawCodeUnit::new(unit_type, Language::Go, name, file_path.to_path_buf(), span);
104        unit.temp_id = id;
105        unit.qualified_name = qname;
106        unit.visibility = vis;
107
108        Some(unit)
109    }
110
111    fn extract_method(
112        &self,
113        node: tree_sitter::Node,
114        source: &str,
115        file_path: &Path,
116        parent_qname: &str,
117        next_id: &mut u64,
118    ) -> Option<RawCodeUnit> {
119        let name_node = node.child_by_field_name("name")?;
120        let name = get_node_text(name_node, source).to_string();
121        let qname = go_qname(parent_qname, &name);
122        let span = node_to_span(node);
123
124        let vis = if name
125            .chars()
126            .next()
127            .map(|c| c.is_uppercase())
128            .unwrap_or(false)
129        {
130            Visibility::Public
131        } else {
132            Visibility::Private
133        };
134
135        let id = *next_id;
136        *next_id += 1;
137
138        let mut unit = RawCodeUnit::new(
139            CodeUnitType::Function,
140            Language::Go,
141            name,
142            file_path.to_path_buf(),
143            span,
144        );
145        unit.temp_id = id;
146        unit.qualified_name = qname;
147        unit.visibility = vis;
148
149        Some(unit)
150    }
151
152    fn extract_type_decl(
153        &self,
154        node: tree_sitter::Node,
155        source: &str,
156        file_path: &Path,
157        units: &mut Vec<RawCodeUnit>,
158        next_id: &mut u64,
159        parent_qname: &str,
160    ) {
161        let mut cursor = node.walk();
162        for child in node.children(&mut cursor) {
163            if child.kind() == "type_spec" {
164                if let Some(name_node) = child.child_by_field_name("name") {
165                    let name = get_node_text(name_node, source).to_string();
166                    let qname = go_qname(parent_qname, &name);
167                    let span = node_to_span(child);
168
169                    let vis = if name
170                        .chars()
171                        .next()
172                        .map(|c| c.is_uppercase())
173                        .unwrap_or(false)
174                    {
175                        Visibility::Public
176                    } else {
177                        Visibility::Private
178                    };
179
180                    let id = *next_id;
181                    *next_id += 1;
182
183                    // Determine if it's an interface
184                    let type_def = child.child_by_field_name("type");
185                    let unit_type = if type_def
186                        .map(|t| t.kind() == "interface_type")
187                        .unwrap_or(false)
188                    {
189                        CodeUnitType::Trait
190                    } else {
191                        CodeUnitType::Type
192                    };
193
194                    let mut unit = RawCodeUnit::new(
195                        unit_type,
196                        Language::Go,
197                        name,
198                        file_path.to_path_buf(),
199                        span,
200                    );
201                    unit.temp_id = id;
202                    unit.qualified_name = qname;
203                    unit.visibility = vis;
204                    units.push(unit);
205                }
206            }
207        }
208    }
209
210    fn extract_import(
211        &self,
212        node: tree_sitter::Node,
213        source: &str,
214        file_path: &Path,
215        parent_qname: &str,
216        next_id: &mut u64,
217    ) -> Option<RawCodeUnit> {
218        let _text = get_node_text(node, source);
219        let span = node_to_span(node);
220
221        let id = *next_id;
222        *next_id += 1;
223
224        let mut unit = RawCodeUnit::new(
225            CodeUnitType::Import,
226            Language::Go,
227            "import".to_string(),
228            file_path.to_path_buf(),
229            span,
230        );
231        unit.temp_id = id;
232        unit.qualified_name = go_qname(parent_qname, "import");
233
234        Some(unit)
235    }
236}
237
238impl LanguageParser for GoParser {
239    fn extract_units(
240        &self,
241        tree: &tree_sitter::Tree,
242        source: &str,
243        file_path: &Path,
244    ) -> AcbResult<Vec<RawCodeUnit>> {
245        let mut units = Vec::new();
246        let mut next_id = 0u64;
247
248        let module_name = file_path
249            .file_stem()
250            .and_then(|s| s.to_str())
251            .unwrap_or("unknown")
252            .to_string();
253
254        let root_span = node_to_span(tree.root_node());
255        let mut module_unit = RawCodeUnit::new(
256            CodeUnitType::Module,
257            Language::Go,
258            module_name.clone(),
259            file_path.to_path_buf(),
260            root_span,
261        );
262        module_unit.temp_id = next_id;
263        module_unit.qualified_name = module_name.clone();
264        next_id += 1;
265        units.push(module_unit);
266
267        self.extract_from_node(
268            tree.root_node(),
269            source,
270            file_path,
271            &mut units,
272            &mut next_id,
273            &module_name,
274        );
275
276        Ok(units)
277    }
278
279    fn is_test_file(&self, path: &Path, _source: &str) -> bool {
280        path.file_name()
281            .and_then(|n| n.to_str())
282            .map(|n| n.ends_with("_test.go"))
283            .unwrap_or(false)
284    }
285}
286
287fn go_qname(parent: &str, name: &str) -> String {
288    if parent.is_empty() {
289        name.to_string()
290    } else {
291        format!("{}.{}", parent, name)
292    }
293}