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 anyhow::{Context, Result};
19use streaming_iterator::StreamingIterator;
20use tree_sitter::{Parser, Query, QueryCursor};
21use crate::models::{Language, SearchResult, Span, SymbolKind};
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(source, &root_node, &language.into())?);
54
55    // Add file path to all symbols
56    for symbol in &mut symbols {
57        symbol.path = path.to_string();
58        symbol.lang = Language::CSharp;
59    }
60
61    Ok(symbols)
62}
63
64/// Extract namespace declarations
65fn extract_namespaces(
66    source: &str,
67    root: &tree_sitter::Node,
68    language: &tree_sitter::Language,
69) -> Result<Vec<SearchResult>> {
70    let query_str = r#"
71        (namespace_declaration
72            name: (_) @name) @namespace
73
74        (file_scoped_namespace_declaration
75            name: (_) @name) @namespace
76    "#;
77
78    let query = Query::new(language, query_str)
79        .context("Failed to create namespace query")?;
80
81    extract_symbols(source, root, &query, SymbolKind::Namespace, None)
82}
83
84/// Extract class declarations
85fn extract_classes(
86    source: &str,
87    root: &tree_sitter::Node,
88    language: &tree_sitter::Language,
89) -> Result<Vec<SearchResult>> {
90    let query_str = r#"
91        (class_declaration
92            name: (identifier) @name) @class
93    "#;
94
95    let query = Query::new(language, query_str)
96        .context("Failed to create class query")?;
97
98    extract_symbols(source, root, &query, SymbolKind::Class, None)
99}
100
101/// Extract interface declarations
102fn extract_interfaces(
103    source: &str,
104    root: &tree_sitter::Node,
105    language: &tree_sitter::Language,
106) -> Result<Vec<SearchResult>> {
107    let query_str = r#"
108        (interface_declaration
109            name: (identifier) @name) @interface
110    "#;
111
112    let query = Query::new(language, query_str)
113        .context("Failed to create interface query")?;
114
115    extract_symbols(source, root, &query, SymbolKind::Interface, None)
116}
117
118/// Extract struct declarations
119fn extract_structs(
120    source: &str,
121    root: &tree_sitter::Node,
122    language: &tree_sitter::Language,
123) -> Result<Vec<SearchResult>> {
124    let query_str = r#"
125        (struct_declaration
126            name: (identifier) @name) @struct
127    "#;
128
129    let query = Query::new(language, query_str)
130        .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)
147        .context("Failed to create enum query")?;
148
149    extract_symbols(source, root, &query, SymbolKind::Enum, None)
150}
151
152/// Extract record declarations (C# 9+)
153fn extract_records(
154    source: &str,
155    root: &tree_sitter::Node,
156    language: &tree_sitter::Language,
157) -> Result<Vec<SearchResult>> {
158    let query_str = r#"
159        (record_declaration
160            name: (identifier) @name) @record
161    "#;
162
163    let query = Query::new(language, query_str)
164        .context("Failed to create record query")?;
165
166    extract_symbols(source, root, &query, SymbolKind::Type, None)
167}
168
169/// Extract delegate declarations
170fn extract_delegates(
171    source: &str,
172    root: &tree_sitter::Node,
173    language: &tree_sitter::Language,
174) -> Result<Vec<SearchResult>> {
175    let query_str = r#"
176        (delegate_declaration
177            name: (identifier) @name) @delegate
178    "#;
179
180    let query = Query::new(language, query_str)
181        .context("Failed to create delegate query")?;
182
183    extract_symbols(source, root, &query, SymbolKind::Type, None)
184}
185
186/// Extract attributes: BOTH definitions and uses
187/// Definitions: class TestAttribute : Attribute { ... }
188/// Uses: [Test] public void TestMethod() { ... }
189fn extract_attributes(
190    source: &str,
191    root: &tree_sitter::Node,
192    language: &tree_sitter::Language,
193) -> Result<Vec<SearchResult>> {
194    let mut symbols = Vec::new();
195
196    // Part 1: Extract attribute class DEFINITIONS
197    let def_query_str = r#"
198        (class_declaration
199            name: (identifier) @name) @class
200    "#;
201
202    let def_query = Query::new(language, def_query_str)
203        .context("Failed to create attribute definition query")?;
204
205    let mut cursor = QueryCursor::new();
206    let mut matches = cursor.matches(&def_query, *root, source.as_bytes());
207
208    while let Some(match_) = matches.next() {
209        let mut name = None;
210        let mut full_node = None;
211
212        for capture in match_.captures {
213            let capture_name: &str = &def_query.capture_names()[capture.index as usize];
214            match capture_name {
215                "name" => {
216                    name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
217                }
218                "class" => {
219                    full_node = Some(capture.node);
220                }
221                _ => {}
222            }
223        }
224
225        // Extract classes that end with "Attribute" suffix (C# naming convention)
226        // or that have Attribute in their base_list
227        if let (Some(name), Some(node)) = (name, full_node) {
228            let mut is_attribute = name.ends_with("Attribute");
229
230            // Also check if the class inherits from Attribute
231            if !is_attribute {
232                // Check base_list for inheritance
233                for i in 0..node.child_count() {
234                    if let Some(child) = node.child(i) {
235                        if child.kind() == "base_list" {
236                            let base_text = child.utf8_text(source.as_bytes()).unwrap_or("");
237                            if base_text.contains("Attribute") {
238                                is_attribute = true;
239                                break;
240                            }
241                        }
242                    }
243                }
244            }
245
246            if is_attribute {
247                let span = node_to_span(&node);
248                let preview = extract_preview(source, &span);
249
250                symbols.push(SearchResult::new(
251                    String::new(),
252                    Language::CSharp,
253                    SymbolKind::Attribute,
254                    Some(name),
255                    span,
256                    None,
257                    preview,
258                ));
259            }
260        }
261    }
262
263    // Part 2: Extract attribute USES ([Test], [Obsolete], etc.)
264    let use_query_str = r#"
265        (attribute_list
266            (attribute
267                name: (_) @name)) @attr
268    "#;
269
270    let use_query = Query::new(language, use_query_str)
271        .context("Failed to create attribute use query")?;
272
273    symbols.extend(extract_symbols(source, root, &use_query, SymbolKind::Attribute, None)?);
274
275    Ok(symbols)
276}
277
278/// Extract method declarations from classes, structs, and interfaces
279fn extract_methods(
280    source: &str,
281    root: &tree_sitter::Node,
282    language: &tree_sitter::Language,
283) -> Result<Vec<SearchResult>> {
284    let query_str = r#"
285        (class_declaration
286            name: (identifier) @class_name
287            body: (declaration_list
288                (method_declaration
289                    name: (identifier) @method_name))) @class
290
291        (struct_declaration
292            name: (identifier) @struct_name
293            body: (declaration_list
294                (method_declaration
295                    name: (identifier) @method_name))) @struct
296
297        (interface_declaration
298            name: (identifier) @interface_name
299            body: (declaration_list
300                (method_declaration
301                    name: (identifier) @method_name))) @interface
302
303        (record_declaration
304            name: (identifier) @record_name
305            body: (declaration_list
306                (method_declaration
307                    name: (identifier) @method_name))) @record
308    "#;
309
310    let query = Query::new(language, query_str)
311        .context("Failed to create method query")?;
312
313    let mut cursor = QueryCursor::new();
314    let mut matches = cursor.matches(&query, *root, source.as_bytes());
315
316    let mut symbols = Vec::new();
317
318    while let Some(match_) = matches.next() {
319        let mut scope_name = None;
320        let mut scope_type = None;
321        let mut method_name = None;
322        let mut method_node = None;
323
324        for capture in match_.captures {
325            let capture_name: &str = &query.capture_names()[capture.index as usize];
326            match capture_name {
327                "class_name" => {
328                    scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
329                    scope_type = Some("class");
330                }
331                "struct_name" => {
332                    scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
333                    scope_type = Some("struct");
334                }
335                "interface_name" => {
336                    scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
337                    scope_type = Some("interface");
338                }
339                "record_name" => {
340                    scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
341                    scope_type = Some("record");
342                }
343                "method_name" => {
344                    method_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
345                    // Find the parent method_declaration node
346                    let mut current = capture.node;
347                    while let Some(parent) = current.parent() {
348                        if parent.kind() == "method_declaration" {
349                            method_node = Some(parent);
350                            break;
351                        }
352                        current = parent;
353                    }
354                }
355                _ => {}
356            }
357        }
358
359        if let (Some(scope_name), Some(scope_type), Some(method_name), Some(node)) =
360            (scope_name, scope_type, method_name, method_node) {
361            let scope = format!("{} {}", scope_type, scope_name);
362            let span = node_to_span(&node);
363            let preview = extract_preview(source, &span);
364
365            symbols.push(SearchResult::new(
366                String::new(),
367                Language::CSharp,
368                SymbolKind::Method,
369                Some(method_name),
370                span,
371                Some(scope),
372                preview,
373            ));
374        }
375    }
376
377    Ok(symbols)
378}
379
380/// Extract property declarations
381fn extract_properties(
382    source: &str,
383    root: &tree_sitter::Node,
384    language: &tree_sitter::Language,
385) -> Result<Vec<SearchResult>> {
386    let query_str = r#"
387        (class_declaration
388            name: (identifier) @class_name
389            body: (declaration_list
390                (property_declaration
391                    name: (identifier) @property_name))) @class
392
393        (struct_declaration
394            name: (identifier) @struct_name
395            body: (declaration_list
396                (property_declaration
397                    name: (identifier) @property_name))) @struct
398
399        (interface_declaration
400            name: (identifier) @interface_name
401            body: (declaration_list
402                (property_declaration
403                    name: (identifier) @property_name))) @interface
404
405        (record_declaration
406            name: (identifier) @record_name
407            body: (declaration_list
408                (property_declaration
409                    name: (identifier) @property_name))) @record
410    "#;
411
412    let query = Query::new(language, query_str)
413        .context("Failed to create property query")?;
414
415    let mut cursor = QueryCursor::new();
416    let mut matches = cursor.matches(&query, *root, source.as_bytes());
417
418    let mut symbols = Vec::new();
419
420    while let Some(match_) = matches.next() {
421        let mut scope_name = None;
422        let mut scope_type = None;
423        let mut property_name = None;
424        let mut property_node = None;
425
426        for capture in match_.captures {
427            let capture_name: &str = &query.capture_names()[capture.index as usize];
428            match capture_name {
429                "class_name" => {
430                    scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
431                    scope_type = Some("class");
432                }
433                "struct_name" => {
434                    scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
435                    scope_type = Some("struct");
436                }
437                "interface_name" => {
438                    scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
439                    scope_type = Some("interface");
440                }
441                "record_name" => {
442                    scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
443                    scope_type = Some("record");
444                }
445                "property_name" => {
446                    property_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
447                    // Find the parent property_declaration node
448                    let mut current = capture.node;
449                    while let Some(parent) = current.parent() {
450                        if parent.kind() == "property_declaration" {
451                            property_node = Some(parent);
452                            break;
453                        }
454                        current = parent;
455                    }
456                }
457                _ => {}
458            }
459        }
460
461        if let (Some(scope_name), Some(scope_type), Some(property_name), Some(node)) =
462            (scope_name, scope_type, property_name, property_node) {
463            let scope = format!("{} {}", scope_type, scope_name);
464            let span = node_to_span(&node);
465            let preview = extract_preview(source, &span);
466
467            symbols.push(SearchResult::new(
468                String::new(),
469                Language::CSharp,
470                SymbolKind::Variable,
471                Some(property_name),
472                span,
473                Some(scope),
474                preview,
475            ));
476        }
477    }
478
479    Ok(symbols)
480}
481
482/// Extract event declarations from classes, structs, and interfaces
483fn extract_events(
484    source: &str,
485    root: &tree_sitter::Node,
486    language: &tree_sitter::Language,
487) -> Result<Vec<SearchResult>> {
488    let query_str = r#"
489        (class_declaration
490            name: (identifier) @class_name
491            body: (declaration_list
492                (event_field_declaration
493                    (variable_declaration
494                        (variable_declarator
495                            (identifier) @event_name))))) @class
496
497        (struct_declaration
498            name: (identifier) @struct_name
499            body: (declaration_list
500                (event_field_declaration
501                    (variable_declaration
502                        (variable_declarator
503                            (identifier) @event_name))))) @struct
504
505        (interface_declaration
506            name: (identifier) @interface_name
507            body: (declaration_list
508                (event_field_declaration
509                    (variable_declaration
510                        (variable_declarator
511                            (identifier) @event_name))))) @interface
512    "#;
513
514    let query = Query::new(language, query_str)
515        .context("Failed to create event query")?;
516
517    let mut cursor = QueryCursor::new();
518    let mut matches = cursor.matches(&query, *root, source.as_bytes());
519
520    let mut symbols = Vec::new();
521
522    while let Some(match_) = matches.next() {
523        let mut scope_name = None;
524        let mut scope_type = None;
525        let mut event_name = None;
526        let mut event_node = None;
527
528        for capture in match_.captures {
529            let capture_name: &str = &query.capture_names()[capture.index as usize];
530            match capture_name {
531                "class_name" => {
532                    scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
533                    scope_type = Some("class");
534                }
535                "struct_name" => {
536                    scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
537                    scope_type = Some("struct");
538                }
539                "interface_name" => {
540                    scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
541                    scope_type = Some("interface");
542                }
543                "event_name" => {
544                    event_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
545                    // Find the parent event_field_declaration node
546                    let mut current = capture.node;
547                    while let Some(parent) = current.parent() {
548                        if parent.kind() == "event_field_declaration" {
549                            event_node = Some(parent);
550                            break;
551                        }
552                        current = parent;
553                    }
554                }
555                _ => {}
556            }
557        }
558
559        if let (Some(scope_name), Some(scope_type), Some(event_name), Some(node)) =
560            (scope_name, scope_type, event_name, event_node) {
561            let scope = format!("{} {}", scope_type, scope_name);
562            let span = node_to_span(&node);
563            let preview = extract_preview(source, &span);
564
565            symbols.push(SearchResult::new(
566                String::new(),
567                Language::CSharp,
568                SymbolKind::Event,
569                Some(event_name),
570                span,
571                Some(scope),
572                preview,
573            ));
574        }
575    }
576
577    Ok(symbols)
578}
579
580/// Extract indexer declarations from classes and structs
581fn extract_indexers(
582    source: &str,
583    root: &tree_sitter::Node,
584    language: &tree_sitter::Language,
585) -> Result<Vec<SearchResult>> {
586    let query_str = r#"
587        (class_declaration
588            name: (identifier) @class_name
589            body: (declaration_list
590                (indexer_declaration) @indexer_name)) @class
591
592        (struct_declaration
593            name: (identifier) @struct_name
594            body: (declaration_list
595                (indexer_declaration) @indexer_name)) @struct
596    "#;
597
598    let query = Query::new(language, query_str)
599        .context("Failed to create indexer query")?;
600
601    let mut cursor = QueryCursor::new();
602    let mut matches = cursor.matches(&query, *root, source.as_bytes());
603
604    let mut symbols = Vec::new();
605
606    while let Some(match_) = matches.next() {
607        let mut scope_name = None;
608        let mut scope_type = None;
609        let mut indexer_node = None;
610
611        for capture in match_.captures {
612            let capture_name: &str = &query.capture_names()[capture.index as usize];
613            match capture_name {
614                "class_name" => {
615                    scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
616                    scope_type = Some("class");
617                }
618                "struct_name" => {
619                    scope_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
620                    scope_type = Some("struct");
621                }
622                "indexer_name" => {
623                    indexer_node = Some(capture.node);
624                }
625                _ => {}
626            }
627        }
628
629        if let (Some(scope_name), Some(scope_type), Some(node)) =
630            (scope_name, scope_type, indexer_node) {
631            let scope = format!("{} {}", scope_type, scope_name);
632            let span = node_to_span(&node);
633            let preview = extract_preview(source, &span);
634
635            // Use "this[]" as the indexer name (C# convention)
636            symbols.push(SearchResult::new(
637                String::new(),
638                Language::CSharp,
639                SymbolKind::Property,
640                Some("this[]".to_string()),
641                span,
642                Some(scope),
643                preview,
644            ));
645        }
646    }
647
648    Ok(symbols)
649}
650
651/// Extract local variable declarations inside methods
652fn extract_local_variables(
653    source: &str,
654    root: &tree_sitter::Node,
655    language: &tree_sitter::Language,
656) -> Result<Vec<SearchResult>> {
657    let query_str = r#"
658        (local_declaration_statement
659            (variable_declaration
660                (variable_declarator
661                    (identifier) @name))) @var
662    "#;
663
664    let query = Query::new(language, query_str)
665        .context("Failed to create local variable query")?;
666
667    extract_symbols(source, root, &query, SymbolKind::Variable, None)
668}
669
670/// Generic symbol extraction helper
671fn extract_symbols(
672    source: &str,
673    root: &tree_sitter::Node,
674    query: &Query,
675    kind: SymbolKind,
676    scope: Option<String>,
677) -> Result<Vec<SearchResult>> {
678    let mut cursor = QueryCursor::new();
679    let mut matches = cursor.matches(query, *root, source.as_bytes());
680
681    let mut symbols = Vec::new();
682
683    while let Some(match_) = matches.next() {
684        // Find the name capture and the full node
685        let mut name = None;
686        let mut full_node = None;
687
688        for capture in match_.captures {
689            let capture_name: &str = &query.capture_names()[capture.index as usize];
690            if capture_name == "name" {
691                name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
692            } else {
693                // Assume any other capture is the full node
694                full_node = Some(capture.node);
695            }
696        }
697
698        if let (Some(name), Some(node)) = (name, full_node) {
699            let span = node_to_span(&node);
700            let preview = extract_preview(source, &span);
701
702            symbols.push(SearchResult::new(
703                String::new(),
704                Language::CSharp,
705                kind.clone(),
706                Some(name),
707                span,
708                scope.clone(),
709                preview,
710            ));
711        }
712    }
713
714    Ok(symbols)
715}
716
717/// Convert a Tree-sitter node to a Span
718fn node_to_span(node: &tree_sitter::Node) -> Span {
719    let start = node.start_position();
720    let end = node.end_position();
721
722    Span::new(
723        start.row + 1,  // Convert 0-indexed to 1-indexed
724        start.column,
725        end.row + 1,
726        end.column,
727    )
728}
729
730/// Extract a preview (7 lines) around the symbol
731fn extract_preview(source: &str, span: &Span) -> String {
732    let lines: Vec<&str> = source.lines().collect();
733
734    // Extract 7 lines: the start line and 6 following lines
735    let start_idx = (span.start_line - 1) as usize; // Convert back to 0-indexed
736    let end_idx = (start_idx + 7).min(lines.len());
737
738    lines[start_idx..end_idx].join("\n")
739}
740
741#[cfg(test)]
742mod tests {
743    use super::*;
744
745    #[test]
746    fn test_parse_class() {
747        let source = r#"
748public class User
749{
750    private string name;
751    private int age;
752}
753        "#;
754
755        let symbols = parse("test.cs", source).unwrap();
756
757        let class_symbols: Vec<_> = symbols.iter()
758            .filter(|s| matches!(s.kind, SymbolKind::Class))
759            .collect();
760
761        assert_eq!(class_symbols.len(), 1);
762        assert_eq!(class_symbols[0].symbol.as_deref(), Some("User"));
763    }
764
765    #[test]
766    fn test_parse_namespace() {
767        let source = r#"
768namespace MyApp.Models
769{
770    public class User { }
771}
772        "#;
773
774        let symbols = parse("test.cs", source).unwrap();
775
776        let namespace_symbols: Vec<_> = symbols.iter()
777            .filter(|s| matches!(s.kind, SymbolKind::Namespace))
778            .collect();
779
780        assert!(namespace_symbols.len() >= 1);
781    }
782
783    #[test]
784    fn test_parse_file_scoped_namespace() {
785        let source = r#"
786namespace MyApp.Models;
787
788public class User { }
789        "#;
790
791        let symbols = parse("test.cs", source).unwrap();
792
793        let namespace_symbols: Vec<_> = symbols.iter()
794            .filter(|s| matches!(s.kind, SymbolKind::Namespace))
795            .collect();
796
797        assert!(namespace_symbols.len() >= 1);
798    }
799
800    #[test]
801    fn test_parse_interface() {
802        let source = r#"
803public interface IRepository
804{
805    void Save();
806    void Delete();
807}
808        "#;
809
810        let symbols = parse("test.cs", source).unwrap();
811
812        let interface_symbols: Vec<_> = symbols.iter()
813            .filter(|s| matches!(s.kind, SymbolKind::Interface))
814            .collect();
815
816        assert_eq!(interface_symbols.len(), 1);
817        assert_eq!(interface_symbols[0].symbol.as_deref(), Some("IRepository"));
818    }
819
820    #[test]
821    fn test_parse_struct() {
822        let source = r#"
823public struct Point
824{
825    public int X;
826    public int Y;
827}
828        "#;
829
830        let symbols = parse("test.cs", source).unwrap();
831
832        let struct_symbols: Vec<_> = symbols.iter()
833            .filter(|s| matches!(s.kind, SymbolKind::Struct))
834            .collect();
835
836        assert_eq!(struct_symbols.len(), 1);
837        assert_eq!(struct_symbols[0].symbol.as_deref(), Some("Point"));
838    }
839
840    #[test]
841    fn test_parse_enum() {
842        let source = r#"
843public enum Status
844{
845    Active,
846    Inactive,
847    Pending
848}
849        "#;
850
851        let symbols = parse("test.cs", source).unwrap();
852
853        let enum_symbols: Vec<_> = symbols.iter()
854            .filter(|s| matches!(s.kind, SymbolKind::Enum))
855            .collect();
856
857        assert_eq!(enum_symbols.len(), 1);
858        assert_eq!(enum_symbols[0].symbol.as_deref(), Some("Status"));
859    }
860
861    #[test]
862    fn test_parse_record() {
863        let source = r#"
864public record Person(string FirstName, string LastName);
865        "#;
866
867        let symbols = parse("test.cs", source).unwrap();
868
869        let record_symbols: Vec<_> = symbols.iter()
870            .filter(|s| matches!(s.kind, SymbolKind::Type))
871            .filter(|s| s.symbol.as_deref() == Some("Person"))
872            .collect();
873
874        assert_eq!(record_symbols.len(), 1);
875    }
876
877    #[test]
878    fn test_parse_methods() {
879        let source = r#"
880public class Calculator
881{
882    public int Add(int a, int b)
883    {
884        return a + b;
885    }
886
887    public int Subtract(int a, int b)
888    {
889        return a - b;
890    }
891}
892        "#;
893
894        let symbols = parse("test.cs", source).unwrap();
895
896        let method_symbols: Vec<_> = symbols.iter()
897            .filter(|s| matches!(s.kind, SymbolKind::Method))
898            .collect();
899
900        assert_eq!(method_symbols.len(), 2);
901        assert!(method_symbols.iter().any(|s| s.symbol.as_deref() == Some("Add")));
902        assert!(method_symbols.iter().any(|s| s.symbol.as_deref() == Some("Subtract")));
903
904        // Check scope
905        for method in method_symbols {
906            // Removed: scope field no longer exists: assert_eq!(method.scope.as_ref().unwrap(), "class Calculator");
907        }
908    }
909
910    #[test]
911    fn test_parse_properties() {
912        let source = r#"
913public class User
914{
915    public string Name { get; set; }
916    public int Age { get; set; }
917    public string Email { get; init; }
918}
919        "#;
920
921        let symbols = parse("test.cs", source).unwrap();
922
923        let property_symbols: Vec<_> = symbols.iter()
924            .filter(|s| matches!(s.kind, SymbolKind::Variable))
925            .collect();
926
927        assert_eq!(property_symbols.len(), 3);
928        assert!(property_symbols.iter().any(|s| s.symbol.as_deref() == Some("Name")));
929        assert!(property_symbols.iter().any(|s| s.symbol.as_deref() == Some("Age")));
930        assert!(property_symbols.iter().any(|s| s.symbol.as_deref() == Some("Email")));
931    }
932
933    #[test]
934    fn test_parse_delegate() {
935        let source = r#"
936public delegate void EventHandler(object sender, EventArgs e);
937        "#;
938
939        let symbols = parse("test.cs", source).unwrap();
940
941        let delegate_symbols: Vec<_> = symbols.iter()
942            .filter(|s| matches!(s.kind, SymbolKind::Type))
943            .filter(|s| s.symbol.as_deref() == Some("EventHandler"))
944            .collect();
945
946        assert_eq!(delegate_symbols.len(), 1);
947    }
948
949    #[test]
950    fn test_parse_mixed_symbols() {
951        let source = r#"
952namespace MyApp
953{
954    public interface IService
955    {
956        void Execute();
957    }
958
959    public class Service : IService
960    {
961        public void Execute()
962        {
963            // Implementation
964        }
965    }
966
967    public enum Priority
968    {
969        Low, Medium, High
970    }
971}
972        "#;
973
974        let symbols = parse("test.cs", source).unwrap();
975
976        // Should find: namespace, interface, class, enum, method
977        assert!(symbols.len() >= 5);
978
979        let kinds: Vec<&SymbolKind> = symbols.iter().map(|s| &s.kind).collect();
980        assert!(kinds.contains(&&SymbolKind::Namespace));
981        assert!(kinds.contains(&&SymbolKind::Interface));
982        assert!(kinds.contains(&&SymbolKind::Class));
983        assert!(kinds.contains(&&SymbolKind::Enum));
984        assert!(kinds.contains(&&SymbolKind::Method));
985    }
986
987    #[test]
988    fn test_local_variables_included() {
989        let source = r#"
990public class Calculator
991{
992    public int Multiplier { get; set; } = 2;
993
994    public int Compute(int input)
995    {
996        int localVar = input * Multiplier;
997        var result = localVar + 10;
998        return result;
999    }
1000}
1001
1002public class Helper
1003{
1004    public static string Format()
1005    {
1006        string message = "Hello";
1007        var count = 5;
1008        return message;
1009    }
1010}
1011        "#;
1012
1013        let symbols = parse("test.cs", source).unwrap();
1014
1015        // Filter to just variables
1016        let variables: Vec<_> = symbols.iter()
1017            .filter(|s| matches!(s.kind, SymbolKind::Variable))
1018            .collect();
1019
1020        // Check that local variables are captured
1021        assert!(variables.iter().any(|v| v.symbol.as_deref() == Some("localVar")));
1022        assert!(variables.iter().any(|v| v.symbol.as_deref() == Some("result")));
1023        assert!(variables.iter().any(|v| v.symbol.as_deref() == Some("message")));
1024        assert!(variables.iter().any(|v| v.symbol.as_deref() == Some("count")));
1025
1026        // Check that class property is also captured
1027        assert!(variables.iter().any(|v| v.symbol.as_deref() == Some("Multiplier")));
1028
1029        // Verify that local variables have no scope
1030        let local_vars: Vec<_> = variables.iter()
1031            .filter(|v| v.symbol.as_deref() == Some("localVar")
1032                     || v.symbol.as_deref() == Some("result")
1033                     || v.symbol.as_deref() == Some("message")
1034                     || v.symbol.as_deref() == Some("count"))
1035            .collect();
1036
1037        for var in local_vars {
1038            // Removed: scope field no longer exists: assert_eq!(var.scope, None);
1039        }
1040
1041        // Verify that class property has scope
1042        let property = variables.iter()
1043            .find(|v| v.symbol.as_deref() == Some("Multiplier"))
1044            .unwrap();
1045        // Removed: scope field no longer exists: assert_eq!(property.scope.as_ref().unwrap(), "class Calculator");
1046    }
1047
1048    #[test]
1049    fn test_parse_events() {
1050        let source = r#"
1051public class Button
1052{
1053    public event EventHandler Click;
1054    public event Action Hover;
1055}
1056
1057public interface INotifier
1058{
1059    event EventHandler<string> Notify;
1060}
1061        "#;
1062
1063        let symbols = parse("test.cs", source).unwrap();
1064
1065        let event_symbols: Vec<_> = symbols.iter()
1066            .filter(|s| matches!(s.kind, SymbolKind::Event))
1067            .collect();
1068
1069        assert_eq!(event_symbols.len(), 3);
1070        assert!(event_symbols.iter().any(|s| s.symbol.as_deref() == Some("Click")));
1071        assert!(event_symbols.iter().any(|s| s.symbol.as_deref() == Some("Hover")));
1072        assert!(event_symbols.iter().any(|s| s.symbol.as_deref() == Some("Notify")));
1073
1074        // Check scope
1075        let click_event = event_symbols.iter()
1076            .find(|s| s.symbol.as_deref() == Some("Click"))
1077            .unwrap();
1078        // Removed: scope field no longer exists: assert_eq!(click_event.scope.as_ref().unwrap(), "class Button");
1079
1080        let notify_event = event_symbols.iter()
1081            .find(|s| s.symbol.as_deref() == Some("Notify"))
1082            .unwrap();
1083        // Removed: scope field no longer exists: assert_eq!(notify_event.scope.as_ref().unwrap(), "interface INotifier");
1084    }
1085
1086    #[test]
1087    fn test_parse_indexers() {
1088        let source = r#"
1089public class StringCollection
1090{
1091    private string[] items = new string[100];
1092
1093    public string this[int index]
1094    {
1095        get { return items[index]; }
1096        set { items[index] = value; }
1097    }
1098}
1099
1100public struct Matrix
1101{
1102    public int this[int row, int col]
1103    {
1104        get { return 0; }
1105        set { }
1106    }
1107}
1108        "#;
1109
1110        let symbols = parse("test.cs", source).unwrap();
1111
1112        let indexer_symbols: Vec<_> = symbols.iter()
1113            .filter(|s| matches!(s.kind, SymbolKind::Property))
1114            .filter(|s| s.symbol.as_deref() == Some("this[]"))
1115            .collect();
1116
1117        assert_eq!(indexer_symbols.len(), 2);
1118
1119        // Note: scope field was removed from SearchResult for token optimization
1120        // Indexers are identified by SymbolKind::Property with symbol name "this[]"
1121    }
1122
1123    #[test]
1124    fn test_parse_attribute_class() {
1125        let source = r#"
1126using System;
1127
1128// Attribute with naming convention (ends with "Attribute")
1129public class TestAttribute : Attribute
1130{
1131    public string Name { get; set; }
1132    public TestAttribute(string name) { Name = name; }
1133}
1134
1135// Attribute without suffix but inherits from Attribute
1136public class Obsolete : Attribute
1137{
1138    public string Message { get; set; }
1139}
1140
1141// Not an attribute (regular class without "Attribute" suffix)
1142public class RegularClass
1143{
1144    public void DoSomething() { }
1145}
1146
1147// Attribute with only suffix (no explicit inheritance)
1148public class CustomAttribute
1149{
1150    public int Value { get; set; }
1151}
1152        "#;
1153
1154        let symbols = parse("test.cs", source).unwrap();
1155
1156        let attribute_symbols: Vec<_> = symbols.iter()
1157            .filter(|s| matches!(s.kind, SymbolKind::Attribute))
1158            .collect();
1159
1160        // Should find TestAttribute, Obsolete, and CustomAttribute
1161        assert_eq!(attribute_symbols.len(), 3);
1162        assert!(attribute_symbols.iter().any(|s| s.symbol.as_deref() == Some("TestAttribute")));
1163        assert!(attribute_symbols.iter().any(|s| s.symbol.as_deref() == Some("Obsolete")));
1164        assert!(attribute_symbols.iter().any(|s| s.symbol.as_deref() == Some("CustomAttribute")));
1165
1166        // Should NOT find RegularClass
1167        assert!(!attribute_symbols.iter().any(|s| s.symbol.as_deref() == Some("RegularClass")));
1168    }
1169
1170    #[test]
1171    fn test_parse_attribute_uses() {
1172        let source = r#"
1173using System;
1174
1175public class TestAttribute : Attribute { }
1176public class ObsoleteAttribute : Attribute { }
1177
1178[Test]
1179public class TestClass
1180{
1181    [Test]
1182    public void TestMethod1()
1183    {
1184        // Test code
1185    }
1186
1187    [Test]
1188    [Obsolete]
1189    public void TestMethod2()
1190    {
1191        // Another test
1192    }
1193}
1194
1195[Obsolete]
1196public class LegacyClass
1197{
1198    [Test]
1199    public void OldTest()
1200    {
1201        // Legacy test
1202    }
1203}
1204        "#;
1205
1206        let symbols = parse("test.cs", source).unwrap();
1207
1208        let attribute_symbols: Vec<_> = symbols.iter()
1209            .filter(|s| matches!(s.kind, SymbolKind::Attribute))
1210            .collect();
1211
1212        // Should find attribute class definitions (TestAttribute, ObsoleteAttribute)
1213        // AND attribute uses (Test appears 4 times, Obsolete appears 2 times)
1214        // Total expected: 2 definitions + 6 uses = 8
1215        assert!(attribute_symbols.len() >= 6);
1216
1217        // Count specific attribute uses
1218        let test_count = attribute_symbols.iter()
1219            .filter(|s| {
1220                let symbol = s.symbol.as_deref().unwrap_or("");
1221                symbol == "Test" || symbol == "TestAttribute"
1222            })
1223            .count();
1224
1225        let obsolete_count = attribute_symbols.iter()
1226            .filter(|s| {
1227                let symbol = s.symbol.as_deref().unwrap_or("");
1228                symbol == "Obsolete" || symbol == "ObsoleteAttribute"
1229            })
1230            .count();
1231
1232        // Should find Test/TestAttribute at least 4 times (1 definition + 4 uses)
1233        assert!(test_count >= 4);
1234
1235        // Should find Obsolete/ObsoleteAttribute at least 3 times (1 definition + 2 uses)
1236        assert!(obsolete_count >= 3);
1237    }
1238
1239    #[test]
1240    fn test_extract_csharp_usings() {
1241        let source = r#"
1242            using System;
1243            using System.Collections.Generic;
1244            using System.Linq;
1245            using Microsoft.AspNetCore.Mvc;
1246
1247            namespace MyApp.Controllers
1248            {
1249                public class HomeController : Controller
1250                {
1251                    public IActionResult Index()
1252                    {
1253                        return View();
1254                    }
1255                }
1256            }
1257        "#;
1258
1259        use crate::parsers::DependencyExtractor;
1260
1261        let deps = CSharpDependencyExtractor::extract_dependencies(source).unwrap();
1262
1263        assert_eq!(deps.len(), 4, "Should extract 4 using directives");
1264        assert!(deps.iter().any(|d| d.imported_path == "System"));
1265        assert!(deps.iter().any(|d| d.imported_path == "System.Collections.Generic"));
1266        assert!(deps.iter().any(|d| d.imported_path == "System.Linq"));
1267        assert!(deps.iter().any(|d| d.imported_path == "Microsoft.AspNetCore.Mvc"));
1268
1269        for dep in &deps {
1270            assert!(matches!(dep.import_type, ImportType::Stdlib),
1271                    "System and Microsoft namespaces should be classified as Stdlib");
1272        }
1273    }
1274}
1275
1276// ============================================================================
1277// Dependency Extraction
1278// ============================================================================
1279
1280use crate::models::ImportType;
1281use crate::parsers::{DependencyExtractor, ImportInfo};
1282
1283/// C# dependency extractor
1284pub struct CSharpDependencyExtractor;
1285
1286impl DependencyExtractor for CSharpDependencyExtractor {
1287    fn extract_dependencies(source: &str) -> Result<Vec<ImportInfo>> {
1288        let mut parser = Parser::new();
1289        let language = tree_sitter_c_sharp::LANGUAGE;
1290
1291        parser
1292            .set_language(&language.into())
1293            .context("Failed to set C# language")?;
1294
1295        let tree = parser
1296            .parse(source, None)
1297            .context("Failed to parse C# source")?;
1298
1299        let root_node = tree.root_node();
1300
1301        let mut imports = Vec::new();
1302
1303        // Extract using directives
1304        imports.extend(extract_csharp_usings(source, &root_node)?);
1305
1306        Ok(imports)
1307    }
1308}
1309
1310/// Extract C# using directives
1311fn extract_csharp_usings(
1312    source: &str,
1313    root: &tree_sitter::Node,
1314) -> Result<Vec<ImportInfo>> {
1315    let language = tree_sitter_c_sharp::LANGUAGE;
1316
1317    let query_str = r#"
1318        (using_directive
1319            [
1320                (qualified_name) @using_path
1321                (identifier) @using_path
1322            ])
1323    "#;
1324
1325    let query = Query::new(&language.into(), query_str)
1326        .context("Failed to create C# using query")?;
1327
1328    let mut cursor = QueryCursor::new();
1329    let mut matches = cursor.matches(&query, *root, source.as_bytes());
1330
1331    let mut imports = Vec::new();
1332
1333    while let Some(match_) = matches.next() {
1334        for capture in match_.captures {
1335            let capture_name: &str = &query.capture_names()[capture.index as usize];
1336            if capture_name == "using_path" {
1337                let path = capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string();
1338                let import_type = classify_csharp_using(&path);
1339                let line_number = capture.node.start_position().row + 1;
1340
1341                imports.push(ImportInfo {
1342                    imported_path: path,
1343                    import_type,
1344                    line_number,
1345                    imported_symbols: None, // C# imports entire namespace
1346                });
1347            }
1348        }
1349    }
1350
1351    Ok(imports)
1352}
1353
1354/// Classify a C# using directive as internal, external, or stdlib
1355fn classify_csharp_using(using_path: &str) -> ImportType {
1356    // C# standard library namespaces (Microsoft-provided)
1357    const CSHARP_STDLIB_NAMESPACES: &[&str] = &[
1358        // Core system namespaces
1359        "System", "System.Collections", "System.Collections.Generic", "System.Collections.Concurrent",
1360        "System.Collections.Immutable", "System.Collections.ObjectModel", "System.Collections.Specialized",
1361        "System.ComponentModel", "System.ComponentModel.DataAnnotations",
1362        "System.Configuration", "System.Data", "System.Data.Common", "System.Data.SqlClient",
1363        "System.Diagnostics", "System.Diagnostics.CodeAnalysis", "System.Diagnostics.Contracts",
1364        "System.Drawing", "System.Globalization", "System.IO", "System.IO.Compression",
1365        "System.IO.Pipes", "System.Linq", "System.Linq.Expressions", "System.Net",
1366        "System.Net.Http", "System.Net.Mail", "System.Net.Sockets", "System.Numerics",
1367        "System.Reflection", "System.Reflection.Emit", "System.Resources", "System.Runtime",
1368        "System.Runtime.CompilerServices", "System.Runtime.InteropServices", "System.Runtime.Serialization",
1369        "System.Security", "System.Security.Cryptography", "System.Security.Principal",
1370        "System.Text", "System.Text.Json", "System.Text.RegularExpressions", "System.Threading",
1371        "System.Threading.Tasks", "System.Timers", "System.Xml", "System.Xml.Linq",
1372        "System.Xml.Serialization",
1373
1374        // ASP.NET namespaces
1375        "Microsoft.AspNetCore", "Microsoft.AspNetCore.Builder", "Microsoft.AspNetCore.Hosting",
1376        "Microsoft.AspNetCore.Http", "Microsoft.AspNetCore.Mvc", "Microsoft.AspNetCore.Routing",
1377        "Microsoft.AspNetCore.Authentication", "Microsoft.AspNetCore.Authorization",
1378
1379        // Entity Framework
1380        "Microsoft.EntityFrameworkCore", "Microsoft.EntityFrameworkCore.Design",
1381        "Microsoft.EntityFrameworkCore.Migrations",
1382
1383        // Extensions
1384        "Microsoft.Extensions.Configuration", "Microsoft.Extensions.DependencyInjection",
1385        "Microsoft.Extensions.Hosting", "Microsoft.Extensions.Logging", "Microsoft.Extensions.Options",
1386
1387        // Windows-specific
1388        "Microsoft.Win32", "System.Windows", "System.Windows.Forms", "System.Windows.Controls",
1389        "System.Windows.Data", "System.Windows.Input", "System.Windows.Media",
1390
1391        // WPF
1392        "System.Xaml",
1393
1394        // Older frameworks
1395        "System.Web", "System.Web.Mvc", "System.Web.Http",
1396    ];
1397
1398    // Check if it's a standard library namespace or starts with one
1399    for stdlib_ns in CSHARP_STDLIB_NAMESPACES {
1400        if using_path == *stdlib_ns || using_path.starts_with(&format!("{}.", stdlib_ns)) {
1401            return ImportType::Stdlib;
1402        }
1403    }
1404
1405    // Internal: anything that doesn't match stdlib patterns
1406    // In C#, project namespaces are typically named after the project/company
1407    // External packages usually have their own top-level namespace (e.g., Newtonsoft.Json)
1408    // We'll classify non-System/Microsoft namespaces as internal by default
1409    // (This means third-party packages will initially appear as internal,
1410    // but will be filtered out at indexing time if they're not in the project)
1411    ImportType::Internal
1412}
1413
1414// ============================================================================
1415// Path Resolution
1416// ============================================================================
1417
1418/// Resolve a C# using directive to a file path
1419///
1420/// # Arguments
1421/// * `using_path` - The namespace from the using directive (e.g., "MyApp.Models.User")
1422/// * `current_file_path` - Path to the file containing the using directive (unused for C#)
1423///
1424/// # Returns
1425/// * `Some(path)` if the namespace can be resolved to a file
1426/// * `None` if resolution fails
1427///
1428/// # Notes
1429/// C# namespace-to-file resolution follows these common patterns:
1430/// - `MyApp.Models.User` → `MyApp/Models/User.cs`
1431/// - `MyApp.Services.UserService` → `MyApp/Services/UserService.cs`
1432///
1433/// This resolver tries to convert namespace paths to file paths based on
1434/// C# naming conventions where namespace structure matches directory structure.
1435pub fn resolve_csharp_using_to_path(
1436    using_path: &str,
1437    _current_file_path: Option<&str>,
1438) -> Option<String> {
1439    // C# namespaces typically map to directory structure
1440    // Example: MyApp.Models.User → MyApp/Models/User.cs
1441
1442    // Convert namespace separators to path separators
1443    let path_without_extension = using_path.replace('.', "/");
1444
1445    // Try with .cs extension (most common)
1446    Some(format!("{}.cs", path_without_extension))
1447}
1448
1449// ============================================================================
1450// Tests for Path Resolution
1451// ============================================================================
1452
1453#[cfg(test)]
1454mod resolution_tests {
1455    use super::*;
1456
1457    #[test]
1458    fn test_resolve_csharp_using_simple_namespace() {
1459        let result = resolve_csharp_using_to_path(
1460            "MyApp.Models.User",
1461            None,
1462        );
1463
1464        assert_eq!(result, Some("MyApp/Models/User.cs".to_string()));
1465    }
1466
1467    #[test]
1468    fn test_resolve_csharp_using_services() {
1469        let result = resolve_csharp_using_to_path(
1470            "MyApp.Services.UserService",
1471            None,
1472        );
1473
1474        assert_eq!(result, Some("MyApp/Services/UserService.cs".to_string()));
1475    }
1476
1477    #[test]
1478    fn test_resolve_csharp_using_single_level() {
1479        let result = resolve_csharp_using_to_path(
1480            "MyApp",
1481            None,
1482        );
1483
1484        assert_eq!(result, Some("MyApp.cs".to_string()));
1485    }
1486
1487    #[test]
1488    fn test_resolve_csharp_using_deep_namespace() {
1489        let result = resolve_csharp_using_to_path(
1490            "MyApp.Core.Domain.Models.User",
1491            None,
1492        );
1493
1494        assert_eq!(result, Some("MyApp/Core/Domain/Models/User.cs".to_string()));
1495    }
1496}