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 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(¶ms, 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 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 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 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 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 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}