1use crate::models::{Language, SearchResult, Span, SymbolKind};
19use anyhow::{Context, Result};
20use streaming_iterator::StreamingIterator;
21use tree_sitter::{Parser, Query, QueryCursor};
22
23pub fn parse(path: &str, source: &str) -> Result<Vec<SearchResult>> {
25 let mut parser = Parser::new();
26 let language = tree_sitter_c_sharp::LANGUAGE;
27
28 parser
29 .set_language(&language.into())
30 .context("Failed to set C# language")?;
31
32 let tree = parser
33 .parse(source, None)
34 .context("Failed to parse C# source")?;
35
36 let root_node = tree.root_node();
37
38 let mut symbols = Vec::new();
39
40 symbols.extend(extract_namespaces(source, &root_node, &language.into())?);
42 symbols.extend(extract_classes(source, &root_node, &language.into())?);
43 symbols.extend(extract_interfaces(source, &root_node, &language.into())?);
44 symbols.extend(extract_structs(source, &root_node, &language.into())?);
45 symbols.extend(extract_enums(source, &root_node, &language.into())?);
46 symbols.extend(extract_records(source, &root_node, &language.into())?);
47 symbols.extend(extract_delegates(source, &root_node, &language.into())?);
48 symbols.extend(extract_attributes(source, &root_node, &language.into())?);
49 symbols.extend(extract_methods(source, &root_node, &language.into())?);
50 symbols.extend(extract_properties(source, &root_node, &language.into())?);
51 symbols.extend(extract_events(source, &root_node, &language.into())?);
52 symbols.extend(extract_indexers(source, &root_node, &language.into())?);
53 symbols.extend(extract_local_variables(
54 source,
55 &root_node,
56 &language.into(),
57 )?);
58
59 for symbol in &mut symbols {
61 symbol.path = path.to_string();
62 symbol.lang = Language::CSharp;
63 }
64
65 Ok(symbols)
66}
67
68fn extract_namespaces(
70 source: &str,
71 root: &tree_sitter::Node,
72 language: &tree_sitter::Language,
73) -> Result<Vec<SearchResult>> {
74 let query_str = r#"
75 (namespace_declaration
76 name: (_) @name) @namespace
77
78 (file_scoped_namespace_declaration
79 name: (_) @name) @namespace
80 "#;
81
82 let query = Query::new(language, query_str).context("Failed to create namespace query")?;
83
84 extract_symbols(source, root, &query, SymbolKind::Namespace, None)
85}
86
87fn extract_classes(
89 source: &str,
90 root: &tree_sitter::Node,
91 language: &tree_sitter::Language,
92) -> Result<Vec<SearchResult>> {
93 let query_str = r#"
94 (class_declaration
95 name: (identifier) @name) @class
96 "#;
97
98 let query = Query::new(language, query_str).context("Failed to create class query")?;
99
100 extract_symbols(source, root, &query, SymbolKind::Class, None)
101}
102
103fn extract_interfaces(
105 source: &str,
106 root: &tree_sitter::Node,
107 language: &tree_sitter::Language,
108) -> Result<Vec<SearchResult>> {
109 let query_str = r#"
110 (interface_declaration
111 name: (identifier) @name) @interface
112 "#;
113
114 let query = Query::new(language, query_str).context("Failed to create interface query")?;
115
116 extract_symbols(source, root, &query, SymbolKind::Interface, None)
117}
118
119fn extract_structs(
121 source: &str,
122 root: &tree_sitter::Node,
123 language: &tree_sitter::Language,
124) -> Result<Vec<SearchResult>> {
125 let query_str = r#"
126 (struct_declaration
127 name: (identifier) @name) @struct
128 "#;
129
130 let query = Query::new(language, query_str).context("Failed to create struct query")?;
131
132 extract_symbols(source, root, &query, SymbolKind::Struct, None)
133}
134
135fn extract_enums(
137 source: &str,
138 root: &tree_sitter::Node,
139 language: &tree_sitter::Language,
140) -> Result<Vec<SearchResult>> {
141 let query_str = r#"
142 (enum_declaration
143 name: (identifier) @name) @enum
144 "#;
145
146 let query = Query::new(language, query_str).context("Failed to create enum query")?;
147
148 extract_symbols(source, root, &query, SymbolKind::Enum, None)
149}
150
151fn extract_records(
153 source: &str,
154 root: &tree_sitter::Node,
155 language: &tree_sitter::Language,
156) -> Result<Vec<SearchResult>> {
157 let query_str = r#"
158 (record_declaration
159 name: (identifier) @name) @record
160 "#;
161
162 let query = Query::new(language, query_str).context("Failed to create record query")?;
163
164 extract_symbols(source, root, &query, SymbolKind::Type, None)
165}
166
167fn extract_delegates(
169 source: &str,
170 root: &tree_sitter::Node,
171 language: &tree_sitter::Language,
172) -> Result<Vec<SearchResult>> {
173 let query_str = r#"
174 (delegate_declaration
175 name: (identifier) @name) @delegate
176 "#;
177
178 let query = Query::new(language, query_str).context("Failed to create delegate query")?;
179
180 extract_symbols(source, root, &query, SymbolKind::Type, None)
181}
182
183fn extract_attributes(
187 source: &str,
188 root: &tree_sitter::Node,
189 language: &tree_sitter::Language,
190) -> Result<Vec<SearchResult>> {
191 let mut symbols = Vec::new();
192
193 let def_query_str = r#"
195 (class_declaration
196 name: (identifier) @name) @class
197 "#;
198
199 let def_query = Query::new(language, def_query_str)
200 .context("Failed to create attribute definition query")?;
201
202 let mut cursor = QueryCursor::new();
203 let mut matches = cursor.matches(&def_query, *root, source.as_bytes());
204
205 while let Some(match_) = matches.next() {
206 let mut name = None;
207 let mut full_node = None;
208
209 for capture in match_.captures {
210 let capture_name: &str = &def_query.capture_names()[capture.index as usize];
211 match capture_name {
212 "name" => {
213 name = Some(
214 capture
215 .node
216 .utf8_text(source.as_bytes())
217 .unwrap_or("")
218 .to_string(),
219 );
220 }
221 "class" => {
222 full_node = Some(capture.node);
223 }
224 _ => {}
225 }
226 }
227
228 if let (Some(name), Some(node)) = (name, full_node) {
231 let mut is_attribute = name.ends_with("Attribute");
232
233 if !is_attribute {
235 for i in 0..node.child_count() {
237 if let Some(child) = node.child(i as u32) {
238 if child.kind() == "base_list" {
239 let base_text = child.utf8_text(source.as_bytes()).unwrap_or("");
240 if base_text.contains("Attribute") {
241 is_attribute = true;
242 break;
243 }
244 }
245 }
246 }
247 }
248
249 if is_attribute {
250 let span = node_to_span(&node);
251 let preview = extract_preview(source, &span);
252
253 symbols.push(SearchResult::new(
254 String::new(),
255 Language::CSharp,
256 SymbolKind::Attribute,
257 Some(name),
258 span,
259 None,
260 preview,
261 ));
262 }
263 }
264 }
265
266 let use_query_str = r#"
268 (attribute_list
269 (attribute
270 name: (_) @name)) @attr
271 "#;
272
273 let use_query =
274 Query::new(language, use_query_str).context("Failed to create attribute use query")?;
275
276 symbols.extend(extract_symbols(
277 source,
278 root,
279 &use_query,
280 SymbolKind::Attribute,
281 None,
282 )?);
283
284 Ok(symbols)
285}
286
287fn extract_methods(
289 source: &str,
290 root: &tree_sitter::Node,
291 language: &tree_sitter::Language,
292) -> Result<Vec<SearchResult>> {
293 let query_str = r#"
294 (class_declaration
295 name: (identifier) @class_name
296 body: (declaration_list
297 (method_declaration
298 name: (identifier) @method_name))) @class
299
300 (struct_declaration
301 name: (identifier) @struct_name
302 body: (declaration_list
303 (method_declaration
304 name: (identifier) @method_name))) @struct
305
306 (interface_declaration
307 name: (identifier) @interface_name
308 body: (declaration_list
309 (method_declaration
310 name: (identifier) @method_name))) @interface
311
312 (record_declaration
313 name: (identifier) @record_name
314 body: (declaration_list
315 (method_declaration
316 name: (identifier) @method_name))) @record
317 "#;
318
319 let query = Query::new(language, query_str).context("Failed to create method query")?;
320
321 let mut cursor = QueryCursor::new();
322 let mut matches = cursor.matches(&query, *root, source.as_bytes());
323
324 let mut symbols = Vec::new();
325
326 while let Some(match_) = matches.next() {
327 let mut scope_name = None;
328 let mut scope_type = None;
329 let mut method_name = None;
330 let mut method_node = None;
331
332 for capture in match_.captures {
333 let capture_name: &str = &query.capture_names()[capture.index as usize];
334 match capture_name {
335 "class_name" => {
336 scope_name = Some(
337 capture
338 .node
339 .utf8_text(source.as_bytes())
340 .unwrap_or("")
341 .to_string(),
342 );
343 scope_type = Some("class");
344 }
345 "struct_name" => {
346 scope_name = Some(
347 capture
348 .node
349 .utf8_text(source.as_bytes())
350 .unwrap_or("")
351 .to_string(),
352 );
353 scope_type = Some("struct");
354 }
355 "interface_name" => {
356 scope_name = Some(
357 capture
358 .node
359 .utf8_text(source.as_bytes())
360 .unwrap_or("")
361 .to_string(),
362 );
363 scope_type = Some("interface");
364 }
365 "record_name" => {
366 scope_name = Some(
367 capture
368 .node
369 .utf8_text(source.as_bytes())
370 .unwrap_or("")
371 .to_string(),
372 );
373 scope_type = Some("record");
374 }
375 "method_name" => {
376 method_name = Some(
377 capture
378 .node
379 .utf8_text(source.as_bytes())
380 .unwrap_or("")
381 .to_string(),
382 );
383 let mut current = capture.node;
385 while let Some(parent) = current.parent() {
386 if parent.kind() == "method_declaration" {
387 method_node = Some(parent);
388 break;
389 }
390 current = parent;
391 }
392 }
393 _ => {}
394 }
395 }
396
397 if let (Some(scope_name), Some(scope_type), Some(method_name), Some(node)) =
398 (scope_name, scope_type, method_name, method_node)
399 {
400 let scope = format!("{} {}", scope_type, scope_name);
401 let span = node_to_span(&node);
402 let preview = extract_preview(source, &span);
403
404 symbols.push(SearchResult::new(
405 String::new(),
406 Language::CSharp,
407 SymbolKind::Method,
408 Some(method_name),
409 span,
410 Some(scope),
411 preview,
412 ));
413 }
414 }
415
416 Ok(symbols)
417}
418
419fn extract_properties(
421 source: &str,
422 root: &tree_sitter::Node,
423 language: &tree_sitter::Language,
424) -> Result<Vec<SearchResult>> {
425 let query_str = r#"
426 (class_declaration
427 name: (identifier) @class_name
428 body: (declaration_list
429 (property_declaration
430 name: (identifier) @property_name))) @class
431
432 (struct_declaration
433 name: (identifier) @struct_name
434 body: (declaration_list
435 (property_declaration
436 name: (identifier) @property_name))) @struct
437
438 (interface_declaration
439 name: (identifier) @interface_name
440 body: (declaration_list
441 (property_declaration
442 name: (identifier) @property_name))) @interface
443
444 (record_declaration
445 name: (identifier) @record_name
446 body: (declaration_list
447 (property_declaration
448 name: (identifier) @property_name))) @record
449 "#;
450
451 let query = Query::new(language, query_str).context("Failed to create property query")?;
452
453 let mut cursor = QueryCursor::new();
454 let mut matches = cursor.matches(&query, *root, source.as_bytes());
455
456 let mut symbols = Vec::new();
457
458 while let Some(match_) = matches.next() {
459 let mut scope_name = None;
460 let mut scope_type = None;
461 let mut property_name = None;
462 let mut property_node = None;
463
464 for capture in match_.captures {
465 let capture_name: &str = &query.capture_names()[capture.index as usize];
466 match capture_name {
467 "class_name" => {
468 scope_name = Some(
469 capture
470 .node
471 .utf8_text(source.as_bytes())
472 .unwrap_or("")
473 .to_string(),
474 );
475 scope_type = Some("class");
476 }
477 "struct_name" => {
478 scope_name = Some(
479 capture
480 .node
481 .utf8_text(source.as_bytes())
482 .unwrap_or("")
483 .to_string(),
484 );
485 scope_type = Some("struct");
486 }
487 "interface_name" => {
488 scope_name = Some(
489 capture
490 .node
491 .utf8_text(source.as_bytes())
492 .unwrap_or("")
493 .to_string(),
494 );
495 scope_type = Some("interface");
496 }
497 "record_name" => {
498 scope_name = Some(
499 capture
500 .node
501 .utf8_text(source.as_bytes())
502 .unwrap_or("")
503 .to_string(),
504 );
505 scope_type = Some("record");
506 }
507 "property_name" => {
508 property_name = Some(
509 capture
510 .node
511 .utf8_text(source.as_bytes())
512 .unwrap_or("")
513 .to_string(),
514 );
515 let mut current = capture.node;
517 while let Some(parent) = current.parent() {
518 if parent.kind() == "property_declaration" {
519 property_node = Some(parent);
520 break;
521 }
522 current = parent;
523 }
524 }
525 _ => {}
526 }
527 }
528
529 if let (Some(scope_name), Some(scope_type), Some(property_name), Some(node)) =
530 (scope_name, scope_type, property_name, property_node)
531 {
532 let scope = format!("{} {}", scope_type, scope_name);
533 let span = node_to_span(&node);
534 let preview = extract_preview(source, &span);
535
536 symbols.push(SearchResult::new(
537 String::new(),
538 Language::CSharp,
539 SymbolKind::Variable,
540 Some(property_name),
541 span,
542 Some(scope),
543 preview,
544 ));
545 }
546 }
547
548 Ok(symbols)
549}
550
551fn extract_events(
553 source: &str,
554 root: &tree_sitter::Node,
555 language: &tree_sitter::Language,
556) -> Result<Vec<SearchResult>> {
557 let query_str = r#"
558 (class_declaration
559 name: (identifier) @class_name
560 body: (declaration_list
561 (event_field_declaration
562 (variable_declaration
563 (variable_declarator
564 (identifier) @event_name))))) @class
565
566 (struct_declaration
567 name: (identifier) @struct_name
568 body: (declaration_list
569 (event_field_declaration
570 (variable_declaration
571 (variable_declarator
572 (identifier) @event_name))))) @struct
573
574 (interface_declaration
575 name: (identifier) @interface_name
576 body: (declaration_list
577 (event_field_declaration
578 (variable_declaration
579 (variable_declarator
580 (identifier) @event_name))))) @interface
581 "#;
582
583 let query = Query::new(language, query_str).context("Failed to create event query")?;
584
585 let mut cursor = QueryCursor::new();
586 let mut matches = cursor.matches(&query, *root, source.as_bytes());
587
588 let mut symbols = Vec::new();
589
590 while let Some(match_) = matches.next() {
591 let mut scope_name = None;
592 let mut scope_type = None;
593 let mut event_name = None;
594 let mut event_node = None;
595
596 for capture in match_.captures {
597 let capture_name: &str = &query.capture_names()[capture.index as usize];
598 match capture_name {
599 "class_name" => {
600 scope_name = Some(
601 capture
602 .node
603 .utf8_text(source.as_bytes())
604 .unwrap_or("")
605 .to_string(),
606 );
607 scope_type = Some("class");
608 }
609 "struct_name" => {
610 scope_name = Some(
611 capture
612 .node
613 .utf8_text(source.as_bytes())
614 .unwrap_or("")
615 .to_string(),
616 );
617 scope_type = Some("struct");
618 }
619 "interface_name" => {
620 scope_name = Some(
621 capture
622 .node
623 .utf8_text(source.as_bytes())
624 .unwrap_or("")
625 .to_string(),
626 );
627 scope_type = Some("interface");
628 }
629 "event_name" => {
630 event_name = Some(
631 capture
632 .node
633 .utf8_text(source.as_bytes())
634 .unwrap_or("")
635 .to_string(),
636 );
637 let mut current = capture.node;
639 while let Some(parent) = current.parent() {
640 if parent.kind() == "event_field_declaration" {
641 event_node = Some(parent);
642 break;
643 }
644 current = parent;
645 }
646 }
647 _ => {}
648 }
649 }
650
651 if let (Some(scope_name), Some(scope_type), Some(event_name), Some(node)) =
652 (scope_name, scope_type, event_name, event_node)
653 {
654 let scope = format!("{} {}", scope_type, scope_name);
655 let span = node_to_span(&node);
656 let preview = extract_preview(source, &span);
657
658 symbols.push(SearchResult::new(
659 String::new(),
660 Language::CSharp,
661 SymbolKind::Event,
662 Some(event_name),
663 span,
664 Some(scope),
665 preview,
666 ));
667 }
668 }
669
670 Ok(symbols)
671}
672
673fn extract_indexers(
675 source: &str,
676 root: &tree_sitter::Node,
677 language: &tree_sitter::Language,
678) -> Result<Vec<SearchResult>> {
679 let query_str = r#"
680 (class_declaration
681 name: (identifier) @class_name
682 body: (declaration_list
683 (indexer_declaration) @indexer_name)) @class
684
685 (struct_declaration
686 name: (identifier) @struct_name
687 body: (declaration_list
688 (indexer_declaration) @indexer_name)) @struct
689 "#;
690
691 let query = Query::new(language, query_str).context("Failed to create indexer query")?;
692
693 let mut cursor = QueryCursor::new();
694 let mut matches = cursor.matches(&query, *root, source.as_bytes());
695
696 let mut symbols = Vec::new();
697
698 while let Some(match_) = matches.next() {
699 let mut scope_name = None;
700 let mut scope_type = None;
701 let mut indexer_node = None;
702
703 for capture in match_.captures {
704 let capture_name: &str = &query.capture_names()[capture.index as usize];
705 match capture_name {
706 "class_name" => {
707 scope_name = Some(
708 capture
709 .node
710 .utf8_text(source.as_bytes())
711 .unwrap_or("")
712 .to_string(),
713 );
714 scope_type = Some("class");
715 }
716 "struct_name" => {
717 scope_name = Some(
718 capture
719 .node
720 .utf8_text(source.as_bytes())
721 .unwrap_or("")
722 .to_string(),
723 );
724 scope_type = Some("struct");
725 }
726 "indexer_name" => {
727 indexer_node = Some(capture.node);
728 }
729 _ => {}
730 }
731 }
732
733 if let (Some(scope_name), Some(scope_type), Some(node)) =
734 (scope_name, scope_type, indexer_node)
735 {
736 let scope = format!("{} {}", scope_type, scope_name);
737 let span = node_to_span(&node);
738 let preview = extract_preview(source, &span);
739
740 symbols.push(SearchResult::new(
742 String::new(),
743 Language::CSharp,
744 SymbolKind::Property,
745 Some("this[]".to_string()),
746 span,
747 Some(scope),
748 preview,
749 ));
750 }
751 }
752
753 Ok(symbols)
754}
755
756fn extract_local_variables(
758 source: &str,
759 root: &tree_sitter::Node,
760 language: &tree_sitter::Language,
761) -> Result<Vec<SearchResult>> {
762 let query_str = r#"
763 (local_declaration_statement
764 (variable_declaration
765 (variable_declarator
766 (identifier) @name))) @var
767 "#;
768
769 let query = Query::new(language, query_str).context("Failed to create local variable query")?;
770
771 extract_symbols(source, root, &query, SymbolKind::Variable, None)
772}
773
774fn extract_symbols(
776 source: &str,
777 root: &tree_sitter::Node,
778 query: &Query,
779 kind: SymbolKind,
780 scope: Option<String>,
781) -> Result<Vec<SearchResult>> {
782 let mut cursor = QueryCursor::new();
783 let mut matches = cursor.matches(query, *root, source.as_bytes());
784
785 let mut symbols = Vec::new();
786
787 while let Some(match_) = matches.next() {
788 let mut name = None;
790 let mut full_node = None;
791
792 for capture in match_.captures {
793 let capture_name: &str = &query.capture_names()[capture.index as usize];
794 if capture_name == "name" {
795 name = Some(
796 capture
797 .node
798 .utf8_text(source.as_bytes())
799 .unwrap_or("")
800 .to_string(),
801 );
802 } else {
803 full_node = Some(capture.node);
805 }
806 }
807
808 if let (Some(name), Some(node)) = (name, full_node) {
809 let span = node_to_span(&node);
810 let preview = extract_preview(source, &span);
811
812 symbols.push(SearchResult::new(
813 String::new(),
814 Language::CSharp,
815 kind.clone(),
816 Some(name),
817 span,
818 scope.clone(),
819 preview,
820 ));
821 }
822 }
823
824 Ok(symbols)
825}
826
827fn node_to_span(node: &tree_sitter::Node) -> Span {
829 let start = node.start_position();
830 let end = node.end_position();
831
832 Span::new(
833 start.row + 1, start.column,
835 end.row + 1,
836 end.column,
837 )
838}
839
840fn extract_preview(source: &str, span: &Span) -> String {
842 let lines: Vec<&str> = source.lines().collect();
843
844 let start_idx = (span.start_line - 1) as usize; let end_idx = (start_idx + 7).min(lines.len());
847
848 lines[start_idx..end_idx].join("\n")
849}
850
851#[cfg(test)]
852mod tests {
853 use super::*;
854
855 #[test]
856 fn test_parse_class() {
857 let source = r#"
858public class User
859{
860 private string name;
861 private int age;
862}
863 "#;
864
865 let symbols = parse("test.cs", source).unwrap();
866
867 let class_symbols: Vec<_> = symbols
868 .iter()
869 .filter(|s| matches!(s.kind, SymbolKind::Class))
870 .collect();
871
872 assert_eq!(class_symbols.len(), 1);
873 assert_eq!(class_symbols[0].symbol.as_deref(), Some("User"));
874 }
875
876 #[test]
877 fn test_parse_namespace() {
878 let source = r#"
879namespace MyApp.Models
880{
881 public class User { }
882}
883 "#;
884
885 let symbols = parse("test.cs", source).unwrap();
886
887 let namespace_symbols: Vec<_> = symbols
888 .iter()
889 .filter(|s| matches!(s.kind, SymbolKind::Namespace))
890 .collect();
891
892 assert!(namespace_symbols.len() >= 1);
893 }
894
895 #[test]
896 fn test_parse_file_scoped_namespace() {
897 let source = r#"
898namespace MyApp.Models;
899
900public class User { }
901 "#;
902
903 let symbols = parse("test.cs", source).unwrap();
904
905 let namespace_symbols: Vec<_> = symbols
906 .iter()
907 .filter(|s| matches!(s.kind, SymbolKind::Namespace))
908 .collect();
909
910 assert!(namespace_symbols.len() >= 1);
911 }
912
913 #[test]
914 fn test_parse_interface() {
915 let source = r#"
916public interface IRepository
917{
918 void Save();
919 void Delete();
920}
921 "#;
922
923 let symbols = parse("test.cs", source).unwrap();
924
925 let interface_symbols: Vec<_> = symbols
926 .iter()
927 .filter(|s| matches!(s.kind, SymbolKind::Interface))
928 .collect();
929
930 assert_eq!(interface_symbols.len(), 1);
931 assert_eq!(interface_symbols[0].symbol.as_deref(), Some("IRepository"));
932 }
933
934 #[test]
935 fn test_parse_struct() {
936 let source = r#"
937public struct Point
938{
939 public int X;
940 public int Y;
941}
942 "#;
943
944 let symbols = parse("test.cs", source).unwrap();
945
946 let struct_symbols: Vec<_> = symbols
947 .iter()
948 .filter(|s| matches!(s.kind, SymbolKind::Struct))
949 .collect();
950
951 assert_eq!(struct_symbols.len(), 1);
952 assert_eq!(struct_symbols[0].symbol.as_deref(), Some("Point"));
953 }
954
955 #[test]
956 fn test_parse_enum() {
957 let source = r#"
958public enum Status
959{
960 Active,
961 Inactive,
962 Pending
963}
964 "#;
965
966 let symbols = parse("test.cs", source).unwrap();
967
968 let enum_symbols: Vec<_> = symbols
969 .iter()
970 .filter(|s| matches!(s.kind, SymbolKind::Enum))
971 .collect();
972
973 assert_eq!(enum_symbols.len(), 1);
974 assert_eq!(enum_symbols[0].symbol.as_deref(), Some("Status"));
975 }
976
977 #[test]
978 fn test_parse_record() {
979 let source = r#"
980public record Person(string FirstName, string LastName);
981 "#;
982
983 let symbols = parse("test.cs", source).unwrap();
984
985 let record_symbols: Vec<_> = symbols
986 .iter()
987 .filter(|s| matches!(s.kind, SymbolKind::Type))
988 .filter(|s| s.symbol.as_deref() == Some("Person"))
989 .collect();
990
991 assert_eq!(record_symbols.len(), 1);
992 }
993
994 #[test]
995 fn test_parse_methods() {
996 let source = r#"
997public class Calculator
998{
999 public int Add(int a, int b)
1000 {
1001 return a + b;
1002 }
1003
1004 public int Subtract(int a, int b)
1005 {
1006 return a - b;
1007 }
1008}
1009 "#;
1010
1011 let symbols = parse("test.cs", source).unwrap();
1012
1013 let method_symbols: Vec<_> = symbols
1014 .iter()
1015 .filter(|s| matches!(s.kind, SymbolKind::Method))
1016 .collect();
1017
1018 assert_eq!(method_symbols.len(), 2);
1019 assert!(
1020 method_symbols
1021 .iter()
1022 .any(|s| s.symbol.as_deref() == Some("Add"))
1023 );
1024 assert!(
1025 method_symbols
1026 .iter()
1027 .any(|s| s.symbol.as_deref() == Some("Subtract"))
1028 );
1029
1030 for method in method_symbols {
1032 }
1034 }
1035
1036 #[test]
1037 fn test_parse_properties() {
1038 let source = r#"
1039public class User
1040{
1041 public string Name { get; set; }
1042 public int Age { get; set; }
1043 public string Email { get; init; }
1044}
1045 "#;
1046
1047 let symbols = parse("test.cs", source).unwrap();
1048
1049 let property_symbols: Vec<_> = symbols
1050 .iter()
1051 .filter(|s| matches!(s.kind, SymbolKind::Variable))
1052 .collect();
1053
1054 assert_eq!(property_symbols.len(), 3);
1055 assert!(
1056 property_symbols
1057 .iter()
1058 .any(|s| s.symbol.as_deref() == Some("Name"))
1059 );
1060 assert!(
1061 property_symbols
1062 .iter()
1063 .any(|s| s.symbol.as_deref() == Some("Age"))
1064 );
1065 assert!(
1066 property_symbols
1067 .iter()
1068 .any(|s| s.symbol.as_deref() == Some("Email"))
1069 );
1070 }
1071
1072 #[test]
1073 fn test_parse_delegate() {
1074 let source = r#"
1075public delegate void EventHandler(object sender, EventArgs e);
1076 "#;
1077
1078 let symbols = parse("test.cs", source).unwrap();
1079
1080 let delegate_symbols: Vec<_> = symbols
1081 .iter()
1082 .filter(|s| matches!(s.kind, SymbolKind::Type))
1083 .filter(|s| s.symbol.as_deref() == Some("EventHandler"))
1084 .collect();
1085
1086 assert_eq!(delegate_symbols.len(), 1);
1087 }
1088
1089 #[test]
1090 fn test_parse_mixed_symbols() {
1091 let source = r#"
1092namespace MyApp
1093{
1094 public interface IService
1095 {
1096 void Execute();
1097 }
1098
1099 public class Service : IService
1100 {
1101 public void Execute()
1102 {
1103 // Implementation
1104 }
1105 }
1106
1107 public enum Priority
1108 {
1109 Low, Medium, High
1110 }
1111}
1112 "#;
1113
1114 let symbols = parse("test.cs", source).unwrap();
1115
1116 assert!(symbols.len() >= 5);
1118
1119 let kinds: Vec<&SymbolKind> = symbols.iter().map(|s| &s.kind).collect();
1120 assert!(kinds.contains(&&SymbolKind::Namespace));
1121 assert!(kinds.contains(&&SymbolKind::Interface));
1122 assert!(kinds.contains(&&SymbolKind::Class));
1123 assert!(kinds.contains(&&SymbolKind::Enum));
1124 assert!(kinds.contains(&&SymbolKind::Method));
1125 }
1126
1127 #[test]
1128 fn test_local_variables_included() {
1129 let source = r#"
1130public class Calculator
1131{
1132 public int Multiplier { get; set; } = 2;
1133
1134 public int Compute(int input)
1135 {
1136 int localVar = input * Multiplier;
1137 var result = localVar + 10;
1138 return result;
1139 }
1140}
1141
1142public class Helper
1143{
1144 public static string Format()
1145 {
1146 string message = "Hello";
1147 var count = 5;
1148 return message;
1149 }
1150}
1151 "#;
1152
1153 let symbols = parse("test.cs", source).unwrap();
1154
1155 let variables: Vec<_> = symbols
1157 .iter()
1158 .filter(|s| matches!(s.kind, SymbolKind::Variable))
1159 .collect();
1160
1161 assert!(
1163 variables
1164 .iter()
1165 .any(|v| v.symbol.as_deref() == Some("localVar"))
1166 );
1167 assert!(
1168 variables
1169 .iter()
1170 .any(|v| v.symbol.as_deref() == Some("result"))
1171 );
1172 assert!(
1173 variables
1174 .iter()
1175 .any(|v| v.symbol.as_deref() == Some("message"))
1176 );
1177 assert!(
1178 variables
1179 .iter()
1180 .any(|v| v.symbol.as_deref() == Some("count"))
1181 );
1182
1183 assert!(
1185 variables
1186 .iter()
1187 .any(|v| v.symbol.as_deref() == Some("Multiplier"))
1188 );
1189
1190 let local_vars: Vec<_> = variables
1192 .iter()
1193 .filter(|v| {
1194 v.symbol.as_deref() == Some("localVar")
1195 || v.symbol.as_deref() == Some("result")
1196 || v.symbol.as_deref() == Some("message")
1197 || v.symbol.as_deref() == Some("count")
1198 })
1199 .collect();
1200
1201 for var in local_vars {
1202 }
1204
1205 let property = variables
1207 .iter()
1208 .find(|v| v.symbol.as_deref() == Some("Multiplier"))
1209 .unwrap();
1210 }
1212
1213 #[test]
1214 fn test_parse_events() {
1215 let source = r#"
1216public class Button
1217{
1218 public event EventHandler Click;
1219 public event Action Hover;
1220}
1221
1222public interface INotifier
1223{
1224 event EventHandler<string> Notify;
1225}
1226 "#;
1227
1228 let symbols = parse("test.cs", source).unwrap();
1229
1230 let event_symbols: Vec<_> = symbols
1231 .iter()
1232 .filter(|s| matches!(s.kind, SymbolKind::Event))
1233 .collect();
1234
1235 assert_eq!(event_symbols.len(), 3);
1236 assert!(
1237 event_symbols
1238 .iter()
1239 .any(|s| s.symbol.as_deref() == Some("Click"))
1240 );
1241 assert!(
1242 event_symbols
1243 .iter()
1244 .any(|s| s.symbol.as_deref() == Some("Hover"))
1245 );
1246 assert!(
1247 event_symbols
1248 .iter()
1249 .any(|s| s.symbol.as_deref() == Some("Notify"))
1250 );
1251
1252 let click_event = event_symbols
1254 .iter()
1255 .find(|s| s.symbol.as_deref() == Some("Click"))
1256 .unwrap();
1257 let notify_event = event_symbols
1260 .iter()
1261 .find(|s| s.symbol.as_deref() == Some("Notify"))
1262 .unwrap();
1263 }
1265
1266 #[test]
1267 fn test_parse_indexers() {
1268 let source = r#"
1269public class StringCollection
1270{
1271 private string[] items = new string[100];
1272
1273 public string this[int index]
1274 {
1275 get { return items[index]; }
1276 set { items[index] = value; }
1277 }
1278}
1279
1280public struct Matrix
1281{
1282 public int this[int row, int col]
1283 {
1284 get { return 0; }
1285 set { }
1286 }
1287}
1288 "#;
1289
1290 let symbols = parse("test.cs", source).unwrap();
1291
1292 let indexer_symbols: Vec<_> = symbols
1293 .iter()
1294 .filter(|s| matches!(s.kind, SymbolKind::Property))
1295 .filter(|s| s.symbol.as_deref() == Some("this[]"))
1296 .collect();
1297
1298 assert_eq!(indexer_symbols.len(), 2);
1299
1300 }
1303
1304 #[test]
1305 fn test_parse_attribute_class() {
1306 let source = r#"
1307using System;
1308
1309// Attribute with naming convention (ends with "Attribute")
1310public class TestAttribute : Attribute
1311{
1312 public string Name { get; set; }
1313 public TestAttribute(string name) { Name = name; }
1314}
1315
1316// Attribute without suffix but inherits from Attribute
1317public class Obsolete : Attribute
1318{
1319 public string Message { get; set; }
1320}
1321
1322// Not an attribute (regular class without "Attribute" suffix)
1323public class RegularClass
1324{
1325 public void DoSomething() { }
1326}
1327
1328// Attribute with only suffix (no explicit inheritance)
1329public class CustomAttribute
1330{
1331 public int Value { get; set; }
1332}
1333 "#;
1334
1335 let symbols = parse("test.cs", source).unwrap();
1336
1337 let attribute_symbols: Vec<_> = symbols
1338 .iter()
1339 .filter(|s| matches!(s.kind, SymbolKind::Attribute))
1340 .collect();
1341
1342 assert_eq!(attribute_symbols.len(), 3);
1344 assert!(
1345 attribute_symbols
1346 .iter()
1347 .any(|s| s.symbol.as_deref() == Some("TestAttribute"))
1348 );
1349 assert!(
1350 attribute_symbols
1351 .iter()
1352 .any(|s| s.symbol.as_deref() == Some("Obsolete"))
1353 );
1354 assert!(
1355 attribute_symbols
1356 .iter()
1357 .any(|s| s.symbol.as_deref() == Some("CustomAttribute"))
1358 );
1359
1360 assert!(
1362 !attribute_symbols
1363 .iter()
1364 .any(|s| s.symbol.as_deref() == Some("RegularClass"))
1365 );
1366 }
1367
1368 #[test]
1369 fn test_parse_attribute_uses() {
1370 let source = r#"
1371using System;
1372
1373public class TestAttribute : Attribute { }
1374public class ObsoleteAttribute : Attribute { }
1375
1376[Test]
1377public class TestClass
1378{
1379 [Test]
1380 public void TestMethod1()
1381 {
1382 // Test code
1383 }
1384
1385 [Test]
1386 [Obsolete]
1387 public void TestMethod2()
1388 {
1389 // Another test
1390 }
1391}
1392
1393[Obsolete]
1394public class LegacyClass
1395{
1396 [Test]
1397 public void OldTest()
1398 {
1399 // Legacy test
1400 }
1401}
1402 "#;
1403
1404 let symbols = parse("test.cs", source).unwrap();
1405
1406 let attribute_symbols: Vec<_> = symbols
1407 .iter()
1408 .filter(|s| matches!(s.kind, SymbolKind::Attribute))
1409 .collect();
1410
1411 assert!(attribute_symbols.len() >= 6);
1415
1416 let test_count = attribute_symbols
1418 .iter()
1419 .filter(|s| {
1420 let symbol = s.symbol.as_deref().unwrap_or("");
1421 symbol == "Test" || symbol == "TestAttribute"
1422 })
1423 .count();
1424
1425 let obsolete_count = attribute_symbols
1426 .iter()
1427 .filter(|s| {
1428 let symbol = s.symbol.as_deref().unwrap_or("");
1429 symbol == "Obsolete" || symbol == "ObsoleteAttribute"
1430 })
1431 .count();
1432
1433 assert!(test_count >= 4);
1435
1436 assert!(obsolete_count >= 3);
1438 }
1439
1440 #[test]
1441 fn test_extract_csharp_usings() {
1442 let source = r#"
1443 using System;
1444 using System.Collections.Generic;
1445 using System.Linq;
1446 using Microsoft.AspNetCore.Mvc;
1447
1448 namespace MyApp.Controllers
1449 {
1450 public class HomeController : Controller
1451 {
1452 public IActionResult Index()
1453 {
1454 return View();
1455 }
1456 }
1457 }
1458 "#;
1459
1460 use crate::parsers::DependencyExtractor;
1461
1462 let deps = CSharpDependencyExtractor::extract_dependencies(source).unwrap();
1463
1464 assert_eq!(deps.len(), 4, "Should extract 4 using directives");
1465 assert!(deps.iter().any(|d| d.imported_path == "System"));
1466 assert!(
1467 deps.iter()
1468 .any(|d| d.imported_path == "System.Collections.Generic")
1469 );
1470 assert!(deps.iter().any(|d| d.imported_path == "System.Linq"));
1471 assert!(
1472 deps.iter()
1473 .any(|d| d.imported_path == "Microsoft.AspNetCore.Mvc")
1474 );
1475
1476 for dep in &deps {
1477 assert!(
1478 matches!(dep.import_type, ImportType::Stdlib),
1479 "System and Microsoft namespaces should be classified as Stdlib"
1480 );
1481 }
1482 }
1483}
1484
1485use crate::models::ImportType;
1490use crate::parsers::{DependencyExtractor, ImportInfo};
1491
1492pub struct CSharpDependencyExtractor;
1494
1495impl DependencyExtractor for CSharpDependencyExtractor {
1496 fn extract_dependencies(source: &str) -> Result<Vec<ImportInfo>> {
1497 let mut parser = Parser::new();
1498 let language = tree_sitter_c_sharp::LANGUAGE;
1499
1500 parser
1501 .set_language(&language.into())
1502 .context("Failed to set C# language")?;
1503
1504 let tree = parser
1505 .parse(source, None)
1506 .context("Failed to parse C# source")?;
1507
1508 let root_node = tree.root_node();
1509
1510 let mut imports = Vec::new();
1511
1512 imports.extend(extract_csharp_usings(source, &root_node)?);
1514
1515 Ok(imports)
1516 }
1517}
1518
1519fn extract_csharp_usings(source: &str, root: &tree_sitter::Node) -> Result<Vec<ImportInfo>> {
1521 let language = tree_sitter_c_sharp::LANGUAGE;
1522
1523 let query_str = r#"
1524 (using_directive
1525 [
1526 (qualified_name) @using_path
1527 (identifier) @using_path
1528 ])
1529 "#;
1530
1531 let query =
1532 Query::new(&language.into(), query_str).context("Failed to create C# using query")?;
1533
1534 let mut cursor = QueryCursor::new();
1535 let mut matches = cursor.matches(&query, *root, source.as_bytes());
1536
1537 let mut imports = Vec::new();
1538
1539 while let Some(match_) = matches.next() {
1540 for capture in match_.captures {
1541 let capture_name: &str = &query.capture_names()[capture.index as usize];
1542 if capture_name == "using_path" {
1543 let path = capture
1544 .node
1545 .utf8_text(source.as_bytes())
1546 .unwrap_or("")
1547 .to_string();
1548 let import_type = classify_csharp_using(&path);
1549 let line_number = capture.node.start_position().row + 1;
1550
1551 imports.push(ImportInfo {
1552 imported_path: path,
1553 import_type,
1554 line_number,
1555 imported_symbols: None, });
1557 }
1558 }
1559 }
1560
1561 Ok(imports)
1562}
1563
1564fn classify_csharp_using(using_path: &str) -> ImportType {
1566 const CSHARP_STDLIB_NAMESPACES: &[&str] = &[
1568 "System",
1570 "System.Collections",
1571 "System.Collections.Generic",
1572 "System.Collections.Concurrent",
1573 "System.Collections.Immutable",
1574 "System.Collections.ObjectModel",
1575 "System.Collections.Specialized",
1576 "System.ComponentModel",
1577 "System.ComponentModel.DataAnnotations",
1578 "System.Configuration",
1579 "System.Data",
1580 "System.Data.Common",
1581 "System.Data.SqlClient",
1582 "System.Diagnostics",
1583 "System.Diagnostics.CodeAnalysis",
1584 "System.Diagnostics.Contracts",
1585 "System.Drawing",
1586 "System.Globalization",
1587 "System.IO",
1588 "System.IO.Compression",
1589 "System.IO.Pipes",
1590 "System.Linq",
1591 "System.Linq.Expressions",
1592 "System.Net",
1593 "System.Net.Http",
1594 "System.Net.Mail",
1595 "System.Net.Sockets",
1596 "System.Numerics",
1597 "System.Reflection",
1598 "System.Reflection.Emit",
1599 "System.Resources",
1600 "System.Runtime",
1601 "System.Runtime.CompilerServices",
1602 "System.Runtime.InteropServices",
1603 "System.Runtime.Serialization",
1604 "System.Security",
1605 "System.Security.Cryptography",
1606 "System.Security.Principal",
1607 "System.Text",
1608 "System.Text.Json",
1609 "System.Text.RegularExpressions",
1610 "System.Threading",
1611 "System.Threading.Tasks",
1612 "System.Timers",
1613 "System.Xml",
1614 "System.Xml.Linq",
1615 "System.Xml.Serialization",
1616 "Microsoft.AspNetCore",
1618 "Microsoft.AspNetCore.Builder",
1619 "Microsoft.AspNetCore.Hosting",
1620 "Microsoft.AspNetCore.Http",
1621 "Microsoft.AspNetCore.Mvc",
1622 "Microsoft.AspNetCore.Routing",
1623 "Microsoft.AspNetCore.Authentication",
1624 "Microsoft.AspNetCore.Authorization",
1625 "Microsoft.EntityFrameworkCore",
1627 "Microsoft.EntityFrameworkCore.Design",
1628 "Microsoft.EntityFrameworkCore.Migrations",
1629 "Microsoft.Extensions.Configuration",
1631 "Microsoft.Extensions.DependencyInjection",
1632 "Microsoft.Extensions.Hosting",
1633 "Microsoft.Extensions.Logging",
1634 "Microsoft.Extensions.Options",
1635 "Microsoft.Win32",
1637 "System.Windows",
1638 "System.Windows.Forms",
1639 "System.Windows.Controls",
1640 "System.Windows.Data",
1641 "System.Windows.Input",
1642 "System.Windows.Media",
1643 "System.Xaml",
1645 "System.Web",
1647 "System.Web.Mvc",
1648 "System.Web.Http",
1649 ];
1650
1651 for stdlib_ns in CSHARP_STDLIB_NAMESPACES {
1653 if using_path == *stdlib_ns || using_path.starts_with(&format!("{}.", stdlib_ns)) {
1654 return ImportType::Stdlib;
1655 }
1656 }
1657
1658 ImportType::Internal
1665}
1666
1667pub fn resolve_csharp_using_to_path(
1689 using_path: &str,
1690 _current_file_path: Option<&str>,
1691) -> Option<String> {
1692 let path_without_extension = using_path.replace('.', "/");
1697
1698 Some(format!("{}.cs", path_without_extension))
1700}
1701
1702#[cfg(test)]
1707mod resolution_tests {
1708 use super::*;
1709
1710 #[test]
1711 fn test_resolve_csharp_using_simple_namespace() {
1712 let result = resolve_csharp_using_to_path("MyApp.Models.User", None);
1713
1714 assert_eq!(result, Some("MyApp/Models/User.cs".to_string()));
1715 }
1716
1717 #[test]
1718 fn test_resolve_csharp_using_services() {
1719 let result = resolve_csharp_using_to_path("MyApp.Services.UserService", None);
1720
1721 assert_eq!(result, Some("MyApp/Services/UserService.cs".to_string()));
1722 }
1723
1724 #[test]
1725 fn test_resolve_csharp_using_single_level() {
1726 let result = resolve_csharp_using_to_path("MyApp", None);
1727
1728 assert_eq!(result, Some("MyApp.cs".to_string()));
1729 }
1730
1731 #[test]
1732 fn test_resolve_csharp_using_deep_namespace() {
1733 let result = resolve_csharp_using_to_path("MyApp.Core.Domain.Models.User", None);
1734
1735 assert_eq!(result, Some("MyApp/Core/Domain/Models/User.cs".to_string()));
1736 }
1737}