codebank/parser/lang/
go.rs

1use super::GoParser;
2use crate::{
3    DeclareKind, DeclareStatements, Error, FieldUnit, FileUnit, FunctionUnit, ImplUnit,
4    LanguageParser, ModuleUnit, Result, StructUnit, TraitUnit, Visibility,
5};
6use std::fs;
7use std::ops::{Deref, DerefMut};
8use std::path::Path;
9use tree_sitter::{Node, Parser};
10
11impl LanguageParser for GoParser {
12    fn parse_file(&mut self, file_path: &Path) -> Result<FileUnit> {
13        // Read the file
14        let source_code = fs::read_to_string(file_path).map_err(Error::Io)?;
15
16        // Parse the file
17        let tree = self
18            .parse(source_code.as_bytes(), None)
19            .ok_or_else(|| Error::TreeSitter("Failed to parse source code".to_string()))?;
20        let root_node = tree.root_node();
21
22        // Create a new file unit
23        let mut file_unit = FileUnit::new(file_path.to_path_buf());
24        file_unit.source = Some(source_code.clone());
25
26        // Maps to collect methods by receiver type
27        let mut methods_by_type: std::collections::HashMap<String, Vec<FunctionUnit>> =
28            std::collections::HashMap::new();
29
30        // Process top-level declarations
31        let mut cursor = root_node.walk();
32        for child in root_node.children(&mut cursor) {
33            match child.kind() {
34                "package_clause" => {
35                    let package_doc = extract_documentation(child, &source_code);
36                    if let Some(package_name) =
37                        get_child_node_text(child, "package_identifier", &source_code)
38                    {
39                        let module = ModuleUnit {
40                            name: package_name,
41                            visibility: Visibility::Public, // Packages are public
42                            doc: package_doc,
43                            source: get_node_text(child, &source_code),
44                            attributes: Vec::new(),
45                            ..Default::default()
46                        };
47                        file_unit.modules.push(module);
48                    }
49                }
50                "import_declaration" => {
51                    // Handle single and block imports
52                    let mut import_cursor = child.walk();
53                    for import_spec in child.children(&mut import_cursor) {
54                        if import_spec.kind() == "import_spec"
55                            || import_spec.kind() == "interpreted_string_literal"
56                            || import_spec.kind() == "raw_string_literal"
57                        {
58                            if let Some(import_text) = get_node_text(import_spec, &source_code) {
59                                file_unit.declares.push(DeclareStatements {
60                                    source: import_text,
61                                    kind: DeclareKind::Use,
62                                });
63                            }
64                        } else if import_spec.kind() == "import_spec_list" {
65                            let mut list_cursor = import_spec.walk();
66                            for inner_spec in import_spec.children(&mut list_cursor) {
67                                if inner_spec.kind() == "import_spec" {
68                                    if let Some(import_text) =
69                                        get_node_text(inner_spec, &source_code)
70                                    {
71                                        file_unit.declares.push(DeclareStatements {
72                                            source: import_text,
73                                            kind: DeclareKind::Use,
74                                        });
75                                    }
76                                }
77                            }
78                        }
79                    }
80                }
81                "function_declaration" => {
82                    if let Ok(func) = self.parse_function(child, &source_code) {
83                        file_unit.functions.push(func);
84                    }
85                }
86                "method_declaration" => {
87                    if let Ok((receiver_type, method)) = self.parse_method(child, &source_code) {
88                        methods_by_type
89                            .entry(receiver_type)
90                            .or_default()
91                            .push(method);
92                    }
93                }
94                "type_declaration" => {
95                    let mut type_decl_cursor = child.walk();
96                    for type_spec_node in child.children(&mut type_decl_cursor) {
97                        if type_spec_node.kind() == "type_spec" {
98                            let mut type_spec_cursor = type_spec_node.walk();
99                            if let Some(type_def_node) = type_spec_node
100                                .children(&mut type_spec_cursor)
101                                .find(|n| n.kind() == "struct_type" || n.kind() == "interface_type")
102                            {
103                                if type_def_node.kind() == "struct_type" {
104                                    if let Ok(struct_item) =
105                                        self.parse_struct(type_spec_node, &source_code)
106                                    {
107                                        file_unit.structs.push(struct_item);
108                                    }
109                                } else if type_def_node.kind() == "interface_type" {
110                                    if let Ok(interface_item) =
111                                        self.parse_interface(type_spec_node, &source_code)
112                                    {
113                                        file_unit.traits.push(interface_item);
114                                    }
115                                }
116                            }
117                        }
118                    }
119                }
120                "const_declaration" | "var_declaration" => {
121                    let mut decl_cursor = child.walk();
122                    for spec_node in child.children(&mut decl_cursor) {
123                        if spec_node.kind() == "const_spec" || spec_node.kind() == "var_spec" {
124                            if let Some(declare_text) = get_node_text(spec_node, &source_code) {
125                                let kind_str = if child.kind() == "const_declaration" {
126                                    "const"
127                                } else {
128                                    "var"
129                                };
130                                file_unit.declares.push(DeclareStatements {
131                                    source: declare_text,
132                                    kind: DeclareKind::Other(kind_str.to_string()),
133                                });
134                            }
135                        } else if spec_node.kind() == "var_spec_list"
136                            || spec_node.kind() == "const_spec_list"
137                        {
138                            let mut list_cursor = spec_node.walk();
139                            for inner_spec_node in spec_node.children(&mut list_cursor) {
140                                if inner_spec_node.kind() == "const_spec"
141                                    || inner_spec_node.kind() == "var_spec"
142                                {
143                                    if let Some(declare_text) =
144                                        get_node_text(inner_spec_node, &source_code)
145                                    {
146                                        let kind_str = if child.kind() == "const_declaration" {
147                                            "const"
148                                        } else {
149                                            "var"
150                                        };
151                                        file_unit.declares.push(DeclareStatements {
152                                            source: declare_text,
153                                            kind: DeclareKind::Other(kind_str.to_string()),
154                                        });
155                                    }
156                                }
157                            }
158                        }
159                    }
160                }
161                "comment" => {
162                    // Ignore comments at top level for now
163                }
164                _ => {
165                    // Ignore other top-level node: {}
166                }
167            }
168        }
169
170        // Add methods to their respective structs
171        for struct_item in &mut file_unit.structs {
172            if let Some(methods) = methods_by_type.remove(&struct_item.name) {
173                struct_item.methods.extend(methods.clone()); // Add methods to struct
174
175                // Also create an ImplUnit for each struct with methods
176                let impl_unit = ImplUnit {
177                    doc: None, // Could try to find doc for the impl block if needed
178                    head: format!("methods for {}", struct_item.name),
179                    source: None, // Source for the whole impl block is tricky
180                    attributes: Vec::new(),
181                    methods, // Moves methods into the impl unit
182                };
183                file_unit.impls.push(impl_unit);
184            }
185        }
186
187        // For any methods whose receiver types weren't found as structs,
188        // still create impl units (e.g., methods on built-in types or type aliases)
189        for (receiver_type, methods) in methods_by_type {
190            let impl_unit = ImplUnit {
191                doc: None,
192                head: format!("methods for {}", receiver_type),
193                source: None,
194                attributes: Vec::new(),
195                methods,
196            };
197            file_unit.impls.push(impl_unit);
198        }
199
200        Ok(file_unit)
201    }
202}
203
204impl GoParser {
205    pub fn try_new() -> Result<Self> {
206        let mut parser = Parser::new();
207        let language = tree_sitter_go::LANGUAGE;
208        parser
209            .set_language(&language.into())
210            .map_err(|e| Error::TreeSitter(e.to_string()))?;
211        Ok(Self { parser })
212    }
213
214    // Helper function to determine visibility (in Go, uppercase first letter means exported/public)
215    fn determine_visibility(&self, name: &str) -> Visibility {
216        if !name.is_empty() && name.chars().next().unwrap().is_uppercase() {
217            Visibility::Public
218        } else {
219            Visibility::Private
220        }
221    }
222
223    // Parse function and extract its details
224    fn parse_function(&self, node: Node, source_code: &str) -> Result<FunctionUnit> {
225        let documentation = extract_documentation(node, source_code);
226        let name = get_child_node_text(node, "identifier", source_code)
227            .unwrap_or_else(|| "unknown".to_string());
228
229        let visibility = self.determine_visibility(&name);
230        let source = get_node_text(node, source_code);
231        let mut signature = None;
232        let mut body = None;
233
234        // Extract signature (everything before the body block)
235        if let Some(body_node) = node.child_by_field_name("body") {
236            let sig_end = body_node.start_byte();
237            let sig_start = node.start_byte();
238            if sig_end > sig_start {
239                signature = Some(source_code[sig_start..sig_end].trim().to_string());
240            }
241            body = get_node_text(body_node, source_code);
242        } else {
243            // Fallback for function declarations without body (e.g. in interfaces - though handled separately)
244            signature = source.clone();
245        }
246
247        Ok(FunctionUnit {
248            name,
249            visibility,
250            doc: documentation,
251            source,
252            signature,
253            body,
254            attributes: Vec::new(), // Go doesn't have attributes like Rust
255        })
256    }
257
258    // Parse struct and extract its details
259    // Node passed here should be the `type_spec` node
260    fn parse_struct(&self, type_spec_node: Node, source_code: &str) -> Result<StructUnit> {
261        // Documentation should be associated with the type_spec node or its parent type_declaration
262        let documentation =
263            extract_documentation(type_spec_node, source_code).or_else(|| -> Option<String> {
264                type_spec_node
265                    .parent()
266                    .and_then(|p| extract_documentation(p, source_code))
267            });
268        let name = get_child_node_text(type_spec_node, "type_identifier", source_code)
269            .unwrap_or_else(|| "unknown".to_string());
270        let visibility = self.determine_visibility(&name);
271        let source = get_node_text(
272            type_spec_node.parent().unwrap_or(type_spec_node),
273            source_code,
274        );
275        let head = format!("type {} struct", name);
276
277        let mut fields = Vec::new();
278
279        if let Some(struct_type) = type_spec_node
280            .children(&mut type_spec_node.walk())
281            .find(|child| child.kind() == "struct_type")
282        {
283            if let Some(field_list) = struct_type
284                .children(&mut struct_type.walk())
285                .find(|child| child.kind() == "field_declaration_list")
286            {
287                let mut list_cursor = field_list.walk();
288                for field_decl in field_list.children(&mut list_cursor) {
289                    if field_decl.kind() == "field_declaration" {
290                        let field_documentation = extract_documentation(field_decl, source_code);
291                        let field_source = get_node_text(field_decl, source_code);
292                        let mut field_names = Vec::new();
293                        let mut decl_cursor = field_decl.walk();
294                        for child in field_decl.children(&mut decl_cursor) {
295                            if child.kind() == "identifier" || child.kind() == "field_identifier" {
296                                if let Some(field_name) = get_node_text(child, source_code) {
297                                    field_names.push(field_name);
298                                }
299                            } else if child.kind().ends_with("_type")
300                                || child.kind() == "qualified_type"
301                            {
302                                // Stop collecting names when type is reached
303                                break;
304                            }
305                        }
306                        for field_name in field_names {
307                            fields.push(FieldUnit {
308                                name: field_name,
309                                doc: field_documentation.clone(),
310                                attributes: Vec::new(),
311                                source: field_source.clone(),
312                            });
313                        }
314                    }
315                }
316            }
317        }
318
319        Ok(StructUnit {
320            name,
321            head,
322            visibility,
323            doc: documentation,
324            source,
325            attributes: Vec::new(),
326            fields,
327            methods: Vec::new(),
328        })
329    }
330
331    // Parse interface (similar to trait in Rust)
332    // Node passed here should be the `type_spec` node
333    fn parse_interface(&self, type_spec_node: Node, source_code: &str) -> Result<TraitUnit> {
334        // Documentation should be associated with the type_spec node or its parent type_declaration
335        let documentation =
336            extract_documentation(type_spec_node, source_code).or_else(|| -> Option<String> {
337                type_spec_node
338                    .parent()
339                    .and_then(|p| extract_documentation(p, source_code))
340            });
341        let name = get_child_node_text(type_spec_node, "type_identifier", source_code)
342            .unwrap_or_else(|| "unknown".to_string());
343        let visibility = self.determine_visibility(&name);
344        let source = get_node_text(
345            type_spec_node.parent().unwrap_or(type_spec_node),
346            source_code,
347        );
348
349        let mut methods = Vec::new();
350
351        if let Some(interface_type) = type_spec_node
352            .children(&mut type_spec_node.walk())
353            .find(|child| child.kind() == "interface_type")
354        {
355            let mut interface_cursor = interface_type.walk();
356            for child in interface_type.children(&mut interface_cursor) {
357                if child.kind() == "method_elem" {
358                    let method_spec = child; // Keep variable name for consistency
359                    let method_doc = extract_documentation(method_spec, source_code);
360                    let method_source = get_node_text(method_spec, source_code);
361                    // Method name is typically the first identifier within method_spec
362                    let method_name = get_child_node_text(method_spec, "identifier", source_code)
363                        .or_else(|| {
364                            get_child_node_text(method_spec, "field_identifier", source_code)
365                        })
366                        .unwrap_or_else(|| "unknown_interface_method".to_string());
367                    let visibility = self.determine_visibility(&method_name); // Interface methods are implicitly public
368                    // Interface methods only have signatures, no bodies
369                    let signature = method_source.clone();
370
371                    methods.push(FunctionUnit {
372                        name: method_name,
373                        visibility, // Could force Public, but determine_visibility works
374                        doc: method_doc,
375                        source: method_source,
376                        signature,
377                        body: None, // Interface methods don't have bodies
378                        attributes: Vec::new(),
379                    });
380                }
381            }
382        }
383
384        Ok(TraitUnit {
385            name,
386            visibility,
387            doc: documentation,
388            source,
389            attributes: Vec::new(),
390            methods,
391        })
392    }
393
394    // Parse method (like impl in Rust)
395    // Node is `method_declaration`
396    fn parse_method(&self, node: Node, source_code: &str) -> Result<(String, FunctionUnit)> {
397        let documentation = extract_documentation(node, source_code);
398        let source = get_node_text(node, source_code);
399
400        // Get method name (field identifier)
401        let method_name = get_child_node_text(node, "field_identifier", source_code)
402            .unwrap_or_else(|| "unknown".to_string());
403
404        // Get receiver type (struct type)
405        let receiver_type = if let Some(parameter_list) = node.child_by_field_name("receiver") {
406            // The receiver is a parameter_list containing one parameter_declaration
407            if let Some(parameter) = parameter_list
408                .children(&mut parameter_list.walk())
409                .find(|child| child.kind() == "parameter_declaration")
410            {
411                // Extract type from parameter declaration
412                if let Some(type_node) = parameter.child_by_field_name("type") {
413                    get_node_text(type_node, source_code)
414                        .map(|s| s.trim_start_matches('*').to_string()) // Remove leading * for pointer receivers
415                        .unwrap_or_else(|| "unknown".to_string())
416                } else {
417                    "unknown".to_string()
418                }
419            } else {
420                "unknown".to_string()
421            }
422        } else {
423            "unknown".to_string()
424        };
425
426        let visibility = self.determine_visibility(&method_name);
427        let mut signature = None;
428        let mut body = None;
429
430        // Extract signature (everything before the body block)
431        if let Some(body_node) = node.child_by_field_name("body") {
432            let sig_end = body_node.start_byte();
433            let sig_start = node.start_byte();
434            if sig_end > sig_start {
435                signature = Some(source_code[sig_start..sig_end].trim().to_string());
436            }
437            body = get_node_text(body_node, source_code);
438        } else {
439            signature = source.clone();
440        }
441
442        let function = FunctionUnit {
443            name: method_name,
444            visibility,
445            doc: documentation,
446            source,
447            signature,
448            body,
449            attributes: Vec::new(),
450        };
451
452        Ok((receiver_type, function))
453    }
454}
455
456// Helper function to get the text of a node
457fn get_node_text(node: Node, source_code: &str) -> Option<String> {
458    node.utf8_text(source_code.as_bytes())
459        .ok()
460        .map(String::from)
461}
462
463// Helper function to get the text of the first child node of a specific kind
464fn get_child_node_text<'a>(node: Node<'a>, kind: &str, source_code: &'a str) -> Option<String> {
465    // First try to find it directly as a child using field name if common (e.g., 'name')
466    if kind == "identifier" || kind == "package_identifier" || kind == "field_identifier" {
467        if let Some(name_node) = node.child_by_field_name("name") {
468            // Check if the node kind matches the expected identifier type
469            if name_node.kind() == kind {
470                return name_node
471                    .utf8_text(source_code.as_bytes())
472                    .ok()
473                    .map(String::from);
474            }
475        }
476    }
477
478    // Then try finding by specific node kind
479    if let Some(child) = node
480        .children(&mut node.walk())
481        .find(|child| child.kind() == kind)
482    {
483        return child
484            .utf8_text(source_code.as_bytes())
485            .ok()
486            .map(String::from);
487    }
488
489    // Fallback: Look for any specific identifier kind child if specific kind not found
490    if kind == "identifier" || kind == "package_identifier" || kind == "field_identifier" {
491        if let Some(ident_child) = node
492            .children(&mut node.walk())
493            .find(|child| child.kind() == kind)
494        {
495            return ident_child
496                .utf8_text(source_code.as_bytes())
497                .ok()
498                .map(String::from);
499        }
500    }
501    // Generic identifier fallback
502    if let Some(ident_child) = node
503        .children(&mut node.walk())
504        .find(|child| child.kind() == "identifier")
505    {
506        return ident_child
507            .utf8_text(source_code.as_bytes())
508            .ok()
509            .map(String::from);
510    }
511
512    None
513}
514
515// Extract documentation from comments preceding a node
516fn extract_documentation(node: Node, source_code: &str) -> Option<String> {
517    // Attempt to find a preceding comment block associated with the node.
518    // Go documentation comments are typically immediately before the declaration.
519    let mut prev_sibling = node.prev_sibling();
520    while let Some(sibling) = prev_sibling {
521        if sibling.kind() == "comment" {
522            // Check if the comment is "close" enough (on the preceding line(s))
523            if node.start_position().row == sibling.end_position().row + 1
524                || node.start_position().row == sibling.start_position().row + 1
525            {
526                // Found a relevant comment block
527                let doc_text = get_node_text(sibling, source_code)?; // Use ? to propagate None
528                // Basic cleaning: remove comment markers and trim whitespace
529                let cleaned_doc = doc_text
530                    .trim_start_matches("//")
531                    .trim_start_matches("/*")
532                    .trim_end_matches("*/")
533                    .trim()
534                    .to_string();
535                // If multiple comment lines form a block, they should be concatenated.
536                // Tree-sitter often gives the whole block as one node.
537                // If not, more complex logic might be needed to combine multi-line comments.
538                return Some(cleaned_doc);
539            } else {
540                // Comment is not immediately preceding, stop searching backwards
541                break;
542            }
543        } else if !sibling.is_extra() {
544            // Reached a non-comment, non-whitespace node, stop searching
545            break;
546        }
547        prev_sibling = sibling.prev_sibling();
548    }
549
550    None // No documentation comment found immediately preceding the node
551}
552
553impl Deref for GoParser {
554    type Target = Parser;
555
556    fn deref(&self) -> &Self::Target {
557        &self.parser
558    }
559}
560
561impl DerefMut for GoParser {
562    fn deref_mut(&mut self) -> &mut Self::Target {
563        &mut self.parser
564    }
565}
566
567#[cfg(test)]
568mod tests {
569    use super::*;
570    use std::path::PathBuf;
571
572    fn parse_fixture(file_name: &str) -> Result<FileUnit> {
573        let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
574            .expect("CARGO_MANIFEST_DIR should be set during tests");
575        let path = PathBuf::from(manifest_dir).join("fixtures").join(file_name);
576        let mut parser = GoParser::try_new()?;
577        parser.parse_file(&path)
578    }
579
580    #[test]
581    fn test_parse_go_package() {
582        let file_unit = parse_fixture("sample.go").expect("Failed to parse Go file");
583        assert_eq!(
584            file_unit.modules.len(),
585            1,
586            "Should parse one package module"
587        );
588        assert_eq!(file_unit.modules[0].name, "example");
589        assert!(
590            file_unit.modules[0].doc.is_some(),
591            "Package doc comment missing"
592        );
593        assert!(
594            file_unit.modules[0]
595                .doc
596                .as_ref()
597                .unwrap()
598                .contains("sample Go file")
599        );
600    }
601
602    #[test]
603    fn test_parse_go_imports() {
604        let file_unit = parse_fixture("sample.go").expect("Failed to parse Go file");
605        // Count only imports
606        let import_count = file_unit
607            .declares
608            .iter()
609            .filter(|d| d.kind == DeclareKind::Use)
610            .count();
611        assert_eq!(
612            import_count, 7,
613            "Expected exactly 7 imports, found {}",
614            import_count
615        ); // Check exact count
616        // Check specific imports
617        assert!(
618            file_unit
619                .declares
620                .iter()
621                .any(|d| d.kind == DeclareKind::Use && d.source.contains("\"fmt\""))
622        );
623        assert!(
624            file_unit
625                .declares
626                .iter()
627                .any(|d| d.kind == DeclareKind::Use && d.source.contains("\"strings\""))
628        );
629        assert!(
630            file_unit
631                .declares
632                .iter()
633                .any(|d| d.kind == DeclareKind::Use && d.source.contains("\"os\""))
634        );
635        // Check const/var declarations
636        let const_count = file_unit
637            .declares
638            .iter()
639            .filter(|d| matches!(&d.kind, DeclareKind::Other(s) if s == "const"))
640            .count();
641        assert!(
642            const_count >= 3,
643            "Expected at least 3 const declarations, found {}",
644            const_count
645        );
646        let var_count = file_unit
647            .declares
648            .iter()
649            .filter(|d| matches!(&d.kind, DeclareKind::Other(s) if s == "var"))
650            .count();
651        assert!(
652            var_count >= 1,
653            "Expected at least 1 var declaration, found {}",
654            var_count
655        );
656    }
657
658    #[test]
659    fn test_parse_go_functions() {
660        let file_unit = parse_fixture("sample.go").expect("Failed to parse Go file");
661        // Check top-level functions
662        let new_person_func = file_unit.functions.iter().find(|f| f.name == "NewPerson");
663        assert!(new_person_func.is_some(), "NewPerson function not found");
664        let new_person_func = new_person_func.unwrap();
665        assert_eq!(new_person_func.visibility, Visibility::Public);
666        assert!(new_person_func.doc.is_some(), "NewPerson doc missing");
667        assert!(
668            new_person_func
669                .doc
670                .as_ref()
671                .unwrap()
672                .contains("creates a new Person instance")
673        );
674        assert!(new_person_func.signature.is_some());
675        assert!(new_person_func.body.is_some());
676
677        let upper_case_func = file_unit.functions.iter().find(|f| f.name == "UpperCase");
678        assert!(upper_case_func.is_some(), "UpperCase function not found");
679        let upper_case_func = upper_case_func.unwrap();
680        assert_eq!(upper_case_func.visibility, Visibility::Public);
681        assert!(upper_case_func.doc.is_some(), "UpperCase doc missing");
682        assert!(
683            upper_case_func
684                .doc
685                .as_ref()
686                .unwrap()
687                .contains("converts a string to uppercase")
688        );
689        assert!(upper_case_func.signature.is_some());
690        assert!(upper_case_func.body.is_some());
691    }
692
693    #[test]
694    fn test_parse_go_structs() {
695        let file_unit = parse_fixture("sample.go").expect("Failed to parse Go file");
696
697        let person_struct = file_unit.structs.iter().find(|s| s.name == "Person");
698        assert!(person_struct.is_some(), "Person struct not found");
699        let person_struct = person_struct.unwrap();
700        assert_eq!(person_struct.visibility, Visibility::Public);
701        assert!(person_struct.doc.is_some(), "Person doc missing");
702        assert!(
703            person_struct
704                .doc
705                .as_ref()
706                .unwrap()
707                .contains("represents a person")
708        );
709        assert_eq!(person_struct.fields.len(), 3, "Person should have 3 fields");
710        // Check field names
711        assert!(person_struct.fields.iter().any(|f| f.name == "Name"));
712        assert!(person_struct.fields.iter().any(|f| f.name == "Age"));
713        assert!(person_struct.fields.iter().any(|f| f.name == "address"));
714        // Check field documentation
715        let name_field = person_struct
716            .fields
717            .iter()
718            .find(|f| f.name == "Name")
719            .unwrap();
720        assert!(name_field.doc.is_some(), "Name field doc missing");
721        assert!(name_field.doc.as_ref().unwrap().contains("person's name"));
722
723        let age_field = person_struct
724            .fields
725            .iter()
726            .find(|f| f.name == "Age")
727            .unwrap();
728        assert!(age_field.doc.is_some(), "Age field doc missing");
729        assert!(age_field.doc.as_ref().unwrap().contains("person's age"));
730
731        let address_field = person_struct
732            .fields
733            .iter()
734            .find(|f| f.name == "address")
735            .unwrap();
736        assert!(address_field.doc.is_some(), "address field doc missing");
737        assert!(
738            address_field
739                .doc
740                .as_ref()
741                .unwrap()
742                .contains("unexported field")
743        );
744
745        let greeter_impl_struct = file_unit.structs.iter().find(|s| s.name == "GreeterImpl");
746        assert!(
747            greeter_impl_struct.is_some(),
748            "GreeterImpl struct not found"
749        );
750        let greeter_impl_struct = greeter_impl_struct.unwrap();
751        assert_eq!(greeter_impl_struct.visibility, Visibility::Public);
752        assert!(greeter_impl_struct.doc.is_some(), "GreeterImpl doc missing");
753        assert!(
754            greeter_impl_struct
755                .doc
756                .as_ref()
757                .unwrap()
758                .contains("implements the Greeter interface")
759        );
760        assert_eq!(
761            greeter_impl_struct.fields.len(),
762            1,
763            "GreeterImpl should have 1 field"
764        );
765        assert_eq!(greeter_impl_struct.fields[0].name, "greeting");
766
767        // Check associated methods (parsed into impls)
768        let greeter_impl_methods = file_unit
769            .impls
770            .iter()
771            .find(|imp| imp.head == "methods for GreeterImpl");
772        assert!(
773            greeter_impl_methods.is_some(),
774            "Impl block for GreeterImpl not found"
775        );
776        assert_eq!(
777            greeter_impl_methods.unwrap().methods.len(),
778            1,
779            "GreeterImpl should have 1 method"
780        );
781        assert_eq!(greeter_impl_methods.unwrap().methods[0].name, "Greet");
782    }
783
784    #[test]
785    fn test_parse_go_interfaces() {
786        let file_unit = parse_fixture("sample.go").expect("Failed to parse Go file");
787
788        let greeter_interface = file_unit.traits.iter().find(|t| t.name == "Greeter");
789        assert!(greeter_interface.is_some(), "Greeter interface not found");
790        let greeter_interface = greeter_interface.unwrap();
791        assert_eq!(greeter_interface.visibility, Visibility::Public);
792        assert!(greeter_interface.doc.is_some(), "Greeter doc missing");
793        assert!(
794            greeter_interface
795                .doc
796                .as_ref()
797                .unwrap()
798                .contains("defines an interface")
799        );
800        assert_eq!(
801            greeter_interface.methods.len(),
802            1,
803            "Greeter interface should have 1 method"
804        );
805        assert_eq!(greeter_interface.methods[0].name, "Greet");
806        assert!(
807            greeter_interface.methods[0].doc.is_some(),
808            "Greet method doc missing"
809        );
810        assert!(
811            greeter_interface.methods[0]
812                .doc
813                .as_ref()
814                .unwrap()
815                .contains("returns a greeting message")
816        );
817        assert!(greeter_interface.methods[0].signature.is_some());
818        assert!(greeter_interface.methods[0].body.is_none());
819    }
820
821    #[test]
822    fn test_parse_go_methods() {
823        let file_unit = parse_fixture("sample.go").expect("Failed to parse Go file");
824
825        // Find the ImplUnit for Person methods
826        let person_impl = file_unit
827            .impls
828            .iter()
829            .find(|imp| imp.head == "methods for Person");
830        assert!(person_impl.is_some(), "Impl block for Person not found");
831        let person_impl = person_impl.unwrap();
832
833        // Check method count
834        assert_eq!(person_impl.methods.len(), 3, "Person should have 3 methods");
835
836        // Check SetAddress method
837        let set_address = person_impl.methods.iter().find(|m| m.name == "SetAddress");
838        assert!(set_address.is_some(), "SetAddress method not found");
839        let set_address = set_address.unwrap();
840        assert_eq!(set_address.visibility, Visibility::Public);
841        assert!(set_address.doc.is_some(), "SetAddress doc missing");
842        assert!(
843            set_address
844                .doc
845                .as_ref()
846                .unwrap()
847                .contains("sets the person's address")
848        );
849        assert!(set_address.signature.is_some());
850        assert!(set_address.body.is_some());
851
852        // Check GetAddress method
853        let get_address = person_impl.methods.iter().find(|m| m.name == "GetAddress");
854        assert!(get_address.is_some(), "GetAddress method not found");
855        let get_address = get_address.unwrap();
856        assert_eq!(get_address.visibility, Visibility::Public);
857        assert!(get_address.doc.is_some(), "GetAddress doc missing");
858        assert!(
859            get_address
860                .doc
861                .as_ref()
862                .unwrap()
863                .contains("returns the person's address")
864        );
865        assert!(get_address.signature.is_some());
866        assert!(get_address.body.is_some());
867
868        // Check String method
869        let string_method = person_impl.methods.iter().find(|m| m.name == "String");
870        assert!(string_method.is_some(), "String method not found");
871        let string_method = string_method.unwrap();
872        assert_eq!(string_method.visibility, Visibility::Public);
873        assert!(string_method.doc.is_some(), "String method doc missing");
874        assert!(
875            string_method
876                .doc
877                .as_ref()
878                .unwrap()
879                .contains("implements the Stringer interface")
880        );
881        assert!(string_method.signature.is_some());
882        assert!(string_method.body.is_some());
883    }
884}