acp/ast/languages/
go.rs

1//! @acp:module "Go Extractor"
2//! @acp:summary "Symbol extraction for Go source files"
3//! @acp:domain cli
4//! @acp:layer parsing
5
6use super::{node_text, LanguageExtractor};
7use crate::ast::{ExtractedSymbol, FunctionCall, Import, Parameter, SymbolKind, Visibility};
8use crate::error::Result;
9use tree_sitter::{Language, Node, Tree};
10
11/// Go language extractor
12pub struct GoExtractor;
13
14impl LanguageExtractor for GoExtractor {
15    fn language(&self) -> Language {
16        tree_sitter_go::LANGUAGE.into()
17    }
18
19    fn name(&self) -> &'static str {
20        "go"
21    }
22
23    fn extensions(&self) -> &'static [&'static str] {
24        &["go"]
25    }
26
27    fn extract_symbols(&self, tree: &Tree, source: &str) -> Result<Vec<ExtractedSymbol>> {
28        let mut symbols = Vec::new();
29        let root = tree.root_node();
30        self.extract_symbols_recursive(&root, source, &mut symbols, None);
31        Ok(symbols)
32    }
33
34    fn extract_imports(&self, tree: &Tree, source: &str) -> Result<Vec<Import>> {
35        let mut imports = Vec::new();
36        let root = tree.root_node();
37        self.extract_imports_recursive(&root, source, &mut imports);
38        Ok(imports)
39    }
40
41    fn extract_calls(
42        &self,
43        tree: &Tree,
44        source: &str,
45        current_function: Option<&str>,
46    ) -> Result<Vec<FunctionCall>> {
47        let mut calls = Vec::new();
48        let root = tree.root_node();
49        self.extract_calls_recursive(&root, source, &mut calls, current_function);
50        Ok(calls)
51    }
52
53    fn extract_doc_comment(&self, node: &Node, source: &str) -> Option<String> {
54        // Look for comment groups before this node
55        let mut comments = Vec::new();
56        let mut current = node.prev_sibling();
57
58        while let Some(prev) = current {
59            if prev.kind() == "comment" {
60                let comment = node_text(&prev, source);
61                comments.push(comment.trim_start_matches("//").trim().to_string());
62                current = prev.prev_sibling();
63            } else {
64                break;
65            }
66        }
67
68        if comments.is_empty() {
69            None
70        } else {
71            comments.reverse();
72            Some(comments.join("\n"))
73        }
74    }
75}
76
77impl GoExtractor {
78    fn extract_symbols_recursive(
79        &self,
80        node: &Node,
81        source: &str,
82        symbols: &mut Vec<ExtractedSymbol>,
83        parent: Option<&str>,
84    ) {
85        match node.kind() {
86            "function_declaration" => {
87                if let Some(sym) = self.extract_function(node, source, parent) {
88                    symbols.push(sym);
89                }
90            }
91
92            "method_declaration" => {
93                if let Some(sym) = self.extract_method(node, source) {
94                    symbols.push(sym);
95                }
96            }
97
98            "type_declaration" => {
99                self.extract_type_declaration(node, source, symbols, parent);
100            }
101
102            "const_declaration" | "var_declaration" => {
103                self.extract_var_declaration(node, source, symbols, parent);
104            }
105
106            _ => {}
107        }
108
109        // Recurse into children
110        let mut cursor = node.walk();
111        for child in node.children(&mut cursor) {
112            self.extract_symbols_recursive(&child, source, symbols, parent);
113        }
114    }
115
116    fn extract_function(
117        &self,
118        node: &Node,
119        source: &str,
120        parent: Option<&str>,
121    ) -> Option<ExtractedSymbol> {
122        let name_node = node.child_by_field_name("name")?;
123        let name = node_text(&name_node, source).to_string();
124
125        let mut sym = ExtractedSymbol::new(
126            name.clone(),
127            SymbolKind::Function,
128            node.start_position().row + 1,
129            node.end_position().row + 1,
130        )
131        .with_columns(node.start_position().column, node.end_position().column);
132
133        // Go: Exported if first letter is uppercase
134        if name
135            .chars()
136            .next()
137            .map(|c| c.is_uppercase())
138            .unwrap_or(false)
139        {
140            sym = sym.exported();
141            sym.visibility = Visibility::Public;
142        } else {
143            sym.visibility = Visibility::Private;
144        }
145
146        // Extract parameters
147        if let Some(params) = node.child_by_field_name("parameters") {
148            self.extract_parameters(&params, source, &mut sym);
149        }
150
151        // Extract return type
152        if let Some(result) = node.child_by_field_name("result") {
153            sym.return_type = Some(node_text(&result, source).to_string());
154        }
155
156        sym.doc_comment = self.extract_doc_comment(node, source);
157
158        if let Some(p) = parent {
159            sym = sym.with_parent(p);
160        }
161
162        sym.signature = Some(self.build_function_signature(node, source));
163
164        Some(sym)
165    }
166
167    fn extract_method(&self, node: &Node, source: &str) -> Option<ExtractedSymbol> {
168        let name_node = node.child_by_field_name("name")?;
169        let name = node_text(&name_node, source).to_string();
170
171        let mut sym = ExtractedSymbol::new(
172            name.clone(),
173            SymbolKind::Method,
174            node.start_position().row + 1,
175            node.end_position().row + 1,
176        );
177
178        // Go: Exported if first letter is uppercase
179        if name
180            .chars()
181            .next()
182            .map(|c| c.is_uppercase())
183            .unwrap_or(false)
184        {
185            sym = sym.exported();
186            sym.visibility = Visibility::Public;
187        } else {
188            sym.visibility = Visibility::Private;
189        }
190
191        // Get receiver type as parent
192        if let Some(receiver) = node.child_by_field_name("receiver") {
193            let receiver_text = node_text(&receiver, source);
194            // Extract type name from receiver (e.g., "(s *Server)" -> "Server")
195            let type_name = receiver_text
196                .trim_matches(|c| c == '(' || c == ')')
197                .split_whitespace()
198                .last()
199                .unwrap_or("")
200                .trim_start_matches('*');
201            sym = sym.with_parent(type_name);
202        }
203
204        // Extract parameters
205        if let Some(params) = node.child_by_field_name("parameters") {
206            self.extract_parameters(&params, source, &mut sym);
207        }
208
209        // Extract return type
210        if let Some(result) = node.child_by_field_name("result") {
211            sym.return_type = Some(node_text(&result, source).to_string());
212        }
213
214        sym.doc_comment = self.extract_doc_comment(node, source);
215        sym.signature = Some(self.build_method_signature(node, source));
216
217        Some(sym)
218    }
219
220    fn extract_type_declaration(
221        &self,
222        node: &Node,
223        source: &str,
224        symbols: &mut Vec<ExtractedSymbol>,
225        parent: Option<&str>,
226    ) {
227        let mut cursor = node.walk();
228        for child in node.children(&mut cursor) {
229            if child.kind() == "type_spec" {
230                if let Some(name_node) = child.child_by_field_name("name") {
231                    let name = node_text(&name_node, source).to_string();
232
233                    // Determine the kind based on the type
234                    let type_node = child.child_by_field_name("type");
235                    let kind = type_node
236                        .map(|t| match t.kind() {
237                            "struct_type" => SymbolKind::Struct,
238                            "interface_type" => SymbolKind::Interface,
239                            _ => SymbolKind::TypeAlias,
240                        })
241                        .unwrap_or(SymbolKind::TypeAlias);
242
243                    let mut sym = ExtractedSymbol::new(
244                        name.clone(),
245                        kind,
246                        child.start_position().row + 1,
247                        child.end_position().row + 1,
248                    );
249
250                    // Go: Exported if first letter is uppercase
251                    if name
252                        .chars()
253                        .next()
254                        .map(|c| c.is_uppercase())
255                        .unwrap_or(false)
256                    {
257                        sym = sym.exported();
258                        sym.visibility = Visibility::Public;
259                    } else {
260                        sym.visibility = Visibility::Private;
261                    }
262
263                    sym.doc_comment = self.extract_doc_comment(&child, source);
264
265                    if let Some(p) = parent {
266                        sym = sym.with_parent(p);
267                    }
268
269                    symbols.push(sym);
270
271                    // Extract struct fields
272                    if kind == SymbolKind::Struct {
273                        if let Some(type_node) = type_node {
274                            self.extract_struct_fields(&type_node, source, symbols, Some(&name));
275                        }
276                    }
277
278                    // Extract interface methods
279                    if kind == SymbolKind::Interface {
280                        if let Some(type_node) = type_node {
281                            self.extract_interface_methods(
282                                &type_node,
283                                source,
284                                symbols,
285                                Some(&name),
286                            );
287                        }
288                    }
289                }
290            }
291        }
292    }
293
294    fn extract_struct_fields(
295        &self,
296        node: &Node,
297        source: &str,
298        symbols: &mut Vec<ExtractedSymbol>,
299        struct_name: Option<&str>,
300    ) {
301        // Go struct_type has field_declaration_list as direct child (not a "body" field)
302        let mut cursor = node.walk();
303        for child in node.children(&mut cursor) {
304            if child.kind() == "field_declaration_list" {
305                let mut field_cursor = child.walk();
306                for field in child.children(&mut field_cursor) {
307                    if field.kind() == "field_declaration" {
308                        self.extract_single_field(&field, source, symbols, struct_name);
309                    }
310                }
311            } else if child.kind() == "field_declaration" {
312                // Direct field declaration
313                self.extract_single_field(&child, source, symbols, struct_name);
314            }
315        }
316    }
317
318    fn extract_single_field(
319        &self,
320        field: &Node,
321        source: &str,
322        symbols: &mut Vec<ExtractedSymbol>,
323        struct_name: Option<&str>,
324    ) {
325        let mut field_cursor = field.walk();
326        for field_child in field.children(&mut field_cursor) {
327            if field_child.kind() == "field_identifier" {
328                let name = node_text(&field_child, source).to_string();
329
330                let mut sym = ExtractedSymbol::new(
331                    name.clone(),
332                    SymbolKind::Field,
333                    field.start_position().row + 1,
334                    field.end_position().row + 1,
335                );
336
337                // Go: Exported if first letter is uppercase
338                if name
339                    .chars()
340                    .next()
341                    .map(|c| c.is_uppercase())
342                    .unwrap_or(false)
343                {
344                    sym = sym.exported();
345                    sym.visibility = Visibility::Public;
346                } else {
347                    sym.visibility = Visibility::Private;
348                }
349
350                if let Some(type_node) = field.child_by_field_name("type") {
351                    sym.type_info = Some(node_text(&type_node, source).to_string());
352                }
353
354                if let Some(p) = struct_name {
355                    sym = sym.with_parent(p);
356                }
357
358                symbols.push(sym);
359            }
360        }
361    }
362
363    fn extract_interface_methods(
364        &self,
365        node: &Node,
366        source: &str,
367        symbols: &mut Vec<ExtractedSymbol>,
368        interface_name: Option<&str>,
369    ) {
370        let mut cursor = node.walk();
371        for child in node.children(&mut cursor) {
372            if child.kind() == "method_spec" {
373                if let Some(name_node) = child.child_by_field_name("name") {
374                    let name = node_text(&name_node, source).to_string();
375
376                    let mut sym = ExtractedSymbol::new(
377                        name.clone(),
378                        SymbolKind::Method,
379                        child.start_position().row + 1,
380                        child.end_position().row + 1,
381                    );
382
383                    if name
384                        .chars()
385                        .next()
386                        .map(|c| c.is_uppercase())
387                        .unwrap_or(false)
388                    {
389                        sym = sym.exported();
390                        sym.visibility = Visibility::Public;
391                    } else {
392                        sym.visibility = Visibility::Private;
393                    }
394
395                    if let Some(params) = child.child_by_field_name("parameters") {
396                        self.extract_parameters(&params, source, &mut sym);
397                    }
398
399                    if let Some(result) = child.child_by_field_name("result") {
400                        sym.return_type = Some(node_text(&result, source).to_string());
401                    }
402
403                    if let Some(p) = interface_name {
404                        sym = sym.with_parent(p);
405                    }
406
407                    symbols.push(sym);
408                }
409            }
410        }
411    }
412
413    fn extract_var_declaration(
414        &self,
415        node: &Node,
416        source: &str,
417        symbols: &mut Vec<ExtractedSymbol>,
418        parent: Option<&str>,
419    ) {
420        let is_const = node.kind() == "const_declaration";
421
422        let mut cursor = node.walk();
423        for child in node.children(&mut cursor) {
424            if child.kind() == "const_spec" || child.kind() == "var_spec" {
425                let mut name_cursor = child.walk();
426                for name_child in child.children(&mut name_cursor) {
427                    if name_child.kind() == "identifier" {
428                        let name = node_text(&name_child, source).to_string();
429
430                        let mut sym = ExtractedSymbol::new(
431                            name.clone(),
432                            if is_const {
433                                SymbolKind::Constant
434                            } else {
435                                SymbolKind::Variable
436                            },
437                            child.start_position().row + 1,
438                            child.end_position().row + 1,
439                        );
440
441                        if name
442                            .chars()
443                            .next()
444                            .map(|c| c.is_uppercase())
445                            .unwrap_or(false)
446                        {
447                            sym = sym.exported();
448                            sym.visibility = Visibility::Public;
449                        } else {
450                            sym.visibility = Visibility::Private;
451                        }
452
453                        if let Some(type_node) = child.child_by_field_name("type") {
454                            sym.type_info = Some(node_text(&type_node, source).to_string());
455                        }
456
457                        if let Some(p) = parent {
458                            sym = sym.with_parent(p);
459                        }
460
461                        symbols.push(sym);
462                    }
463                }
464            }
465        }
466    }
467
468    fn extract_parameters(&self, params: &Node, source: &str, sym: &mut ExtractedSymbol) {
469        let mut cursor = params.walk();
470        for child in params.children(&mut cursor) {
471            if child.kind() == "parameter_declaration" {
472                let name = child
473                    .child_by_field_name("name")
474                    .map(|n| node_text(&n, source).to_string())
475                    .unwrap_or_default();
476
477                let type_info = child
478                    .child_by_field_name("type")
479                    .map(|n| node_text(&n, source).to_string());
480
481                // Handle variadic parameters
482                let is_rest = type_info
483                    .as_ref()
484                    .map(|t| t.starts_with("..."))
485                    .unwrap_or(false);
486
487                sym.add_parameter(Parameter {
488                    name,
489                    type_info,
490                    default_value: None,
491                    is_rest,
492                    is_optional: false,
493                });
494            }
495        }
496    }
497
498    fn extract_imports_recursive(&self, node: &Node, source: &str, imports: &mut Vec<Import>) {
499        if node.kind() == "import_declaration" {
500            self.parse_import_declaration(node, source, imports);
501        }
502
503        let mut cursor = node.walk();
504        for child in node.children(&mut cursor) {
505            self.extract_imports_recursive(&child, source, imports);
506        }
507    }
508
509    fn parse_import_declaration(&self, node: &Node, source: &str, imports: &mut Vec<Import>) {
510        let mut cursor = node.walk();
511        for child in node.children(&mut cursor) {
512            if child.kind() == "import_spec" || child.kind() == "import_spec_list" {
513                self.parse_import_spec(&child, source, imports);
514            }
515        }
516    }
517
518    fn parse_import_spec(&self, node: &Node, source: &str, imports: &mut Vec<Import>) {
519        if node.kind() == "import_spec_list" {
520            let mut cursor = node.walk();
521            for child in node.children(&mut cursor) {
522                if child.kind() == "import_spec" {
523                    self.parse_import_spec(&child, source, imports);
524                }
525            }
526            return;
527        }
528
529        let path = node
530            .child_by_field_name("path")
531            .map(|n| node_text(&n, source).trim_matches('"').to_string())
532            .unwrap_or_default();
533
534        let alias = node
535            .child_by_field_name("name")
536            .map(|n| node_text(&n, source).to_string());
537
538        let is_dot_import = alias.as_ref().map(|a| a == ".").unwrap_or(false);
539
540        imports.push(Import {
541            source: path,
542            names: Vec::new(),
543            is_default: false,
544            is_namespace: is_dot_import,
545            line: node.start_position().row + 1,
546        });
547    }
548
549    fn extract_calls_recursive(
550        &self,
551        node: &Node,
552        source: &str,
553        calls: &mut Vec<FunctionCall>,
554        current_function: Option<&str>,
555    ) {
556        if node.kind() == "call_expression" {
557            if let Some(call) = self.parse_call(node, source, current_function) {
558                calls.push(call);
559            }
560        }
561
562        let func_name = match node.kind() {
563            "function_declaration" | "method_declaration" => node
564                .child_by_field_name("name")
565                .map(|n| node_text(&n, source)),
566            _ => None,
567        };
568
569        let current = func_name
570            .map(String::from)
571            .or_else(|| current_function.map(String::from));
572
573        let mut cursor = node.walk();
574        for child in node.children(&mut cursor) {
575            self.extract_calls_recursive(&child, source, calls, current.as_deref());
576        }
577    }
578
579    fn parse_call(
580        &self,
581        node: &Node,
582        source: &str,
583        current_function: Option<&str>,
584    ) -> Option<FunctionCall> {
585        let function = node.child_by_field_name("function")?;
586
587        let (callee, is_method, receiver) = match function.kind() {
588            "selector_expression" => {
589                let operand = function
590                    .child_by_field_name("operand")
591                    .map(|n| node_text(&n, source).to_string());
592                let field = function
593                    .child_by_field_name("field")
594                    .map(|n| node_text(&n, source).to_string())?;
595                (field, true, operand)
596            }
597            "identifier" => (node_text(&function, source).to_string(), false, None),
598            _ => return None,
599        };
600
601        Some(FunctionCall {
602            caller: current_function.unwrap_or("<package>").to_string(),
603            callee,
604            line: node.start_position().row + 1,
605            is_method,
606            receiver,
607        })
608    }
609
610    fn build_function_signature(&self, node: &Node, source: &str) -> String {
611        let name = node
612            .child_by_field_name("name")
613            .map(|n| node_text(&n, source))
614            .unwrap_or("unknown");
615
616        let params = node
617            .child_by_field_name("parameters")
618            .map(|n| node_text(&n, source))
619            .unwrap_or("()");
620
621        let result = node
622            .child_by_field_name("result")
623            .map(|n| format!(" {}", node_text(&n, source)))
624            .unwrap_or_default();
625
626        format!("func {}{}{}", name, params, result)
627    }
628
629    fn build_method_signature(&self, node: &Node, source: &str) -> String {
630        let receiver = node
631            .child_by_field_name("receiver")
632            .map(|n| format!("{} ", node_text(&n, source)))
633            .unwrap_or_default();
634
635        let name = node
636            .child_by_field_name("name")
637            .map(|n| node_text(&n, source))
638            .unwrap_or("unknown");
639
640        let params = node
641            .child_by_field_name("parameters")
642            .map(|n| node_text(&n, source))
643            .unwrap_or("()");
644
645        let result = node
646            .child_by_field_name("result")
647            .map(|n| format!(" {}", node_text(&n, source)))
648            .unwrap_or_default();
649
650        format!("func {}{}{}{}", receiver, name, params, result)
651    }
652}
653
654#[cfg(test)]
655mod tests {
656    use super::*;
657
658    fn parse_go(source: &str) -> (Tree, String) {
659        let mut parser = tree_sitter::Parser::new();
660        parser
661            .set_language(&tree_sitter_go::LANGUAGE.into())
662            .unwrap();
663        let tree = parser.parse(source, None).unwrap();
664        (tree, source.to_string())
665    }
666
667    #[test]
668    fn test_extract_function() {
669        let source = r#"
670package main
671
672func Greet(name string) string {
673    return "Hello, " + name + "!"
674}
675"#;
676        let (tree, src) = parse_go(source);
677        let extractor = GoExtractor;
678        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
679
680        assert!(symbols
681            .iter()
682            .any(|s| s.name == "Greet" && s.kind == SymbolKind::Function));
683    }
684
685    #[test]
686    fn test_extract_struct() {
687        let source = r#"
688package main
689
690type User struct {
691    Name string
692    age  int
693}
694"#;
695        let (tree, src) = parse_go(source);
696        let extractor = GoExtractor;
697        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
698
699        assert!(symbols
700            .iter()
701            .any(|s| s.name == "User" && s.kind == SymbolKind::Struct));
702        assert!(symbols
703            .iter()
704            .any(|s| s.name == "Name" && s.kind == SymbolKind::Field && s.exported));
705        assert!(symbols
706            .iter()
707            .any(|s| s.name == "age" && s.kind == SymbolKind::Field && !s.exported));
708    }
709
710    #[test]
711    fn test_extract_method() {
712        let source = r#"
713package main
714
715func (u *User) Greet() string {
716    return "Hello, " + u.Name + "!"
717}
718"#;
719        let (tree, src) = parse_go(source);
720        let extractor = GoExtractor;
721        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
722
723        assert!(symbols
724            .iter()
725            .any(|s| s.name == "Greet" && s.kind == SymbolKind::Method));
726    }
727
728    #[test]
729    fn test_extract_interface() {
730        let source = r#"
731package main
732
733type Greeter interface {
734    Greet() string
735    Farewell() string
736}
737"#;
738        let (tree, src) = parse_go(source);
739        let extractor = GoExtractor;
740        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
741
742        assert!(symbols
743            .iter()
744            .any(|s| s.name == "Greeter" && s.kind == SymbolKind::Interface));
745    }
746}