code_analyze_core/languages/
csharp.rs1use tree_sitter::Node;
5
6pub const ELEMENT_QUERY: &str = r"
9(method_declaration name: (identifier) @method_name) @function
10(constructor_declaration name: (identifier) @ctor_name) @function
11(class_declaration name: (identifier) @class_name) @class
12(interface_declaration name: (identifier) @interface_name) @class
13(record_declaration name: (identifier) @record_name) @class
14(struct_declaration name: (identifier) @struct_name) @class
15(enum_declaration name: (identifier) @enum_name) @class
16";
17
18pub const CALL_QUERY: &str = r"
20(invocation_expression
21 function: (member_access_expression name: (identifier) @call))
22(invocation_expression
23 function: (identifier) @call)
24";
25
26pub const REFERENCE_QUERY: &str = r"
28(base_list (identifier) @type_ref)
29(base_list (generic_name (identifier) @type_ref))
30(type_argument_list (identifier) @type_ref)
31(type_parameter_list (type_parameter (identifier) @type_ref))
32";
33
34pub const IMPORT_QUERY: &str = r"
41(using_directive) @import_path
42";
43
44pub const DEFUSE_QUERY: &str = r"
46(variable_declarator name: (identifier) @write.var)
47(assignment_expression left: (identifier) @write.assign)
48(identifier) @read.usage
49";
50
51#[must_use]
56pub fn extract_inheritance(node: &Node, source: &str) -> Vec<String> {
57 let mut bases = Vec::new();
58
59 for i in 0..node.child_count() {
61 if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX))
62 && child.kind() == "base_list"
63 {
64 bases.extend(extract_base_list(&child, source));
65 break;
66 }
67 }
68
69 bases
70}
71
72fn extract_base_list(node: &Node, source: &str) -> Vec<String> {
74 let mut bases = Vec::new();
75
76 for i in 0..node.named_child_count() {
77 if let Some(child) = node.named_child(u32::try_from(i).unwrap_or(u32::MAX)) {
78 match child.kind() {
79 "identifier" => {
80 let end = child.end_byte();
81 if end <= source.len() {
82 bases.push(source[child.start_byte()..end].to_string());
83 }
84 }
85 "generic_name" => {
86 if let Some(id) = child.named_child(0)
88 && id.kind() == "identifier"
89 {
90 let end = id.end_byte();
91 if end <= source.len() {
92 bases.push(source[id.start_byte()..end].to_string());
93 }
94 }
95 }
96 _ => {}
97 }
98 }
99 }
100
101 bases
102}
103
104#[must_use]
112pub fn find_method_for_receiver(
113 node: &Node,
114 source: &str,
115 _depth: Option<usize>,
116) -> Option<String> {
117 if node.kind() != "method_declaration" && node.kind() != "constructor_declaration" {
118 return None;
119 }
120
121 let mut current = *node;
123 let mut in_type_body = false;
124 while let Some(parent) = current.parent() {
125 match parent.kind() {
126 "class_declaration"
127 | "interface_declaration"
128 | "record_declaration"
129 | "struct_declaration"
130 | "enum_declaration" => {
131 in_type_body = true;
132 break;
133 }
134 _ => {
135 current = parent;
136 }
137 }
138 }
139
140 if !in_type_body {
141 return None;
142 }
143
144 node.child_by_field_name("name").and_then(|n| {
145 let end = n.end_byte();
146 if end <= source.len() {
147 Some(source[n.start_byte()..end].to_string())
148 } else {
149 None
150 }
151 })
152}
153
154#[cfg(all(test, feature = "lang-csharp"))]
155mod tests {
156 use super::*;
157 use crate::DefUseKind;
158 use crate::parser::SemanticExtractor;
159 use tree_sitter::Parser;
160
161 fn parse_csharp(src: &str) -> tree_sitter::Tree {
162 let mut parser = Parser::new();
163 parser
164 .set_language(&tree_sitter_c_sharp::LANGUAGE.into())
165 .expect("Error loading C# language");
166 parser.parse(src, None).expect("Failed to parse C#")
167 }
168
169 #[test]
170 fn test_csharp_method_in_class() {
171 let src = "class Foo { void Bar() { Baz(); } void Baz() {} }";
173 let tree = parse_csharp(src);
174 let root = tree.root_node();
175
176 let mut methods: Vec<String> = Vec::new();
179 let mut stack = vec![root];
180 while let Some(node) = stack.pop() {
181 if node.kind() == "method_declaration" {
182 if let Some(name_node) = node.child_by_field_name("name") {
183 methods.push(src[name_node.start_byte()..name_node.end_byte()].to_string());
184 }
185 }
186 for i in 0..node.child_count() {
187 if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX)) {
188 stack.push(child);
189 }
190 }
191 }
192 methods.sort();
193
194 assert_eq!(methods, vec!["Bar", "Baz"]);
196 }
197
198 #[test]
199 fn test_csharp_constructor() {
200 let src = "class Foo { public Foo() {} }";
202 let tree = parse_csharp(src);
203 let root = tree.root_node();
204
205 let mut ctors: Vec<String> = Vec::new();
207 let mut stack = vec![root];
208 while let Some(node) = stack.pop() {
209 if node.kind() == "constructor_declaration" {
210 if let Some(name_node) = node.child_by_field_name("name") {
211 ctors.push(src[name_node.start_byte()..name_node.end_byte()].to_string());
212 }
213 }
214 for i in 0..node.child_count() {
215 if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX)) {
216 stack.push(child);
217 }
218 }
219 }
220
221 assert_eq!(ctors, vec!["Foo"]);
223 }
224
225 #[test]
226 fn test_csharp_interface() {
227 let src = "interface IBar { void Do(); }";
229 let tree = parse_csharp(src);
230 let root = tree.root_node();
231
232 let mut interfaces: Vec<String> = Vec::new();
234 let mut stack = vec![root];
235 while let Some(node) = stack.pop() {
236 if node.kind() == "interface_declaration" {
237 if let Some(name_node) = node.child_by_field_name("name") {
238 interfaces.push(src[name_node.start_byte()..name_node.end_byte()].to_string());
239 }
240 }
241 for i in 0..node.child_count() {
242 if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX)) {
243 stack.push(child);
244 }
245 }
246 }
247
248 assert_eq!(interfaces, vec!["IBar"]);
250 }
251
252 #[test]
253 fn test_csharp_using_directive() {
254 let src = "using System;";
256 let tree = parse_csharp(src);
257 let root = tree.root_node();
258
259 let mut imports: Vec<String> = Vec::new();
261 let mut stack = vec![root];
262 while let Some(node) = stack.pop() {
263 if node.kind() == "using_directive" {
264 imports.push(src[node.start_byte()..node.end_byte()].to_string());
265 }
266 for i in 0..node.child_count() {
267 if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX)) {
268 stack.push(child);
269 }
270 }
271 }
272
273 assert_eq!(imports, vec!["using System;"]);
275 }
276
277 #[test]
278 fn test_csharp_async_method() {
279 let src = "class C { async Task Foo() { await Bar(); } Task Bar() { return Task.CompletedTask; } }";
281 let tree = parse_csharp(src);
282 let root = tree.root_node();
283
284 let mut methods: Vec<String> = Vec::new();
286 let mut stack = vec![root];
287 while let Some(node) = stack.pop() {
288 if node.kind() == "method_declaration" {
289 if let Some(name_node) = node.child_by_field_name("name") {
290 methods.push(src[name_node.start_byte()..name_node.end_byte()].to_string());
291 }
292 }
293 for i in 0..node.child_count() {
294 if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX)) {
295 stack.push(child);
296 }
297 }
298 }
299
300 assert!(methods.contains(&"Foo".to_string()));
302 }
303
304 #[test]
305 fn test_csharp_generic_class() {
306 let src = "class Generic<T> { T value; }";
308 let tree = parse_csharp(src);
309 let root = tree.root_node();
310
311 let mut classes: Vec<String> = Vec::new();
313 let mut stack = vec![root];
314 while let Some(node) = stack.pop() {
315 if node.kind() == "class_declaration" {
316 if let Some(name_node) = node.child_by_field_name("name") {
317 classes.push(src[name_node.start_byte()..name_node.end_byte()].to_string());
318 }
319 }
320 for i in 0..node.child_count() {
321 if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX)) {
322 stack.push(child);
323 }
324 }
325 }
326
327 assert_eq!(classes, vec!["Generic"]);
329 }
330
331 #[test]
332 fn test_csharp_inheritance_extraction() {
333 let src = "class Dog : Animal, ICanRun {}";
335 let tree = parse_csharp(src);
336 let root = tree.root_node();
337
338 let mut base_list_node: Option<Node> = None;
340 let mut stack = vec![root];
341 while let Some(node) = stack.pop() {
342 if node.kind() == "base_list" {
343 base_list_node = Some(node);
344 break;
345 }
346 for i in 0..node.child_count() {
347 if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX)) {
348 stack.push(child);
349 }
350 }
351 }
352
353 let mut class_node: Option<Node> = None;
355 let mut stack2 = vec![root];
356 while let Some(node) = stack2.pop() {
357 if node.kind() == "class_declaration" {
358 class_node = Some(node);
359 break;
360 }
361 for i in 0..node.child_count() {
362 if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX)) {
363 stack2.push(child);
364 }
365 }
366 }
367 let class = class_node.expect("class_declaration not found");
368 let _ = base_list_node; let bases = extract_inheritance(&class, src);
370
371 assert_eq!(bases, vec!["Animal", "ICanRun"]);
373 }
374
375 #[test]
376 fn test_csharp_find_method_for_receiver() {
377 let src = "class MyClass { void MyMethod() {} }";
379 let tree = parse_csharp(src);
380 let root = tree.root_node();
381
382 let mut method_node: Option<Node> = None;
384 let mut stack = vec![root];
385 while let Some(node) = stack.pop() {
386 if node.kind() == "method_declaration" {
387 method_node = Some(node);
388 break;
389 }
390 for i in 0..node.child_count() {
391 if let Some(child) = node.child(u32::try_from(i).unwrap_or(u32::MAX)) {
392 stack.push(child);
393 }
394 }
395 }
396
397 let method = method_node.expect("method_declaration not found");
398 let name = find_method_for_receiver(&method, src, None);
399
400 assert_eq!(name, Some("MyMethod".to_string()));
402 }
403
404 #[test]
405 fn test_defuse_query_write_site() {
406 let src = "class C { void M() { int b = 3; } }\n";
408 let sites =
409 SemanticExtractor::extract_def_use_for_file(src, "csharp", "b", "test.cs", None);
410 assert!(!sites.is_empty(), "defuse sites should not be empty");
411 let has_write = sites.iter().any(|s| matches!(s.kind, DefUseKind::Write));
412 assert!(has_write, "should contain a Write DefUseSite");
413 }
414}