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        // Set definition_start_line (before attributes/doc comments)
239        sym.definition_start_line = Some(self.find_definition_start_line(node, source));
240
241        Some(sym)
242    }
243
244    fn extract_struct(
245        &self,
246        node: &Node,
247        source: &str,
248        parent: Option<&str>,
249    ) -> Option<ExtractedSymbol> {
250        let name_node = node.child_by_field_name("name")?;
251        let name = node_text(&name_node, source).to_string();
252
253        let mut sym = ExtractedSymbol::new(
254            name,
255            SymbolKind::Struct,
256            node.start_position().row + 1,
257            node.end_position().row + 1,
258        );
259
260        sym.visibility = self.extract_visibility(node, source);
261        if matches!(sym.visibility, Visibility::Public) {
262            sym = sym.exported();
263        }
264
265        if let Some(type_params) = node.child_by_field_name("type_parameters") {
266            self.extract_generics(&type_params, source, &mut sym);
267        }
268
269        sym.doc_comment = self.extract_doc_comment(node, source);
270
271        if let Some(p) = parent {
272            sym = sym.with_parent(p);
273        }
274
275        // Set definition_start_line (before attributes/doc comments)
276        sym.definition_start_line = Some(self.find_definition_start_line(node, source));
277
278        Some(sym)
279    }
280
281    fn extract_struct_fields(
282        &self,
283        body: &Node,
284        source: &str,
285        symbols: &mut Vec<ExtractedSymbol>,
286        struct_name: Option<&str>,
287    ) {
288        let mut cursor = body.walk();
289        for child in body.children(&mut cursor) {
290            if child.kind() == "field_declaration" {
291                if let Some(name_node) = child.child_by_field_name("name") {
292                    let name = node_text(&name_node, source).to_string();
293
294                    let mut sym = ExtractedSymbol::new(
295                        name,
296                        SymbolKind::Field,
297                        child.start_position().row + 1,
298                        child.end_position().row + 1,
299                    );
300
301                    sym.visibility = self.extract_visibility(&child, source);
302
303                    if let Some(type_node) = child.child_by_field_name("type") {
304                        sym.type_info = Some(node_text(&type_node, source).to_string());
305                    }
306
307                    if let Some(p) = struct_name {
308                        sym = sym.with_parent(p);
309                    }
310
311                    symbols.push(sym);
312                }
313            }
314        }
315    }
316
317    fn extract_enum(
318        &self,
319        node: &Node,
320        source: &str,
321        parent: Option<&str>,
322    ) -> Option<ExtractedSymbol> {
323        let name_node = node.child_by_field_name("name")?;
324        let name = node_text(&name_node, source).to_string();
325
326        let mut sym = ExtractedSymbol::new(
327            name,
328            SymbolKind::Enum,
329            node.start_position().row + 1,
330            node.end_position().row + 1,
331        );
332
333        sym.visibility = self.extract_visibility(node, source);
334        if matches!(sym.visibility, Visibility::Public) {
335            sym = sym.exported();
336        }
337
338        if let Some(type_params) = node.child_by_field_name("type_parameters") {
339            self.extract_generics(&type_params, source, &mut sym);
340        }
341
342        sym.doc_comment = self.extract_doc_comment(node, source);
343
344        if let Some(p) = parent {
345            sym = sym.with_parent(p);
346        }
347
348        // Set definition_start_line (before attributes/doc comments)
349        sym.definition_start_line = Some(self.find_definition_start_line(node, source));
350
351        Some(sym)
352    }
353
354    fn extract_enum_variants(
355        &self,
356        body: &Node,
357        source: &str,
358        symbols: &mut Vec<ExtractedSymbol>,
359        enum_name: Option<&str>,
360    ) {
361        let mut cursor = body.walk();
362        for child in body.children(&mut cursor) {
363            if child.kind() == "enum_variant" {
364                if let Some(name_node) = child.child_by_field_name("name") {
365                    let name = node_text(&name_node, source).to_string();
366
367                    let mut sym = ExtractedSymbol::new(
368                        name,
369                        SymbolKind::EnumVariant,
370                        child.start_position().row + 1,
371                        child.end_position().row + 1,
372                    );
373
374                    if let Some(p) = enum_name {
375                        sym = sym.with_parent(p);
376                    }
377
378                    symbols.push(sym);
379                }
380            }
381        }
382    }
383
384    fn extract_trait(
385        &self,
386        node: &Node,
387        source: &str,
388        parent: Option<&str>,
389    ) -> Option<ExtractedSymbol> {
390        let name_node = node.child_by_field_name("name")?;
391        let name = node_text(&name_node, source).to_string();
392
393        let mut sym = ExtractedSymbol::new(
394            name,
395            SymbolKind::Trait,
396            node.start_position().row + 1,
397            node.end_position().row + 1,
398        );
399
400        sym.visibility = self.extract_visibility(node, source);
401        if matches!(sym.visibility, Visibility::Public) {
402            sym = sym.exported();
403        }
404
405        if let Some(type_params) = node.child_by_field_name("type_parameters") {
406            self.extract_generics(&type_params, source, &mut sym);
407        }
408
409        sym.doc_comment = self.extract_doc_comment(node, source);
410
411        if let Some(p) = parent {
412            sym = sym.with_parent(p);
413        }
414
415        // Set definition_start_line (before attributes/doc comments)
416        sym.definition_start_line = Some(self.find_definition_start_line(node, source));
417
418        Some(sym)
419    }
420
421    fn extract_trait_body(
422        &self,
423        body: &Node,
424        source: &str,
425        symbols: &mut Vec<ExtractedSymbol>,
426        trait_name: Option<&str>,
427    ) {
428        let mut cursor = body.walk();
429        for child in body.children(&mut cursor) {
430            if child.kind() == "function_signature_item" || child.kind() == "function_item" {
431                if let Some(name_node) = child.child_by_field_name("name") {
432                    let name = node_text(&name_node, source).to_string();
433
434                    let mut sym = ExtractedSymbol::new(
435                        name,
436                        SymbolKind::Method,
437                        child.start_position().row + 1,
438                        child.end_position().row + 1,
439                    );
440
441                    if let Some(params) = child.child_by_field_name("parameters") {
442                        self.extract_parameters(&params, source, &mut sym);
443                    }
444
445                    if let Some(ret_type) = child.child_by_field_name("return_type") {
446                        sym.return_type = Some(
447                            node_text(&ret_type, source)
448                                .trim_start_matches("->")
449                                .trim()
450                                .to_string(),
451                        );
452                    }
453
454                    sym.doc_comment = self.extract_doc_comment(&child, source);
455
456                    if let Some(p) = trait_name {
457                        sym = sym.with_parent(p);
458                    }
459
460                    symbols.push(sym);
461                }
462            }
463        }
464    }
465
466    fn extract_impl_block(&self, node: &Node, source: &str, symbols: &mut Vec<ExtractedSymbol>) {
467        // Get the type being implemented
468        let type_name = node
469            .child_by_field_name("type")
470            .map(|n| node_text(&n, source).to_string())
471            .unwrap_or_default();
472
473        // Check if this is a trait impl
474        let trait_name = node
475            .child_by_field_name("trait")
476            .map(|n| node_text(&n, source).to_string());
477
478        let impl_name = if let Some(ref trait_n) = trait_name {
479            format!("{} for {}", trait_n, type_name)
480        } else {
481            type_name.clone()
482        };
483
484        // Create impl block symbol
485        let mut impl_sym = ExtractedSymbol::new(
486            impl_name.clone(),
487            SymbolKind::Impl,
488            node.start_position().row + 1,
489            node.end_position().row + 1,
490        );
491
492        if let Some(type_params) = node.child_by_field_name("type_parameters") {
493            self.extract_generics(&type_params, source, &mut impl_sym);
494        }
495
496        // Set definition_start_line (before attributes/doc comments)
497        impl_sym.definition_start_line = Some(self.find_definition_start_line(node, source));
498
499        symbols.push(impl_sym);
500
501        // Extract methods in the impl block
502        if let Some(body) = node.child_by_field_name("body") {
503            let mut cursor = body.walk();
504            for child in body.children(&mut cursor) {
505                if child.kind() == "function_item" {
506                    if let Some(mut sym) = self.extract_function(&child, source, Some(&type_name)) {
507                        // Check if it's a method (has self parameter)
508                        if sym.parameters.iter().any(|p| p.name.contains("self")) {
509                            sym.kind = SymbolKind::Method;
510                        }
511                        symbols.push(sym);
512                    }
513                }
514            }
515        }
516    }
517
518    fn extract_type_alias(
519        &self,
520        node: &Node,
521        source: &str,
522        parent: Option<&str>,
523    ) -> Option<ExtractedSymbol> {
524        let name_node = node.child_by_field_name("name")?;
525        let name = node_text(&name_node, source).to_string();
526
527        let mut sym = ExtractedSymbol::new(
528            name,
529            SymbolKind::TypeAlias,
530            node.start_position().row + 1,
531            node.end_position().row + 1,
532        );
533
534        sym.visibility = self.extract_visibility(node, source);
535        if matches!(sym.visibility, Visibility::Public) {
536            sym = sym.exported();
537        }
538
539        if let Some(type_node) = node.child_by_field_name("type") {
540            sym.type_info = Some(node_text(&type_node, source).to_string());
541        }
542
543        sym.doc_comment = self.extract_doc_comment(node, source);
544
545        if let Some(p) = parent {
546            sym = sym.with_parent(p);
547        }
548
549        // Set definition_start_line (before attributes/doc comments)
550        sym.definition_start_line = Some(self.find_definition_start_line(node, source));
551
552        Some(sym)
553    }
554
555    fn extract_const(
556        &self,
557        node: &Node,
558        source: &str,
559        parent: Option<&str>,
560    ) -> Option<ExtractedSymbol> {
561        let name_node = node.child_by_field_name("name")?;
562        let name = node_text(&name_node, source).to_string();
563
564        let mut sym = ExtractedSymbol::new(
565            name,
566            SymbolKind::Constant,
567            node.start_position().row + 1,
568            node.end_position().row + 1,
569        );
570
571        sym.visibility = self.extract_visibility(node, source);
572        if matches!(sym.visibility, Visibility::Public) {
573            sym = sym.exported();
574        }
575
576        if let Some(type_node) = node.child_by_field_name("type") {
577            sym.type_info = Some(node_text(&type_node, source).to_string());
578        }
579
580        sym.doc_comment = self.extract_doc_comment(node, source);
581
582        if let Some(p) = parent {
583            sym = sym.with_parent(p);
584        }
585
586        // Set definition_start_line (before attributes/doc comments)
587        sym.definition_start_line = Some(self.find_definition_start_line(node, source));
588
589        Some(sym)
590    }
591
592    fn extract_static(
593        &self,
594        node: &Node,
595        source: &str,
596        parent: Option<&str>,
597    ) -> Option<ExtractedSymbol> {
598        let name_node = node.child_by_field_name("name")?;
599        let name = node_text(&name_node, source).to_string();
600
601        let mut sym = ExtractedSymbol::new(
602            name,
603            SymbolKind::Variable,
604            node.start_position().row + 1,
605            node.end_position().row + 1,
606        );
607
608        sym.visibility = self.extract_visibility(node, source);
609        if matches!(sym.visibility, Visibility::Public) {
610            sym = sym.exported();
611        }
612
613        sym.is_static = true;
614
615        if let Some(type_node) = node.child_by_field_name("type") {
616            sym.type_info = Some(node_text(&type_node, source).to_string());
617        }
618
619        sym.doc_comment = self.extract_doc_comment(node, source);
620
621        if let Some(p) = parent {
622            sym = sym.with_parent(p);
623        }
624
625        // Set definition_start_line (before attributes/doc comments)
626        sym.definition_start_line = Some(self.find_definition_start_line(node, source));
627
628        Some(sym)
629    }
630
631    fn extract_module(
632        &self,
633        node: &Node,
634        source: &str,
635        parent: Option<&str>,
636    ) -> Option<ExtractedSymbol> {
637        let name_node = node.child_by_field_name("name")?;
638        let name = node_text(&name_node, source).to_string();
639
640        let mut sym = ExtractedSymbol::new(
641            name,
642            SymbolKind::Module,
643            node.start_position().row + 1,
644            node.end_position().row + 1,
645        );
646
647        sym.visibility = self.extract_visibility(node, source);
648        if matches!(sym.visibility, Visibility::Public) {
649            sym = sym.exported();
650        }
651
652        sym.doc_comment = self.extract_doc_comment(node, source);
653
654        if let Some(p) = parent {
655            sym = sym.with_parent(p);
656        }
657
658        // Set definition_start_line (before attributes/doc comments)
659        sym.definition_start_line = Some(self.find_definition_start_line(node, source));
660
661        Some(sym)
662    }
663
664    fn extract_visibility(&self, node: &Node, source: &str) -> Visibility {
665        let mut cursor = node.walk();
666        for child in node.children(&mut cursor) {
667            if child.kind() == "visibility_modifier" {
668                let text = node_text(&child, source);
669                if text == "pub" {
670                    return Visibility::Public;
671                } else if text.contains("pub(crate)") {
672                    return Visibility::Crate;
673                } else if text.contains("pub(super)") || text.contains("pub(self)") {
674                    return Visibility::Internal;
675                }
676            }
677        }
678        Visibility::Private
679    }
680
681    fn extract_parameters(&self, params: &Node, source: &str, sym: &mut ExtractedSymbol) {
682        let mut cursor = params.walk();
683        for child in params.children(&mut cursor) {
684            match child.kind() {
685                "parameter" => {
686                    let pattern = child
687                        .child_by_field_name("pattern")
688                        .map(|n| node_text(&n, source).to_string())
689                        .unwrap_or_default();
690
691                    let type_info = child
692                        .child_by_field_name("type")
693                        .map(|n| node_text(&n, source).to_string());
694
695                    sym.add_parameter(Parameter {
696                        name: pattern,
697                        type_info,
698                        default_value: None,
699                        is_rest: false,
700                        is_optional: false,
701                    });
702                }
703                "self_parameter" => {
704                    sym.add_parameter(Parameter {
705                        name: node_text(&child, source).to_string(),
706                        type_info: Some("Self".to_string()),
707                        default_value: None,
708                        is_rest: false,
709                        is_optional: false,
710                    });
711                }
712                _ => {}
713            }
714        }
715    }
716
717    fn extract_generics(&self, type_params: &Node, source: &str, sym: &mut ExtractedSymbol) {
718        let mut cursor = type_params.walk();
719        for child in type_params.children(&mut cursor) {
720            match child.kind() {
721                "type_identifier" | "lifetime" => {
722                    sym.add_generic(node_text(&child, source));
723                }
724                "constrained_type_parameter" => {
725                    if let Some(name) = child.child_by_field_name("left") {
726                        sym.add_generic(node_text(&name, source));
727                    }
728                }
729                _ => {}
730            }
731        }
732    }
733
734    fn extract_imports_recursive(&self, node: &Node, source: &str, imports: &mut Vec<Import>) {
735        if node.kind() == "use_declaration" {
736            if let Some(import) = self.parse_use(node, source) {
737                imports.push(import);
738            }
739        }
740
741        let mut cursor = node.walk();
742        for child in node.children(&mut cursor) {
743            self.extract_imports_recursive(&child, source, imports);
744        }
745    }
746
747    fn parse_use(&self, node: &Node, source: &str) -> Option<Import> {
748        let argument = node.child_by_field_name("argument")?;
749
750        let mut import = Import {
751            source: String::new(),
752            names: Vec::new(),
753            is_default: false,
754            is_namespace: false,
755            line: node.start_position().row + 1,
756        };
757
758        self.parse_use_path(&argument, source, &mut import, String::new());
759
760        Some(import)
761    }
762
763    fn parse_use_path(&self, node: &Node, source: &str, import: &mut Import, prefix: String) {
764        match node.kind() {
765            "scoped_identifier" => {
766                let path = node
767                    .child_by_field_name("path")
768                    .map(|n| node_text(&n, source).to_string())
769                    .unwrap_or_default();
770                let name = node
771                    .child_by_field_name("name")
772                    .map(|n| node_text(&n, source).to_string())
773                    .unwrap_or_default();
774
775                let full_path = if prefix.is_empty() {
776                    path
777                } else {
778                    format!("{}::{}", prefix, path)
779                };
780
781                import.source = full_path;
782                import.names.push(ImportedName { name, alias: None });
783            }
784            "use_as_clause" => {
785                let path = node
786                    .child_by_field_name("path")
787                    .map(|n| node_text(&n, source).to_string())
788                    .unwrap_or_default();
789                let alias = node
790                    .child_by_field_name("alias")
791                    .map(|n| node_text(&n, source).to_string());
792
793                import.source = prefix;
794                import.names.push(ImportedName { name: path, alias });
795            }
796            "use_wildcard" => {
797                import.source = prefix;
798                import.is_namespace = true;
799                import.names.push(ImportedName {
800                    name: "*".to_string(),
801                    alias: None,
802                });
803            }
804            "use_list" => {
805                let mut cursor = node.walk();
806                for child in node.children(&mut cursor) {
807                    self.parse_use_path(&child, source, import, prefix.clone());
808                }
809            }
810            "identifier" => {
811                let name = node_text(node, source).to_string();
812                if import.source.is_empty() {
813                    import.source = prefix;
814                }
815                import.names.push(ImportedName { name, alias: None });
816            }
817            _ => {}
818        }
819    }
820
821    fn extract_calls_recursive(
822        &self,
823        node: &Node,
824        source: &str,
825        calls: &mut Vec<FunctionCall>,
826        current_function: Option<&str>,
827    ) {
828        if node.kind() == "call_expression" {
829            if let Some(call) = self.parse_call(node, source, current_function) {
830                calls.push(call);
831            }
832        }
833
834        let func_name = if node.kind() == "function_item" {
835            node.child_by_field_name("name")
836                .map(|n| node_text(&n, source))
837        } else {
838            None
839        };
840
841        let current = func_name
842            .map(String::from)
843            .or_else(|| current_function.map(String::from));
844
845        let mut cursor = node.walk();
846        for child in node.children(&mut cursor) {
847            self.extract_calls_recursive(&child, source, calls, current.as_deref());
848        }
849    }
850
851    fn parse_call(
852        &self,
853        node: &Node,
854        source: &str,
855        current_function: Option<&str>,
856    ) -> Option<FunctionCall> {
857        let function = node.child_by_field_name("function")?;
858
859        let (callee, is_method, receiver) = match function.kind() {
860            "field_expression" => {
861                let value = function
862                    .child_by_field_name("value")
863                    .map(|n| node_text(&n, source).to_string());
864                let field = function
865                    .child_by_field_name("field")
866                    .map(|n| node_text(&n, source).to_string())?;
867                (field, true, value)
868            }
869            "scoped_identifier" => {
870                let name = function
871                    .child_by_field_name("name")
872                    .map(|n| node_text(&n, source).to_string())
873                    .unwrap_or_else(|| node_text(&function, source).to_string());
874                (name, false, None)
875            }
876            "identifier" => (node_text(&function, source).to_string(), false, None),
877            _ => return None,
878        };
879
880        Some(FunctionCall {
881            caller: current_function.unwrap_or("<module>").to_string(),
882            callee,
883            line: node.start_position().row + 1,
884            is_method,
885            receiver,
886        })
887    }
888
889    fn build_function_signature(&self, node: &Node, source: &str) -> String {
890        let vis = node
891            .children(&mut node.walk())
892            .find(|c| c.kind() == "visibility_modifier")
893            .map(|n| format!("{} ", node_text(&n, source)))
894            .unwrap_or_default();
895
896        let async_kw = if node_text(node, source).contains("async fn") {
897            "async "
898        } else {
899            ""
900        };
901
902        let name = node
903            .child_by_field_name("name")
904            .map(|n| node_text(&n, source))
905            .unwrap_or("unknown");
906
907        let generics = node
908            .child_by_field_name("type_parameters")
909            .map(|n| node_text(&n, source))
910            .unwrap_or("");
911
912        let params = node
913            .child_by_field_name("parameters")
914            .map(|n| node_text(&n, source))
915            .unwrap_or("()");
916
917        let return_type = node
918            .child_by_field_name("return_type")
919            .map(|n| format!(" {}", node_text(&n, source)))
920            .unwrap_or_default();
921
922        format!(
923            "{}{}fn {}{}{}{}",
924            vis, async_kw, name, generics, params, return_type
925        )
926    }
927
928    fn clean_block_doc(comment: &str) -> String {
929        comment
930            .trim_start_matches("/**")
931            .trim_start_matches("/*!")
932            .trim_end_matches("*/")
933            .lines()
934            .map(|line| line.trim().trim_start_matches('*').trim())
935            .filter(|line| !line.is_empty())
936            .collect::<Vec<_>>()
937            .join("\n")
938    }
939
940    /// Find the earliest line of attributes/doc comments before a node.
941    /// Returns the line where annotation should be inserted (before attributes/docs).
942    fn find_definition_start_line(&self, node: &Node, source: &str) -> usize {
943        let node_start = node.start_position().row + 1;
944        let mut earliest_line = node_start;
945
946        // Walk backwards through siblings to find attributes and doc comments
947        let mut current = node.prev_sibling();
948        while let Some(prev) = current {
949            match prev.kind() {
950                "attribute_item" => {
951                    // #[...] attributes
952                    earliest_line = prev.start_position().row + 1;
953                    current = prev.prev_sibling();
954                }
955                "line_comment" => {
956                    let comment = node_text(&prev, source);
957                    if comment.starts_with("///") || comment.starts_with("//!") {
958                        // Doc comments should also be considered part of the definition
959                        earliest_line = prev.start_position().row + 1;
960                        current = prev.prev_sibling();
961                    } else {
962                        // Regular comment, stop here
963                        break;
964                    }
965                }
966                "block_comment" => {
967                    let comment = node_text(&prev, source);
968                    if comment.starts_with("/**") || comment.starts_with("/*!") {
969                        earliest_line = prev.start_position().row + 1;
970                        current = prev.prev_sibling();
971                    } else {
972                        break;
973                    }
974                }
975                _ => break,
976            }
977        }
978
979        earliest_line
980    }
981}
982
983#[cfg(test)]
984mod tests {
985    use super::*;
986
987    fn parse_rs(source: &str) -> (Tree, String) {
988        let mut parser = tree_sitter::Parser::new();
989        parser
990            .set_language(&tree_sitter_rust::LANGUAGE.into())
991            .unwrap();
992        let tree = parser.parse(source, None).unwrap();
993        (tree, source.to_string())
994    }
995
996    #[test]
997    fn test_extract_function() {
998        let source = r#"
999pub fn greet(name: &str) -> String {
1000    format!("Hello, {}!", name)
1001}
1002"#;
1003        let (tree, src) = parse_rs(source);
1004        let extractor = RustExtractor;
1005        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
1006
1007        assert_eq!(symbols.len(), 1);
1008        assert_eq!(symbols[0].name, "greet");
1009        assert_eq!(symbols[0].kind, SymbolKind::Function);
1010        assert!(symbols[0].exported);
1011    }
1012
1013    #[test]
1014    fn test_extract_struct() {
1015        let source = r#"
1016pub struct User {
1017    pub name: String,
1018    age: u32,
1019}
1020"#;
1021        let (tree, src) = parse_rs(source);
1022        let extractor = RustExtractor;
1023        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
1024
1025        assert!(symbols
1026            .iter()
1027            .any(|s| s.name == "User" && s.kind == SymbolKind::Struct));
1028        assert!(symbols
1029            .iter()
1030            .any(|s| s.name == "name" && s.kind == SymbolKind::Field));
1031        assert!(symbols
1032            .iter()
1033            .any(|s| s.name == "age" && s.kind == SymbolKind::Field));
1034    }
1035
1036    #[test]
1037    fn test_extract_impl() {
1038        let source = r#"
1039impl User {
1040    pub fn new(name: String) -> Self {
1041        Self { name, age: 0 }
1042    }
1043
1044    pub fn greet(&self) -> String {
1045        format!("Hello, {}!", self.name)
1046    }
1047}
1048"#;
1049        let (tree, src) = parse_rs(source);
1050        let extractor = RustExtractor;
1051        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
1052
1053        assert!(symbols
1054            .iter()
1055            .any(|s| s.name == "User" && s.kind == SymbolKind::Impl));
1056        assert!(symbols
1057            .iter()
1058            .any(|s| s.name == "new" && s.kind == SymbolKind::Function));
1059        assert!(symbols
1060            .iter()
1061            .any(|s| s.name == "greet" && s.kind == SymbolKind::Method));
1062    }
1063
1064    #[test]
1065    fn test_extract_trait() {
1066        let source = r#"
1067pub trait Greeter {
1068    fn greet(&self) -> String;
1069    fn farewell(&self) -> String {
1070        "Goodbye!".to_string()
1071    }
1072}
1073"#;
1074        let (tree, src) = parse_rs(source);
1075        let extractor = RustExtractor;
1076        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
1077
1078        assert!(symbols
1079            .iter()
1080            .any(|s| s.name == "Greeter" && s.kind == SymbolKind::Trait));
1081    }
1082
1083    #[test]
1084    fn test_extract_enum() {
1085        let source = r#"
1086pub enum Status {
1087    Active,
1088    Inactive,
1089    Pending(String),
1090}
1091"#;
1092        let (tree, src) = parse_rs(source);
1093        let extractor = RustExtractor;
1094        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
1095
1096        assert!(symbols
1097            .iter()
1098            .any(|s| s.name == "Status" && s.kind == SymbolKind::Enum));
1099        assert!(symbols
1100            .iter()
1101            .any(|s| s.name == "Active" && s.kind == SymbolKind::EnumVariant));
1102    }
1103
1104    #[test]
1105    fn test_definition_start_line_with_attributes() {
1106        let source = r#"
1107#[derive(Debug, Clone)]
1108#[serde(rename_all = "camelCase")]
1109pub struct User {
1110    name: String,
1111}
1112"#;
1113        let (tree, src) = parse_rs(source);
1114        let extractor = RustExtractor;
1115        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
1116
1117        let user = symbols.iter().find(|s| s.name == "User").unwrap();
1118        // Struct starts at line 4, but definition_start_line should be line 2 (first attribute)
1119        assert_eq!(user.start_line, 4);
1120        assert_eq!(user.definition_start_line, Some(2));
1121    }
1122
1123    #[test]
1124    fn test_definition_start_line_with_doc_comment() {
1125        let source = r#"
1126/// A user in the system.
1127/// Has a name and age.
1128pub fn create_user(name: &str) -> User {
1129    User { name: name.to_string() }
1130}
1131"#;
1132        let (tree, src) = parse_rs(source);
1133        let extractor = RustExtractor;
1134        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
1135
1136        let func = symbols.iter().find(|s| s.name == "create_user").unwrap();
1137        // Function starts at line 4, but definition_start_line should be line 2 (first doc comment)
1138        assert_eq!(func.start_line, 4);
1139        assert_eq!(func.definition_start_line, Some(2));
1140    }
1141
1142    #[test]
1143    fn test_definition_start_line_with_attrs_and_docs() {
1144        let source = r#"
1145/// Documentation for the handler.
1146#[derive(Clone)]
1147#[async_trait]
1148pub async fn handle_request() {
1149    // implementation
1150}
1151"#;
1152        let (tree, src) = parse_rs(source);
1153        let extractor = RustExtractor;
1154        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
1155
1156        let func = symbols.iter().find(|s| s.name == "handle_request").unwrap();
1157        // Function starts at line 5, but definition_start_line should be line 2 (doc comment before attrs)
1158        assert_eq!(func.start_line, 5);
1159        assert_eq!(func.definition_start_line, Some(2));
1160    }
1161
1162    #[test]
1163    fn test_definition_start_line_no_decorations() {
1164        let source = r#"
1165fn simple_function() {}
1166"#;
1167        let (tree, src) = parse_rs(source);
1168        let extractor = RustExtractor;
1169        let symbols = extractor.extract_symbols(&tree, &src).unwrap();
1170
1171        let func = symbols
1172            .iter()
1173            .find(|s| s.name == "simple_function")
1174            .unwrap();
1175        // No attributes or doc comments, so definition_start_line equals start_line
1176        assert_eq!(func.start_line, 2);
1177        assert_eq!(func.definition_start_line, Some(2));
1178    }
1179}