1use 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
13pub 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 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 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 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 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 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 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 sym.visibility = self.extract_visibility(node, source);
200 if matches!(sym.visibility, Visibility::Public) {
201 sym = sym.exported();
202 }
203
204 let text = node_text(node, source);
206 if text.contains("async fn") {
207 sym = sym.async_fn();
208 }
209
210 if let Some(type_params) = node.child_by_field_name("type_parameters") {
212 self.extract_generics(&type_params, source, &mut sym);
213 }
214
215 if let Some(params) = node.child_by_field_name("parameters") {
217 self.extract_parameters(¶ms, source, &mut sym);
218 }
219
220 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 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 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 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 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(¶ms, 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 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 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 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 impl_sym.definition_start_line = Some(self.find_definition_start_line(node, source));
498
499 symbols.push(impl_sym);
500
501 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 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 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 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 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 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 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 let mut current = node.prev_sibling();
948 while let Some(prev) = current {
949 match prev.kind() {
950 "attribute_item" => {
951 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 earliest_line = prev.start_position().row + 1;
960 current = prev.prev_sibling();
961 } else {
962 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 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 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 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 assert_eq!(func.start_line, 2);
1177 assert_eq!(func.definition_start_line, Some(2));
1178 }
1179}