acp/ast/languages/
rust.rs

1//! @acp:module "Rust Extractor"
2//! @acp:summary "Symbol extraction for Rust source files"
3//! @acp:domain cli
4//! @acp:layer parsing
5
6use super::{node_text, LanguageExtractor};
7use crate::ast::{
8    ExtractedSymbol, FunctionCall, Import, ImportedName, Parameter, SymbolKind, Visibility,
9};
10use crate::error::Result;
11use tree_sitter::{Language, Node, Tree};
12
13/// Rust language extractor
14pub struct RustExtractor;
15
16impl LanguageExtractor for RustExtractor {
17    fn language(&self) -> Language {
18        tree_sitter_rust::LANGUAGE.into()
19    }
20
21    fn name(&self) -> &'static str {
22        "rust"
23    }
24
25    fn extensions(&self) -> &'static [&'static str] {
26        &["rs"]
27    }
28
29    fn extract_symbols(&self, tree: &Tree, source: &str) -> Result<Vec<ExtractedSymbol>> {
30        let mut symbols = Vec::new();
31        let root = tree.root_node();
32        self.extract_symbols_recursive(&root, source, &mut symbols, None);
33        Ok(symbols)
34    }
35
36    fn extract_imports(&self, tree: &Tree, source: &str) -> Result<Vec<Import>> {
37        let mut imports = Vec::new();
38        let root = tree.root_node();
39        self.extract_imports_recursive(&root, source, &mut imports);
40        Ok(imports)
41    }
42
43    fn extract_calls(
44        &self,
45        tree: &Tree,
46        source: &str,
47        current_function: Option<&str>,
48    ) -> Result<Vec<FunctionCall>> {
49        let mut calls = Vec::new();
50        let root = tree.root_node();
51        self.extract_calls_recursive(&root, source, &mut calls, current_function);
52        Ok(calls)
53    }
54
55    fn extract_doc_comment(&self, node: &Node, source: &str) -> Option<String> {
56        // Look for doc comments (///, //!, /** */)
57        let mut doc_lines = Vec::new();
58        let mut current = node.prev_sibling();
59
60        while let Some(prev) = current {
61            if prev.kind() == "line_comment" {
62                let comment = node_text(&prev, source);
63                if comment.starts_with("///") || comment.starts_with("//!") {
64                    doc_lines.push(comment[3..].trim().to_string());
65                    current = prev.prev_sibling();
66                    continue;
67                }
68            } else if prev.kind() == "block_comment" {
69                let comment = node_text(&prev, source);
70                if comment.starts_with("/**") || comment.starts_with("/*!") {
71                    return Some(Self::clean_block_doc(comment));
72                }
73            }
74            break;
75        }
76
77        if doc_lines.is_empty() {
78            None
79        } else {
80            doc_lines.reverse();
81            Some(doc_lines.join("\n"))
82        }
83    }
84}
85
86impl RustExtractor {
87    fn extract_symbols_recursive(
88        &self,
89        node: &Node,
90        source: &str,
91        symbols: &mut Vec<ExtractedSymbol>,
92        parent: Option<&str>,
93    ) {
94        match node.kind() {
95            "function_item" => {
96                if let Some(sym) = self.extract_function(node, source, parent) {
97                    symbols.push(sym);
98                }
99            }
100
101            "struct_item" => {
102                if let Some(sym) = self.extract_struct(node, source, parent) {
103                    let struct_name = sym.name.clone();
104                    symbols.push(sym);
105
106                    // Extract struct fields
107                    if let Some(body) = node.child_by_field_name("body") {
108                        self.extract_struct_fields(&body, source, symbols, Some(&struct_name));
109                    }
110                }
111            }
112
113            "enum_item" => {
114                if let Some(sym) = self.extract_enum(node, source, parent) {
115                    let enum_name = sym.name.clone();
116                    symbols.push(sym);
117
118                    // Extract enum variants
119                    if let Some(body) = node.child_by_field_name("body") {
120                        self.extract_enum_variants(&body, source, symbols, Some(&enum_name));
121                    }
122                }
123            }
124
125            "trait_item" => {
126                if let Some(sym) = self.extract_trait(node, source, parent) {
127                    let trait_name = sym.name.clone();
128                    symbols.push(sym);
129
130                    // Extract trait methods
131                    if let Some(body) = node.child_by_field_name("body") {
132                        self.extract_trait_body(&body, source, symbols, Some(&trait_name));
133                    }
134                }
135            }
136
137            "impl_item" => {
138                self.extract_impl_block(node, source, symbols);
139            }
140
141            "type_item" => {
142                if let Some(sym) = self.extract_type_alias(node, source, parent) {
143                    symbols.push(sym);
144                }
145            }
146
147            "const_item" => {
148                if let Some(sym) = self.extract_const(node, source, parent) {
149                    symbols.push(sym);
150                }
151            }
152
153            "static_item" => {
154                if let Some(sym) = self.extract_static(node, source, parent) {
155                    symbols.push(sym);
156                }
157            }
158
159            "mod_item" => {
160                if let Some(sym) = self.extract_module(node, source, parent) {
161                    let mod_name = sym.name.clone();
162                    symbols.push(sym);
163
164                    // Extract module contents
165                    if let Some(body) = node.child_by_field_name("body") {
166                        self.extract_symbols_recursive(&body, source, symbols, Some(&mod_name));
167                    }
168                }
169            }
170
171            _ => {}
172        }
173
174        // Recurse into children
175        let mut cursor = node.walk();
176        for child in node.children(&mut cursor) {
177            self.extract_symbols_recursive(&child, source, symbols, parent);
178        }
179    }
180
181    fn extract_function(
182        &self,
183        node: &Node,
184        source: &str,
185        parent: Option<&str>,
186    ) -> Option<ExtractedSymbol> {
187        let name_node = node.child_by_field_name("name")?;
188        let name = node_text(&name_node, source).to_string();
189
190        let mut sym = ExtractedSymbol::new(
191            name,
192            SymbolKind::Function,
193            node.start_position().row + 1,
194            node.end_position().row + 1,
195        )
196        .with_columns(node.start_position().column, node.end_position().column);
197
198        // Check visibility
199        sym.visibility = self.extract_visibility(node, source);
200        if matches!(sym.visibility, Visibility::Public) {
201            sym = sym.exported();
202        }
203
204        // Check for async
205        let text = node_text(node, source);
206        if text.contains("async fn") {
207            sym = sym.async_fn();
208        }
209
210        // Extract generics
211        if let Some(type_params) = node.child_by_field_name("type_parameters") {
212            self.extract_generics(&type_params, source, &mut sym);
213        }
214
215        // Extract parameters
216        if let Some(params) = node.child_by_field_name("parameters") {
217            self.extract_parameters(&params, source, &mut sym);
218        }
219
220        // Extract return type
221        if let Some(ret_type) = node.child_by_field_name("return_type") {
222            sym.return_type = Some(
223                node_text(&ret_type, source)
224                    .trim_start_matches("->")
225                    .trim()
226                    .to_string(),
227            );
228        }
229
230        sym.doc_comment = self.extract_doc_comment(node, source);
231
232        if let Some(p) = parent {
233            sym = sym.with_parent(p);
234        }
235
236        sym.signature = Some(self.build_function_signature(node, source));
237
238        Some(sym)
239    }
240
241    fn extract_struct(
242        &self,
243        node: &Node,
244        source: &str,
245        parent: Option<&str>,
246    ) -> Option<ExtractedSymbol> {
247        let name_node = node.child_by_field_name("name")?;
248        let name = node_text(&name_node, source).to_string();
249
250        let mut sym = ExtractedSymbol::new(
251            name,
252            SymbolKind::Struct,
253            node.start_position().row + 1,
254            node.end_position().row + 1,
255        );
256
257        sym.visibility = self.extract_visibility(node, source);
258        if matches!(sym.visibility, Visibility::Public) {
259            sym = sym.exported();
260        }
261
262        if let Some(type_params) = node.child_by_field_name("type_parameters") {
263            self.extract_generics(&type_params, source, &mut sym);
264        }
265
266        sym.doc_comment = self.extract_doc_comment(node, source);
267
268        if let Some(p) = parent {
269            sym = sym.with_parent(p);
270        }
271
272        Some(sym)
273    }
274
275    fn extract_struct_fields(
276        &self,
277        body: &Node,
278        source: &str,
279        symbols: &mut Vec<ExtractedSymbol>,
280        struct_name: Option<&str>,
281    ) {
282        let mut cursor = body.walk();
283        for child in body.children(&mut cursor) {
284            if child.kind() == "field_declaration" {
285                if let Some(name_node) = child.child_by_field_name("name") {
286                    let name = node_text(&name_node, source).to_string();
287
288                    let mut sym = ExtractedSymbol::new(
289                        name,
290                        SymbolKind::Field,
291                        child.start_position().row + 1,
292                        child.end_position().row + 1,
293                    );
294
295                    sym.visibility = self.extract_visibility(&child, source);
296
297                    if let Some(type_node) = child.child_by_field_name("type") {
298                        sym.type_info = Some(node_text(&type_node, source).to_string());
299                    }
300
301                    if let Some(p) = struct_name {
302                        sym = sym.with_parent(p);
303                    }
304
305                    symbols.push(sym);
306                }
307            }
308        }
309    }
310
311    fn extract_enum(
312        &self,
313        node: &Node,
314        source: &str,
315        parent: Option<&str>,
316    ) -> Option<ExtractedSymbol> {
317        let name_node = node.child_by_field_name("name")?;
318        let name = node_text(&name_node, source).to_string();
319
320        let mut sym = ExtractedSymbol::new(
321            name,
322            SymbolKind::Enum,
323            node.start_position().row + 1,
324            node.end_position().row + 1,
325        );
326
327        sym.visibility = self.extract_visibility(node, source);
328        if matches!(sym.visibility, Visibility::Public) {
329            sym = sym.exported();
330        }
331
332        if let Some(type_params) = node.child_by_field_name("type_parameters") {
333            self.extract_generics(&type_params, source, &mut sym);
334        }
335
336        sym.doc_comment = self.extract_doc_comment(node, source);
337
338        if let Some(p) = parent {
339            sym = sym.with_parent(p);
340        }
341
342        Some(sym)
343    }
344
345    fn extract_enum_variants(
346        &self,
347        body: &Node,
348        source: &str,
349        symbols: &mut Vec<ExtractedSymbol>,
350        enum_name: Option<&str>,
351    ) {
352        let mut cursor = body.walk();
353        for child in body.children(&mut cursor) {
354            if child.kind() == "enum_variant" {
355                if let Some(name_node) = child.child_by_field_name("name") {
356                    let name = node_text(&name_node, source).to_string();
357
358                    let mut sym = ExtractedSymbol::new(
359                        name,
360                        SymbolKind::EnumVariant,
361                        child.start_position().row + 1,
362                        child.end_position().row + 1,
363                    );
364
365                    if let Some(p) = enum_name {
366                        sym = sym.with_parent(p);
367                    }
368
369                    symbols.push(sym);
370                }
371            }
372        }
373    }
374
375    fn extract_trait(
376        &self,
377        node: &Node,
378        source: &str,
379        parent: Option<&str>,
380    ) -> Option<ExtractedSymbol> {
381        let name_node = node.child_by_field_name("name")?;
382        let name = node_text(&name_node, source).to_string();
383
384        let mut sym = ExtractedSymbol::new(
385            name,
386            SymbolKind::Trait,
387            node.start_position().row + 1,
388            node.end_position().row + 1,
389        );
390
391        sym.visibility = self.extract_visibility(node, source);
392        if matches!(sym.visibility, Visibility::Public) {
393            sym = sym.exported();
394        }
395
396        if let Some(type_params) = node.child_by_field_name("type_parameters") {
397            self.extract_generics(&type_params, source, &mut sym);
398        }
399
400        sym.doc_comment = self.extract_doc_comment(node, source);
401
402        if let Some(p) = parent {
403            sym = sym.with_parent(p);
404        }
405
406        Some(sym)
407    }
408
409    fn extract_trait_body(
410        &self,
411        body: &Node,
412        source: &str,
413        symbols: &mut Vec<ExtractedSymbol>,
414        trait_name: Option<&str>,
415    ) {
416        let mut cursor = body.walk();
417        for child in body.children(&mut cursor) {
418            if child.kind() == "function_signature_item" || child.kind() == "function_item" {
419                if let Some(name_node) = child.child_by_field_name("name") {
420                    let name = node_text(&name_node, source).to_string();
421
422                    let mut sym = ExtractedSymbol::new(
423                        name,
424                        SymbolKind::Method,
425                        child.start_position().row + 1,
426                        child.end_position().row + 1,
427                    );
428
429                    if let Some(params) = child.child_by_field_name("parameters") {
430                        self.extract_parameters(&params, source, &mut sym);
431                    }
432
433                    if let Some(ret_type) = child.child_by_field_name("return_type") {
434                        sym.return_type = Some(
435                            node_text(&ret_type, source)
436                                .trim_start_matches("->")
437                                .trim()
438                                .to_string(),
439                        );
440                    }
441
442                    sym.doc_comment = self.extract_doc_comment(&child, source);
443
444                    if let Some(p) = trait_name {
445                        sym = sym.with_parent(p);
446                    }
447
448                    symbols.push(sym);
449                }
450            }
451        }
452    }
453
454    fn extract_impl_block(&self, node: &Node, source: &str, symbols: &mut Vec<ExtractedSymbol>) {
455        // Get the type being implemented
456        let type_name = node
457            .child_by_field_name("type")
458            .map(|n| node_text(&n, source).to_string())
459            .unwrap_or_default();
460
461        // Check if this is a trait impl
462        let trait_name = node
463            .child_by_field_name("trait")
464            .map(|n| node_text(&n, source).to_string());
465
466        let impl_name = if let Some(ref trait_n) = trait_name {
467            format!("{} for {}", trait_n, type_name)
468        } else {
469            type_name.clone()
470        };
471
472        // Create impl block symbol
473        let mut impl_sym = ExtractedSymbol::new(
474            impl_name.clone(),
475            SymbolKind::Impl,
476            node.start_position().row + 1,
477            node.end_position().row + 1,
478        );
479
480        if let Some(type_params) = node.child_by_field_name("type_parameters") {
481            self.extract_generics(&type_params, source, &mut impl_sym);
482        }
483
484        symbols.push(impl_sym);
485
486        // Extract methods in the impl block
487        if let Some(body) = node.child_by_field_name("body") {
488            let mut cursor = body.walk();
489            for child in body.children(&mut cursor) {
490                if child.kind() == "function_item" {
491                    if let Some(mut sym) = self.extract_function(&child, source, Some(&type_name)) {
492                        // Check if it's a method (has self parameter)
493                        if sym.parameters.iter().any(|p| p.name.contains("self")) {
494                            sym.kind = SymbolKind::Method;
495                        }
496                        symbols.push(sym);
497                    }
498                }
499            }
500        }
501    }
502
503    fn extract_type_alias(
504        &self,
505        node: &Node,
506        source: &str,
507        parent: Option<&str>,
508    ) -> Option<ExtractedSymbol> {
509        let name_node = node.child_by_field_name("name")?;
510        let name = node_text(&name_node, source).to_string();
511
512        let mut sym = ExtractedSymbol::new(
513            name,
514            SymbolKind::TypeAlias,
515            node.start_position().row + 1,
516            node.end_position().row + 1,
517        );
518
519        sym.visibility = self.extract_visibility(node, source);
520        if matches!(sym.visibility, Visibility::Public) {
521            sym = sym.exported();
522        }
523
524        if let Some(type_node) = node.child_by_field_name("type") {
525            sym.type_info = Some(node_text(&type_node, source).to_string());
526        }
527
528        sym.doc_comment = self.extract_doc_comment(node, source);
529
530        if let Some(p) = parent {
531            sym = sym.with_parent(p);
532        }
533
534        Some(sym)
535    }
536
537    fn extract_const(
538        &self,
539        node: &Node,
540        source: &str,
541        parent: Option<&str>,
542    ) -> Option<ExtractedSymbol> {
543        let name_node = node.child_by_field_name("name")?;
544        let name = node_text(&name_node, source).to_string();
545
546        let mut sym = ExtractedSymbol::new(
547            name,
548            SymbolKind::Constant,
549            node.start_position().row + 1,
550            node.end_position().row + 1,
551        );
552
553        sym.visibility = self.extract_visibility(node, source);
554        if matches!(sym.visibility, Visibility::Public) {
555            sym = sym.exported();
556        }
557
558        if let Some(type_node) = node.child_by_field_name("type") {
559            sym.type_info = Some(node_text(&type_node, source).to_string());
560        }
561
562        sym.doc_comment = self.extract_doc_comment(node, source);
563
564        if let Some(p) = parent {
565            sym = sym.with_parent(p);
566        }
567
568        Some(sym)
569    }
570
571    fn extract_static(
572        &self,
573        node: &Node,
574        source: &str,
575        parent: Option<&str>,
576    ) -> Option<ExtractedSymbol> {
577        let name_node = node.child_by_field_name("name")?;
578        let name = node_text(&name_node, source).to_string();
579
580        let mut sym = ExtractedSymbol::new(
581            name,
582            SymbolKind::Variable,
583            node.start_position().row + 1,
584            node.end_position().row + 1,
585        );
586
587        sym.visibility = self.extract_visibility(node, source);
588        if matches!(sym.visibility, Visibility::Public) {
589            sym = sym.exported();
590        }
591
592        sym.is_static = true;
593
594        if let Some(type_node) = node.child_by_field_name("type") {
595            sym.type_info = Some(node_text(&type_node, source).to_string());
596        }
597
598        sym.doc_comment = self.extract_doc_comment(node, source);
599
600        if let Some(p) = parent {
601            sym = sym.with_parent(p);
602        }
603
604        Some(sym)
605    }
606
607    fn extract_module(
608        &self,
609        node: &Node,
610        source: &str,
611        parent: Option<&str>,
612    ) -> Option<ExtractedSymbol> {
613        let name_node = node.child_by_field_name("name")?;
614        let name = node_text(&name_node, source).to_string();
615
616        let mut sym = ExtractedSymbol::new(
617            name,
618            SymbolKind::Module,
619            node.start_position().row + 1,
620            node.end_position().row + 1,
621        );
622
623        sym.visibility = self.extract_visibility(node, source);
624        if matches!(sym.visibility, Visibility::Public) {
625            sym = sym.exported();
626        }
627
628        sym.doc_comment = self.extract_doc_comment(node, source);
629
630        if let Some(p) = parent {
631            sym = sym.with_parent(p);
632        }
633
634        Some(sym)
635    }
636
637    fn extract_visibility(&self, node: &Node, source: &str) -> Visibility {
638        let mut cursor = node.walk();
639        for child in node.children(&mut cursor) {
640            if child.kind() == "visibility_modifier" {
641                let text = node_text(&child, source);
642                if text == "pub" {
643                    return Visibility::Public;
644                } else if text.contains("pub(crate)") {
645                    return Visibility::Crate;
646                } else if text.contains("pub(super)") || text.contains("pub(self)") {
647                    return Visibility::Internal;
648                }
649            }
650        }
651        Visibility::Private
652    }
653
654    fn extract_parameters(&self, params: &Node, source: &str, sym: &mut ExtractedSymbol) {
655        let mut cursor = params.walk();
656        for child in params.children(&mut cursor) {
657            match child.kind() {
658                "parameter" => {
659                    let pattern = child
660                        .child_by_field_name("pattern")
661                        .map(|n| node_text(&n, source).to_string())
662                        .unwrap_or_default();
663
664                    let type_info = child
665                        .child_by_field_name("type")
666                        .map(|n| node_text(&n, source).to_string());
667
668                    sym.add_parameter(Parameter {
669                        name: pattern,
670                        type_info,
671                        default_value: None,
672                        is_rest: false,
673                        is_optional: false,
674                    });
675                }
676                "self_parameter" => {
677                    sym.add_parameter(Parameter {
678                        name: node_text(&child, source).to_string(),
679                        type_info: Some("Self".to_string()),
680                        default_value: None,
681                        is_rest: false,
682                        is_optional: false,
683                    });
684                }
685                _ => {}
686            }
687        }
688    }
689
690    fn extract_generics(&self, type_params: &Node, source: &str, sym: &mut ExtractedSymbol) {
691        let mut cursor = type_params.walk();
692        for child in type_params.children(&mut cursor) {
693            match child.kind() {
694                "type_identifier" | "lifetime" => {
695                    sym.add_generic(node_text(&child, source));
696                }
697                "constrained_type_parameter" => {
698                    if let Some(name) = child.child_by_field_name("left") {
699                        sym.add_generic(node_text(&name, source));
700                    }
701                }
702                _ => {}
703            }
704        }
705    }
706
707    fn extract_imports_recursive(&self, node: &Node, source: &str, imports: &mut Vec<Import>) {
708        if node.kind() == "use_declaration" {
709            if let Some(import) = self.parse_use(node, source) {
710                imports.push(import);
711            }
712        }
713
714        let mut cursor = node.walk();
715        for child in node.children(&mut cursor) {
716            self.extract_imports_recursive(&child, source, imports);
717        }
718    }
719
720    fn parse_use(&self, node: &Node, source: &str) -> Option<Import> {
721        let argument = node.child_by_field_name("argument")?;
722
723        let mut import = Import {
724            source: String::new(),
725            names: Vec::new(),
726            is_default: false,
727            is_namespace: false,
728            line: node.start_position().row + 1,
729        };
730
731        self.parse_use_path(&argument, source, &mut import, String::new());
732
733        Some(import)
734    }
735
736    fn parse_use_path(&self, node: &Node, source: &str, import: &mut Import, prefix: String) {
737        match node.kind() {
738            "scoped_identifier" => {
739                let path = node
740                    .child_by_field_name("path")
741                    .map(|n| node_text(&n, source).to_string())
742                    .unwrap_or_default();
743                let name = node
744                    .child_by_field_name("name")
745                    .map(|n| node_text(&n, source).to_string())
746                    .unwrap_or_default();
747
748                let full_path = if prefix.is_empty() {
749                    path
750                } else {
751                    format!("{}::{}", prefix, path)
752                };
753
754                import.source = full_path;
755                import.names.push(ImportedName { name, alias: None });
756            }
757            "use_as_clause" => {
758                let path = node
759                    .child_by_field_name("path")
760                    .map(|n| node_text(&n, source).to_string())
761                    .unwrap_or_default();
762                let alias = node
763                    .child_by_field_name("alias")
764                    .map(|n| node_text(&n, source).to_string());
765
766                import.source = prefix;
767                import.names.push(ImportedName { name: path, alias });
768            }
769            "use_wildcard" => {
770                import.source = prefix;
771                import.is_namespace = true;
772                import.names.push(ImportedName {
773                    name: "*".to_string(),
774                    alias: None,
775                });
776            }
777            "use_list" => {
778                let mut cursor = node.walk();
779                for child in node.children(&mut cursor) {
780                    self.parse_use_path(&child, source, import, prefix.clone());
781                }
782            }
783            "identifier" => {
784                let name = node_text(node, source).to_string();
785                if import.source.is_empty() {
786                    import.source = prefix;
787                }
788                import.names.push(ImportedName { name, alias: None });
789            }
790            _ => {}
791        }
792    }
793
794    fn extract_calls_recursive(
795        &self,
796        node: &Node,
797        source: &str,
798        calls: &mut Vec<FunctionCall>,
799        current_function: Option<&str>,
800    ) {
801        if node.kind() == "call_expression" {
802            if let Some(call) = self.parse_call(node, source, current_function) {
803                calls.push(call);
804            }
805        }
806
807        let func_name = if node.kind() == "function_item" {
808            node.child_by_field_name("name")
809                .map(|n| node_text(&n, source))
810        } else {
811            None
812        };
813
814        let current = func_name
815            .map(String::from)
816            .or_else(|| current_function.map(String::from));
817
818        let mut cursor = node.walk();
819        for child in node.children(&mut cursor) {
820            self.extract_calls_recursive(&child, source, calls, current.as_deref());
821        }
822    }
823
824    fn parse_call(
825        &self,
826        node: &Node,
827        source: &str,
828        current_function: Option<&str>,
829    ) -> Option<FunctionCall> {
830        let function = node.child_by_field_name("function")?;
831
832        let (callee, is_method, receiver) = match function.kind() {
833            "field_expression" => {
834                let value = function
835                    .child_by_field_name("value")
836                    .map(|n| node_text(&n, source).to_string());
837                let field = function
838                    .child_by_field_name("field")
839                    .map(|n| node_text(&n, source).to_string())?;
840                (field, true, value)
841            }
842            "scoped_identifier" => {
843                let name = function
844                    .child_by_field_name("name")
845                    .map(|n| node_text(&n, source).to_string())
846                    .unwrap_or_else(|| node_text(&function, source).to_string());
847                (name, false, None)
848            }
849            "identifier" => (node_text(&function, source).to_string(), false, None),
850            _ => return None,
851        };
852
853        Some(FunctionCall {
854            caller: current_function.unwrap_or("<module>").to_string(),
855            callee,
856            line: node.start_position().row + 1,
857            is_method,
858            receiver,
859        })
860    }
861
862    fn build_function_signature(&self, node: &Node, source: &str) -> String {
863        let vis = node
864            .children(&mut node.walk())
865            .find(|c| c.kind() == "visibility_modifier")
866            .map(|n| format!("{} ", node_text(&n, source)))
867            .unwrap_or_default();
868
869        let async_kw = if node_text(node, source).contains("async fn") {
870            "async "
871        } else {
872            ""
873        };
874
875        let name = node
876            .child_by_field_name("name")
877            .map(|n| node_text(&n, source))
878            .unwrap_or("unknown");
879
880        let generics = node
881            .child_by_field_name("type_parameters")
882            .map(|n| node_text(&n, source))
883            .unwrap_or("");
884
885        let params = node
886            .child_by_field_name("parameters")
887            .map(|n| node_text(&n, source))
888            .unwrap_or("()");
889
890        let return_type = node
891            .child_by_field_name("return_type")
892            .map(|n| format!(" {}", node_text(&n, source)))
893            .unwrap_or_default();
894
895        format!(
896            "{}{}fn {}{}{}{}",
897            vis, async_kw, name, generics, params, return_type
898        )
899    }
900
901    fn clean_block_doc(comment: &str) -> String {
902        comment
903            .trim_start_matches("/**")
904            .trim_start_matches("/*!")
905            .trim_end_matches("*/")
906            .lines()
907            .map(|line| line.trim().trim_start_matches('*').trim())
908            .filter(|line| !line.is_empty())
909            .collect::<Vec<_>>()
910            .join("\n")
911    }
912}
913
914#[cfg(test)]
915mod tests {
916    use super::*;
917
918    fn parse_rs(source: &str) -> (Tree, String) {
919        let mut parser = tree_sitter::Parser::new();
920        parser
921            .set_language(&tree_sitter_rust::LANGUAGE.into())
922            .unwrap();
923        let tree = parser.parse(source, None).unwrap();
924        (tree, source.to_string())
925    }
926
927    #[test]
928    fn test_extract_function() {
929        let source = r#"
930pub fn greet(name: &str) -> String {
931    format!("Hello, {}!", name)
932}
933"#;
934        let (tree, src) = parse_rs(source);
935        let extractor = RustExtractor;
936        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
937
938        assert_eq!(symbols.len(), 1);
939        assert_eq!(symbols[0].name, "greet");
940        assert_eq!(symbols[0].kind, SymbolKind::Function);
941        assert!(symbols[0].exported);
942    }
943
944    #[test]
945    fn test_extract_struct() {
946        let source = r#"
947pub struct User {
948    pub name: String,
949    age: u32,
950}
951"#;
952        let (tree, src) = parse_rs(source);
953        let extractor = RustExtractor;
954        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
955
956        assert!(symbols
957            .iter()
958            .any(|s| s.name == "User" && s.kind == SymbolKind::Struct));
959        assert!(symbols
960            .iter()
961            .any(|s| s.name == "name" && s.kind == SymbolKind::Field));
962        assert!(symbols
963            .iter()
964            .any(|s| s.name == "age" && s.kind == SymbolKind::Field));
965    }
966
967    #[test]
968    fn test_extract_impl() {
969        let source = r#"
970impl User {
971    pub fn new(name: String) -> Self {
972        Self { name, age: 0 }
973    }
974
975    pub fn greet(&self) -> String {
976        format!("Hello, {}!", self.name)
977    }
978}
979"#;
980        let (tree, src) = parse_rs(source);
981        let extractor = RustExtractor;
982        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
983
984        assert!(symbols
985            .iter()
986            .any(|s| s.name == "User" && s.kind == SymbolKind::Impl));
987        assert!(symbols
988            .iter()
989            .any(|s| s.name == "new" && s.kind == SymbolKind::Function));
990        assert!(symbols
991            .iter()
992            .any(|s| s.name == "greet" && s.kind == SymbolKind::Method));
993    }
994
995    #[test]
996    fn test_extract_trait() {
997        let source = r#"
998pub trait Greeter {
999    fn greet(&self) -> String;
1000    fn farewell(&self) -> String {
1001        "Goodbye!".to_string()
1002    }
1003}
1004"#;
1005        let (tree, src) = parse_rs(source);
1006        let extractor = RustExtractor;
1007        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
1008
1009        assert!(symbols
1010            .iter()
1011            .any(|s| s.name == "Greeter" && s.kind == SymbolKind::Trait));
1012    }
1013
1014    #[test]
1015    fn test_extract_enum() {
1016        let source = r#"
1017pub enum Status {
1018    Active,
1019    Inactive,
1020    Pending(String),
1021}
1022"#;
1023        let (tree, src) = parse_rs(source);
1024        let extractor = RustExtractor;
1025        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
1026
1027        assert!(symbols
1028            .iter()
1029            .any(|s| s.name == "Status" && s.kind == SymbolKind::Enum));
1030        assert!(symbols
1031            .iter()
1032            .any(|s| s.name == "Active" && s.kind == SymbolKind::EnumVariant));
1033    }
1034}