Skip to main content

reflex/parsers/
csharp.rs

1//! C# language parser using Tree-sitter
2//!
3//! Extracts symbols from C# source code:
4//! - Classes (regular, abstract, sealed, partial, static)
5//! - Interfaces
6//! - Structs
7//! - Enums
8//! - Delegates
9//! - Records (C# 9+)
10//! - Methods (with class scope, visibility)
11//! - Properties (class/struct/record members)
12//! - Events (class/struct/interface members)
13//! - Indexers (this[] accessor)
14//! - Local variables (inside methods)
15//! - Namespaces
16//! - Constructors
17
18use crate::models::{Language, SearchResult, Span, SymbolKind};
19use anyhow::{Context, Result};
20use streaming_iterator::StreamingIterator;
21use tree_sitter::{Parser, Query, QueryCursor};
22
23/// Parse C# source code and extract symbols
24pub 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    // Extract different types of symbols using Tree-sitter queries
41    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    // Add file path to all symbols
60    for symbol in &mut symbols {
61        symbol.path = path.to_string();
62        symbol.lang = Language::CSharp;
63    }
64
65    Ok(symbols)
66}
67
68/// Extract namespace declarations
69fn 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
87/// Extract class declarations
88fn 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
103/// Extract interface declarations
104fn 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
119/// Extract struct declarations
120fn 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
135/// Extract enum declarations
136fn 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
151/// Extract record declarations (C# 9+)
152fn 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
167/// Extract delegate declarations
168fn 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
183/// Extract attributes: BOTH definitions and uses
184/// Definitions: class TestAttribute : Attribute { ... }
185/// Uses: [Test] public void TestMethod() { ... }
186fn 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    // Part 1: Extract attribute class DEFINITIONS
194    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        // Extract classes that end with "Attribute" suffix (C# naming convention)
229        // or that have Attribute in their base_list
230        if let (Some(name), Some(node)) = (name, full_node) {
231            let mut is_attribute = name.ends_with("Attribute");
232
233            // Also check if the class inherits from Attribute
234            if !is_attribute {
235                // Check base_list for inheritance
236                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    // Part 2: Extract attribute USES ([Test], [Obsolete], etc.)
267    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
287/// Extract method declarations from classes, structs, and interfaces
288fn 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                    // Find the parent method_declaration node
384                    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
419/// Extract property declarations
420fn 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                    // Find the parent property_declaration node
516                    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
551/// Extract event declarations from classes, structs, and interfaces
552fn 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                    // Find the parent event_field_declaration node
638                    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
673/// Extract indexer declarations from classes and structs
674fn 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            // Use "this[]" as the indexer name (C# convention)
741            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
756/// Extract local variable declarations inside methods
757fn 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
774/// Generic symbol extraction helper
775fn 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        // Find the name capture and the full node
789        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                // Assume any other capture is the full node
804                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
827/// Convert a Tree-sitter node to a Span
828fn 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, // Convert 0-indexed to 1-indexed
834        start.column,
835        end.row + 1,
836        end.column,
837    )
838}
839
840/// Extract a preview (7 lines) around the symbol
841fn extract_preview(source: &str, span: &Span) -> String {
842    let lines: Vec<&str> = source.lines().collect();
843
844    // Extract 7 lines: the start line and 6 following lines
845    let start_idx = (span.start_line - 1) as usize; // Convert back to 0-indexed
846    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        // Check scope
1031        for method in method_symbols {
1032            // Removed: scope field no longer exists: assert_eq!(method.scope.as_ref().unwrap(), "class Calculator");
1033        }
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        // Should find: namespace, interface, class, enum, method
1117        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        // Filter to just variables
1156        let variables: Vec<_> = symbols
1157            .iter()
1158            .filter(|s| matches!(s.kind, SymbolKind::Variable))
1159            .collect();
1160
1161        // Check that local variables are captured
1162        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        // Check that class property is also captured
1184        assert!(
1185            variables
1186                .iter()
1187                .any(|v| v.symbol.as_deref() == Some("Multiplier"))
1188        );
1189
1190        // Verify that local variables have no scope
1191        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            // Removed: scope field no longer exists: assert_eq!(var.scope, None);
1203        }
1204
1205        // Verify that class property has scope
1206        let property = variables
1207            .iter()
1208            .find(|v| v.symbol.as_deref() == Some("Multiplier"))
1209            .unwrap();
1210        // Removed: scope field no longer exists: assert_eq!(property.scope.as_ref().unwrap(), "class Calculator");
1211    }
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        // Check scope
1253        let click_event = event_symbols
1254            .iter()
1255            .find(|s| s.symbol.as_deref() == Some("Click"))
1256            .unwrap();
1257        // Removed: scope field no longer exists: assert_eq!(click_event.scope.as_ref().unwrap(), "class Button");
1258
1259        let notify_event = event_symbols
1260            .iter()
1261            .find(|s| s.symbol.as_deref() == Some("Notify"))
1262            .unwrap();
1263        // Removed: scope field no longer exists: assert_eq!(notify_event.scope.as_ref().unwrap(), "interface INotifier");
1264    }
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        // Note: scope field was removed from SearchResult for token optimization
1301        // Indexers are identified by SymbolKind::Property with symbol name "this[]"
1302    }
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        // Should find TestAttribute, Obsolete, and CustomAttribute
1343        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        // Should NOT find RegularClass
1361        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        // Should find attribute class definitions (TestAttribute, ObsoleteAttribute)
1412        // AND attribute uses (Test appears 4 times, Obsolete appears 2 times)
1413        // Total expected: 2 definitions + 6 uses = 8
1414        assert!(attribute_symbols.len() >= 6);
1415
1416        // Count specific attribute uses
1417        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        // Should find Test/TestAttribute at least 4 times (1 definition + 4 uses)
1434        assert!(test_count >= 4);
1435
1436        // Should find Obsolete/ObsoleteAttribute at least 3 times (1 definition + 2 uses)
1437        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
1485// ============================================================================
1486// Dependency Extraction
1487// ============================================================================
1488
1489use crate::models::ImportType;
1490use crate::parsers::{DependencyExtractor, ImportInfo};
1491
1492/// C# dependency extractor
1493pub 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        // Extract using directives
1513        imports.extend(extract_csharp_usings(source, &root_node)?);
1514
1515        Ok(imports)
1516    }
1517}
1518
1519/// Extract C# using directives
1520fn 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, // C# imports entire namespace
1556                });
1557            }
1558        }
1559    }
1560
1561    Ok(imports)
1562}
1563
1564/// Classify a C# using directive as internal, external, or stdlib
1565fn classify_csharp_using(using_path: &str) -> ImportType {
1566    // C# standard library namespaces (Microsoft-provided)
1567    const CSHARP_STDLIB_NAMESPACES: &[&str] = &[
1568        // Core system namespaces
1569        "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        // ASP.NET namespaces
1617        "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        // Entity Framework
1626        "Microsoft.EntityFrameworkCore",
1627        "Microsoft.EntityFrameworkCore.Design",
1628        "Microsoft.EntityFrameworkCore.Migrations",
1629        // Extensions
1630        "Microsoft.Extensions.Configuration",
1631        "Microsoft.Extensions.DependencyInjection",
1632        "Microsoft.Extensions.Hosting",
1633        "Microsoft.Extensions.Logging",
1634        "Microsoft.Extensions.Options",
1635        // Windows-specific
1636        "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        // WPF
1644        "System.Xaml",
1645        // Older frameworks
1646        "System.Web",
1647        "System.Web.Mvc",
1648        "System.Web.Http",
1649    ];
1650
1651    // Check if it's a standard library namespace or starts with one
1652    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    // Internal: anything that doesn't match stdlib patterns
1659    // In C#, project namespaces are typically named after the project/company
1660    // External packages usually have their own top-level namespace (e.g., Newtonsoft.Json)
1661    // We'll classify non-System/Microsoft namespaces as internal by default
1662    // (This means third-party packages will initially appear as internal,
1663    // but will be filtered out at indexing time if they're not in the project)
1664    ImportType::Internal
1665}
1666
1667// ============================================================================
1668// Path Resolution
1669// ============================================================================
1670
1671/// Resolve a C# using directive to a file path
1672///
1673/// # Arguments
1674/// * `using_path` - The namespace from the using directive (e.g., "MyApp.Models.User")
1675/// * `current_file_path` - Path to the file containing the using directive (unused for C#)
1676///
1677/// # Returns
1678/// * `Some(path)` if the namespace can be resolved to a file
1679/// * `None` if resolution fails
1680///
1681/// # Notes
1682/// C# namespace-to-file resolution follows these common patterns:
1683/// - `MyApp.Models.User` → `MyApp/Models/User.cs`
1684/// - `MyApp.Services.UserService` → `MyApp/Services/UserService.cs`
1685///
1686/// This resolver tries to convert namespace paths to file paths based on
1687/// C# naming conventions where namespace structure matches directory structure.
1688pub fn resolve_csharp_using_to_path(
1689    using_path: &str,
1690    _current_file_path: Option<&str>,
1691) -> Option<String> {
1692    // C# namespaces typically map to directory structure
1693    // Example: MyApp.Models.User → MyApp/Models/User.cs
1694
1695    // Convert namespace separators to path separators
1696    let path_without_extension = using_path.replace('.', "/");
1697
1698    // Try with .cs extension (most common)
1699    Some(format!("{}.cs", path_without_extension))
1700}
1701
1702// ============================================================================
1703// Tests for Path Resolution
1704// ============================================================================
1705
1706#[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}