1+ use tree_sitter:: { Parser , Node } ;
2+ use crate :: utils:: diff_parser:: Hunk ;
3+
4+ /// Represents a C# method in the code
5+ #[ derive( Debug , PartialEq ) ]
6+ pub struct CSharpMethod {
7+ /// Start line of the method (1-indexed)
8+ pub start_line : usize ,
9+ /// End line of the method (1-indexed)
10+ pub end_line : usize ,
11+ /// Line containing the method signature
12+ pub signature_line : usize ,
13+ /// Full method text
14+ pub text : String ,
15+ /// Whether this method contains changes
16+ pub has_changes : bool ,
17+ }
18+
19+ /// Represents a C# file in the code
20+ #[ derive( Debug ) ]
21+ pub struct CSharpFile {
22+ /// Methods in the file
23+ pub methods : Vec < CSharpMethod > ,
24+ /// Using statements in the file
25+ pub using_statements : Vec < ( usize , usize ) > , // (start_line, end_line)
26+ /// Class declarations in the file
27+ pub class_declarations : Vec < ( usize , usize ) > , // (start_line, end_line)
28+ /// Namespace declarations in the file
29+ pub namespace_declarations : Vec < ( usize , usize ) > , // (start_line, end_line)
30+ }
31+
32+ /// Parser for C# code that extracts method information
33+ pub struct CSharpParser {
34+ parser : Parser ,
35+ }
36+
37+ impl CSharpParser {
38+ /// Create a new C# parser
39+ pub fn new ( ) -> Self {
40+ let mut parser = Parser :: new ( ) ;
41+ parser. set_language ( tree_sitter_c_sharp:: language ( ) ) . expect ( "Error loading C# grammar" ) ;
42+ CSharpParser { parser }
43+ }
44+
45+ /// Parse C# code and extract method information
46+ ///
47+ /// # Arguments
48+ ///
49+ /// * `code` - The C# code to parse
50+ /// * `hunks` - The diff hunks to identify changed methods
51+ pub fn parse_file ( & mut self , code : & str , hunks : & [ Hunk ] ) -> CSharpFile {
52+ let tree = self . parser . parse ( code, None ) . expect ( "Failed to parse C# code" ) ;
53+ let root_node = tree. root_node ( ) ;
54+
55+ let mut file = CSharpFile {
56+ methods : Vec :: new ( ) ,
57+ using_statements : Vec :: new ( ) ,
58+ class_declarations : Vec :: new ( ) ,
59+ namespace_declarations : Vec :: new ( ) ,
60+ } ;
61+
62+ self . find_nodes ( root_node, code, & mut file) ;
63+
64+ // Mark methods that contain changes or have changes in their body
65+ for method in & mut file. methods {
66+ method. has_changes = self . method_contains_changes ( method, hunks) ;
67+ }
68+
69+ file
70+ }
71+
72+ /// Find all method declarations in the AST
73+ fn find_nodes ( & self , node : Node , code : & str , file : & mut CSharpFile ) {
74+ match node. kind ( ) {
75+ "method_declaration" => {
76+ let start_line = node. start_position ( ) . row + 1 ;
77+ let end_line = node. end_position ( ) . row + 1 ;
78+
79+ // Find the signature line by looking for the first child that's a method header
80+ let signature_line = node. child_by_field_name ( "header" )
81+ . map ( |n| n. start_position ( ) . row + 1 )
82+ . unwrap_or ( start_line) ;
83+
84+ let text = node. utf8_text ( code. as_bytes ( ) )
85+ . unwrap_or_default ( )
86+ . to_string ( ) ;
87+
88+ file. methods . push ( CSharpMethod {
89+ start_line,
90+ end_line,
91+ signature_line,
92+ text,
93+ has_changes : false ,
94+ } ) ;
95+ } ,
96+ "property_declaration" => {
97+ let start_line = node. start_position ( ) . row + 1 ;
98+ let end_line = node. end_position ( ) . row + 1 ;
99+ let signature_line = start_line;
100+
101+ // Check if this is an arrow expression property (=>)
102+ let is_arrow_expr = node. child_by_field_name ( "value" )
103+ . map ( |n| n. kind ( ) == "arrow_expression_clause" )
104+ . unwrap_or ( false ) ;
105+
106+ if is_arrow_expr {
107+ // For arrow expression properties, treat the whole thing as one method
108+ let text = node. utf8_text ( code. as_bytes ( ) )
109+ . unwrap_or_default ( )
110+ . to_string ( ) ;
111+
112+ file. methods . push ( CSharpMethod {
113+ start_line,
114+ end_line,
115+ signature_line,
116+ text,
117+ has_changes : false ,
118+ } ) ;
119+ } else {
120+ // For regular properties, first add the property declaration itself
121+ let text = node. utf8_text ( code. as_bytes ( ) )
122+ . unwrap_or_default ( )
123+ . to_string ( ) ;
124+
125+ file. methods . push ( CSharpMethod {
126+ start_line,
127+ end_line,
128+ signature_line,
129+ text,
130+ has_changes : false ,
131+ } ) ;
132+
133+ // Then look for accessors within the property
134+ let mut cursor: tree_sitter:: TreeCursor < ' _ > = node. walk ( ) ;
135+ for child in node. children ( & mut cursor) {
136+ if child. kind ( ) == "accessor_declaration" {
137+ let accessor_start = child. start_position ( ) . row + 1 ;
138+ let accessor_end = child. end_position ( ) . row + 1 ;
139+ let accessor_text = child. utf8_text ( code. as_bytes ( ) )
140+ . unwrap_or_default ( )
141+ . to_string ( ) ;
142+
143+ file. methods . push ( CSharpMethod {
144+ start_line : accessor_start,
145+ end_line : accessor_end,
146+ signature_line : accessor_start,
147+ text : accessor_text,
148+ has_changes : false ,
149+ } ) ;
150+ }
151+ }
152+ }
153+ } ,
154+ "using_directive" => {
155+ let start_line = node. start_position ( ) . row + 1 ;
156+ let end_line = node. end_position ( ) . row + 1 ;
157+ file. using_statements . push ( ( start_line, end_line) ) ;
158+ } ,
159+ "namespace_declaration" => {
160+ let start_line = node. start_position ( ) . row + 1 ;
161+ let end_line = node. end_position ( ) . row + 1 ;
162+ file. namespace_declarations . push ( ( start_line, end_line) ) ;
163+ } ,
164+ "class_declaration" => {
165+ let start_line = node. start_position ( ) . row + 1 ;
166+ let end_line = node. end_position ( ) . row + 1 ;
167+ file. class_declarations . push ( ( start_line, end_line) ) ;
168+ } ,
169+ _ => { }
170+ }
171+
172+ let mut cursor = node. walk ( ) ;
173+ for child in node. children ( & mut cursor) {
174+ self . find_nodes ( child, code, file) ;
175+ }
176+ }
177+
178+ /// Check if a method contains any changes from the diff hunks
179+ fn method_contains_changes ( & self , method : & CSharpMethod , hunks : & [ Hunk ] ) -> bool {
180+ for hunk in hunks {
181+ let mut current_line = hunk. new_start ;
182+
183+ // Check if any line in the hunk is within this method's body
184+ for line in & hunk. lines {
185+ if current_line >= method. start_line && current_line <= method. end_line {
186+ // If it's a change line (+ or -) within the method body, mark the method as changed
187+ if line. starts_with ( '+' ) || line. starts_with ( '-' ) {
188+ return true ;
189+ }
190+ }
191+
192+ // Only increment line count for non-deletion lines
193+ if !line. starts_with ( '-' ) {
194+ current_line += 1 ;
195+ }
196+ }
197+ }
198+ false
199+ }
200+
201+ /// Check if a node contains any changes from the diff hunks
202+ pub fn node_contains_changes ( & self , start_line : usize , end_line : usize , hunks : & [ Hunk ] ) -> bool {
203+ for hunk in hunks {
204+ let mut current_line = hunk. new_start ;
205+
206+ for line in & hunk. lines {
207+ if current_line >= start_line && current_line <= end_line {
208+ if line. starts_with ( '+' ) || line. starts_with ( '-' ) {
209+ return true ;
210+ }
211+ }
212+
213+ if !line. starts_with ( '-' ) {
214+ current_line += 1 ;
215+ }
216+ }
217+ }
218+ false
219+ }
220+ }
0 commit comments