use codegraph_parser_api::{
CallRelation, ClassEntity, FunctionEntity, ImplementationRelation, ImportRelation,
InheritanceRelation, Parameter, ParserConfig, TraitEntity,
};
use tree_sitter::Node;
pub struct CSharpVisitor<'a> {
pub source: &'a [u8],
#[allow(dead_code)]
pub config: ParserConfig,
pub functions: Vec<FunctionEntity>,
pub classes: Vec<ClassEntity>,
pub traits: Vec<TraitEntity>,
pub imports: Vec<ImportRelation>,
pub calls: Vec<CallRelation>,
pub inheritance: Vec<InheritanceRelation>,
pub implementations: Vec<ImplementationRelation>,
current_namespace: Option<String>,
current_class: Option<String>,
current_function: Option<String>,
}
impl<'a> CSharpVisitor<'a> {
pub fn new(source: &'a [u8], config: ParserConfig) -> Self {
Self {
source,
config,
functions: Vec::new(),
classes: Vec::new(),
traits: Vec::new(),
imports: Vec::new(),
calls: Vec::new(),
inheritance: Vec::new(),
implementations: Vec::new(),
current_namespace: None,
current_class: None,
current_function: None,
}
}
fn node_text(&self, node: Node) -> String {
node.utf8_text(self.source).unwrap_or("").to_string()
}
pub fn visit_node(&mut self, node: Node) {
let should_recurse = match node.kind() {
"using_directive" => {
self.visit_using(node);
false
}
"namespace_declaration" => {
self.visit_namespace(node);
false }
"file_scoped_namespace_declaration" => {
self.visit_file_scoped_namespace(node);
true }
"class_declaration" => {
self.visit_class(node);
false }
"interface_declaration" => {
self.visit_interface(node);
false }
"struct_declaration" => {
self.visit_struct(node);
false }
"enum_declaration" => {
self.visit_enum(node);
false }
"record_declaration" => {
self.visit_record(node);
false }
"method_declaration" | "constructor_declaration" => {
if self.current_class.is_none() {
self.visit_method(node);
}
false
}
"invocation_expression" | "object_creation_expression" => {
self.visit_call_expression(node);
true }
_ => true, };
if should_recurse {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.visit_node(child);
}
}
}
fn visit_using(&mut self, node: Node) {
let mut cursor = node.walk();
let mut imported = String::new();
for child in node.children(&mut cursor) {
match child.kind() {
"qualified_name" | "identifier" => {
imported = self.node_text(child);
}
_ => {}
}
}
if !imported.is_empty() {
let import = ImportRelation {
importer: self
.current_namespace
.clone()
.unwrap_or_else(|| "global".to_string()),
imported,
symbols: Vec::new(),
is_wildcard: false,
alias: None,
};
self.imports.push(import);
}
}
fn visit_namespace(&mut self, node: Node) {
if let Some(name_node) = node.child_by_field_name("name") {
self.current_namespace = Some(self.node_text(name_node));
}
if let Some(body) = node.child_by_field_name("body") {
self.visit_namespace_body(body);
}
}
fn visit_file_scoped_namespace(&mut self, node: Node) {
if let Some(name_node) = node.child_by_field_name("name") {
self.current_namespace = Some(self.node_text(name_node));
}
}
fn visit_namespace_body(&mut self, node: Node) {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
match child.kind() {
"class_declaration" => self.visit_class(child),
"interface_declaration" => self.visit_interface(child),
"struct_declaration" => self.visit_struct(child),
"enum_declaration" => self.visit_enum(child),
"record_declaration" => self.visit_record(child),
"namespace_declaration" => self.visit_namespace(child), _ => {}
}
}
}
fn visit_class(&mut self, node: Node) {
let name = node
.child_by_field_name("name")
.map(|n| self.node_text(n))
.unwrap_or_else(|| "Class".to_string());
let qualified_name = self.qualify_name(&name);
let modifiers = self.extract_modifiers(node);
let is_abstract = modifiers.contains(&"abstract".to_string());
let visibility = self.extract_visibility(&modifiers);
let doc_comment = self.extract_doc_comment(node);
let mut base_classes = Vec::new();
let mut implemented_traits = Vec::new();
self.extract_base_list(
node,
&qualified_name,
&mut base_classes,
&mut implemented_traits,
);
let class_entity = ClassEntity {
name: qualified_name.clone(),
visibility,
line_start: node.start_position().row + 1,
line_end: node.end_position().row + 1,
is_abstract,
is_interface: false,
base_classes,
implemented_traits,
methods: Vec::new(),
fields: Vec::new(),
doc_comment,
attributes: self.extract_attributes(node),
type_parameters: self.extract_type_parameters(node),
};
self.classes.push(class_entity);
let previous_class = self.current_class.take();
self.current_class = Some(qualified_name);
if let Some(body) = node.child_by_field_name("body") {
self.visit_class_body(body);
}
self.current_class = previous_class;
}
fn visit_class_body(&mut self, node: Node) {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
match child.kind() {
"method_declaration" | "constructor_declaration" => self.visit_method(child),
"property_declaration" => self.visit_property(child),
"class_declaration" => self.visit_class(child), "interface_declaration" => self.visit_interface(child), "struct_declaration" => self.visit_struct(child), "enum_declaration" => self.visit_enum(child), _ => {}
}
}
}
fn visit_interface(&mut self, node: Node) {
let name = node
.child_by_field_name("name")
.map(|n| self.node_text(n))
.unwrap_or_else(|| "Interface".to_string());
let qualified_name = self.qualify_name(&name);
let modifiers = self.extract_modifiers(node);
let visibility = self.extract_visibility(&modifiers);
let doc_comment = self.extract_doc_comment(node);
let mut parent_traits = Vec::new();
let mut dummy_base_classes = Vec::new();
self.extract_base_list(
node,
&qualified_name,
&mut dummy_base_classes,
&mut parent_traits,
);
let required_methods = self.extract_interface_methods(node);
let interface_entity = TraitEntity {
name: qualified_name.clone(),
visibility,
line_start: node.start_position().row + 1,
line_end: node.end_position().row + 1,
required_methods,
parent_traits,
doc_comment,
attributes: self.extract_attributes(node),
};
self.traits.push(interface_entity);
let previous_class = self.current_class.take();
self.current_class = Some(qualified_name);
if let Some(body) = node.child_by_field_name("body") {
let mut cursor = body.walk();
for child in body.children(&mut cursor) {
if child.kind() == "method_declaration" {
self.visit_method(child);
}
}
}
self.current_class = previous_class;
}
fn visit_struct(&mut self, node: Node) {
let name = node
.child_by_field_name("name")
.map(|n| self.node_text(n))
.unwrap_or_else(|| "Struct".to_string());
let qualified_name = self.qualify_name(&name);
let modifiers = self.extract_modifiers(node);
let visibility = self.extract_visibility(&modifiers);
let doc_comment = self.extract_doc_comment(node);
let mut implemented_traits = Vec::new();
let mut dummy_base = Vec::new();
self.extract_base_list(
node,
&qualified_name,
&mut dummy_base,
&mut implemented_traits,
);
let struct_entity = ClassEntity {
name: qualified_name.clone(),
visibility,
line_start: node.start_position().row + 1,
line_end: node.end_position().row + 1,
is_abstract: false,
is_interface: false,
base_classes: Vec::new(),
implemented_traits,
methods: Vec::new(),
fields: Vec::new(),
doc_comment,
attributes: vec!["struct".to_string()],
type_parameters: self.extract_type_parameters(node),
};
self.classes.push(struct_entity);
let previous_class = self.current_class.take();
self.current_class = Some(qualified_name);
if let Some(body) = node.child_by_field_name("body") {
self.visit_class_body(body);
}
self.current_class = previous_class;
}
fn visit_enum(&mut self, node: Node) {
let name = node
.child_by_field_name("name")
.map(|n| self.node_text(n))
.unwrap_or_else(|| "Enum".to_string());
let qualified_name = self.qualify_name(&name);
let modifiers = self.extract_modifiers(node);
let visibility = self.extract_visibility(&modifiers);
let doc_comment = self.extract_doc_comment(node);
let enum_entity = ClassEntity {
name: qualified_name,
visibility,
line_start: node.start_position().row + 1,
line_end: node.end_position().row + 1,
is_abstract: false,
is_interface: false,
base_classes: Vec::new(),
implemented_traits: Vec::new(),
methods: Vec::new(),
fields: Vec::new(),
doc_comment,
attributes: vec!["enum".to_string()],
type_parameters: Vec::new(),
};
self.classes.push(enum_entity);
}
fn visit_record(&mut self, node: Node) {
let name = node
.child_by_field_name("name")
.map(|n| self.node_text(n))
.unwrap_or_else(|| "Record".to_string());
let qualified_name = self.qualify_name(&name);
let modifiers = self.extract_modifiers(node);
let visibility = self.extract_visibility(&modifiers);
let doc_comment = self.extract_doc_comment(node);
let mut base_classes = Vec::new();
let mut implemented_traits = Vec::new();
self.extract_base_list(
node,
&qualified_name,
&mut base_classes,
&mut implemented_traits,
);
let record_entity = ClassEntity {
name: qualified_name.clone(),
visibility,
line_start: node.start_position().row + 1,
line_end: node.end_position().row + 1,
is_abstract: false,
is_interface: false,
base_classes,
implemented_traits,
methods: Vec::new(),
fields: Vec::new(),
doc_comment,
attributes: vec!["record".to_string()],
type_parameters: self.extract_type_parameters(node),
};
self.classes.push(record_entity);
let previous_class = self.current_class.take();
self.current_class = Some(qualified_name);
if let Some(body) = node.child_by_field_name("body") {
self.visit_class_body(body);
}
self.current_class = previous_class;
}
fn visit_method(&mut self, node: Node) {
let name = node
.child_by_field_name("name")
.map(|n| self.node_text(n))
.unwrap_or_else(|| {
if node.kind() == "constructor_declaration" {
self.current_class
.clone()
.unwrap_or_else(|| "constructor".to_string())
.split('.')
.next_back()
.unwrap_or("constructor")
.to_string()
} else {
"method".to_string()
}
});
let modifiers = self.extract_modifiers(node);
let visibility = self.extract_visibility(&modifiers);
let is_static = modifiers.contains(&"static".to_string());
let is_abstract = modifiers.contains(&"abstract".to_string());
let is_async = modifiers.contains(&"async".to_string());
let return_type = self.extract_return_type(node);
let parameters = self.extract_parameters(node);
let doc_comment = self.extract_doc_comment(node);
let func = FunctionEntity {
name: name.clone(),
signature: self.extract_method_signature(node),
visibility,
line_start: node.start_position().row + 1,
line_end: node.end_position().row + 1,
is_async,
is_test: self.has_test_attribute(node),
is_static,
is_abstract,
parameters,
return_type,
doc_comment,
attributes: self.extract_attributes(node),
parent_class: self.current_class.clone(),
complexity: None,
};
self.functions.push(func);
let previous_function = self.current_function.take();
self.current_function = Some(name);
if let Some(body) = node.child_by_field_name("body") {
self.visit_method_body(body);
}
self.current_function = previous_function;
}
fn visit_property(&mut self, node: Node) {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "accessor_list" {
self.visit_accessor_list(child);
}
}
}
fn visit_accessor_list(&mut self, node: Node) {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "accessor_declaration" {
if let Some(body) = child.child_by_field_name("body") {
self.visit_method_body(body);
}
}
}
}
fn visit_call_expression(&mut self, node: Node) {
let caller = match &self.current_function {
Some(name) => name.clone(),
None => return,
};
let callee = self.extract_callee_name(node);
if callee.is_empty() {
return;
}
let call_site_line = node.start_position().row + 1;
let call = CallRelation {
caller,
callee,
call_site_line,
is_direct: true,
};
self.calls.push(call);
}
fn extract_callee_name(&self, node: Node) -> String {
match node.kind() {
"invocation_expression" => {
if let Some(func_node) = node.child_by_field_name("function") {
let text = self.node_text(func_node);
if let Some(stripped) = text.strip_prefix("this.") {
return stripped.to_string();
}
if let Some(stripped) = text.strip_prefix("base.") {
return stripped.to_string();
}
text
} else {
String::new()
}
}
"object_creation_expression" => {
if let Some(type_node) = node.child_by_field_name("type") {
format!("new {}", self.node_text(type_node))
} else {
String::new()
}
}
_ => String::new(),
}
}
fn visit_method_body(&mut self, node: Node) {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
match child.kind() {
"invocation_expression" | "object_creation_expression" => {
self.visit_call_expression(child);
self.visit_method_body(child);
}
_ => {
self.visit_method_body(child);
}
}
}
}
fn extract_base_list(
&mut self,
node: Node,
class_name: &str,
base_classes: &mut Vec<String>,
implemented_traits: &mut Vec<String>,
) {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "base_list" {
let mut base_cursor = child.walk();
let mut first = true;
for base_child in child.children(&mut base_cursor) {
if base_child.kind() == ":" || base_child.kind() == "," {
continue;
}
let type_name = self.extract_type_name(base_child);
if type_name.is_empty() {
continue;
}
let is_interface = type_name.starts_with('I')
&& type_name.len() > 1
&& type_name.chars().nth(1).is_some_and(|c| c.is_uppercase());
if is_interface {
implemented_traits.push(type_name.clone());
self.implementations.push(ImplementationRelation {
implementor: class_name.to_string(),
trait_name: type_name,
});
} else if first {
base_classes.push(type_name.clone());
self.inheritance.push(InheritanceRelation {
child: class_name.to_string(),
parent: type_name,
order: 0,
});
first = false;
} else {
implemented_traits.push(type_name.clone());
self.implementations.push(ImplementationRelation {
implementor: class_name.to_string(),
trait_name: type_name,
});
}
}
break;
}
}
}
fn extract_interface_methods(&self, node: Node) -> Vec<FunctionEntity> {
let mut methods = Vec::new();
if let Some(body) = node.child_by_field_name("body") {
let mut cursor = body.walk();
for child in body.children(&mut cursor) {
if child.kind() == "method_declaration" {
let name = child
.child_by_field_name("name")
.map(|n| self.node_text(n))
.unwrap_or_else(|| "method".to_string());
let modifiers = self.extract_modifiers(child);
let visibility = self.extract_visibility(&modifiers);
let is_static = modifiers.contains(&"static".to_string());
let return_type = self.extract_return_type(child);
let parameters = self.extract_parameters(child);
let func = FunctionEntity {
name,
signature: self.extract_method_signature(child),
visibility,
line_start: child.start_position().row + 1,
line_end: child.end_position().row + 1,
is_async: false,
is_test: false,
is_static,
is_abstract: true, parameters,
return_type,
doc_comment: None,
attributes: Vec::new(),
parent_class: None,
complexity: None,
};
methods.push(func);
}
}
}
methods
}
fn extract_modifiers(&self, node: Node) -> Vec<String> {
let mut modifiers = Vec::new();
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "modifier" {
modifiers.push(self.node_text(child));
}
}
modifiers
}
fn extract_visibility(&self, modifiers: &[String]) -> String {
for modifier in modifiers {
match modifier.as_str() {
"public" | "private" | "protected" | "internal" => return modifier.clone(),
"protected internal" | "private protected" => return modifier.clone(),
_ => {}
}
}
"internal".to_string() }
fn extract_return_type(&self, node: Node) -> Option<String> {
node.child_by_field_name("type")
.map(|n| self.node_text(n))
.filter(|t| t != "void")
}
fn extract_parameters(&self, node: Node) -> Vec<Parameter> {
let mut params = Vec::new();
if let Some(params_node) = node.child_by_field_name("parameters") {
let mut cursor = params_node.walk();
for child in params_node.children(&mut cursor) {
if child.kind() == "parameter" {
let name = child
.child_by_field_name("name")
.map(|n| self.node_text(n))
.unwrap_or_else(|| "param".to_string());
let type_annotation =
child.child_by_field_name("type").map(|n| self.node_text(n));
let is_variadic = self
.extract_modifiers(child)
.contains(&"params".to_string());
let mut param = Parameter::new(name);
if let Some(t) = type_annotation {
param = param.with_type(t);
}
if is_variadic {
param = param.variadic();
}
params.push(param);
}
}
}
params
}
fn extract_method_signature(&self, node: Node) -> String {
self.node_text(node)
.lines()
.next()
.unwrap_or("")
.to_string()
}
fn extract_type_name(&self, node: Node) -> String {
match node.kind() {
"identifier" | "qualified_name" => self.node_text(node),
"generic_name" => {
if let Some(name_node) = node.child_by_field_name("name") {
self.node_text(name_node)
} else {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "identifier" {
return self.node_text(child);
}
}
self.node_text(node)
}
}
"simple_base_type" | "base_type" => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
let type_name = self.extract_type_name(child);
if !type_name.is_empty() {
return type_name;
}
}
String::new()
}
_ => self.node_text(node),
}
}
fn extract_type_parameters(&self, node: Node) -> Vec<String> {
let mut type_params = Vec::new();
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "type_parameter_list" {
let mut param_cursor = child.walk();
for param_child in child.children(&mut param_cursor) {
if param_child.kind() == "type_parameter" {
if let Some(id) = param_child.child_by_field_name("name") {
type_params.push(self.node_text(id));
} else {
let mut id_cursor = param_child.walk();
for id_child in param_child.children(&mut id_cursor) {
if id_child.kind() == "identifier" {
type_params.push(self.node_text(id_child));
break;
}
}
}
}
}
break;
}
}
type_params
}
fn extract_doc_comment(&self, node: Node) -> Option<String> {
if let Some(prev) = node.prev_sibling() {
if prev.kind() == "comment" {
let comment = self.node_text(prev);
if comment.starts_with("///") {
return Some(comment);
}
}
}
None
}
fn extract_attributes(&self, node: Node) -> Vec<String> {
let mut attributes = Vec::new();
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "attribute_list" {
let mut attr_cursor = child.walk();
for attr in child.children(&mut attr_cursor) {
if attr.kind() == "attribute" {
attributes.push(self.node_text(attr));
}
}
}
}
attributes
}
fn has_test_attribute(&self, node: Node) -> bool {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "attribute_list" {
let text = self.node_text(child);
if text.contains("Test")
|| text.contains("Fact")
|| text.contains("Theory")
|| text.contains("TestMethod")
{
return true;
}
}
}
false
}
fn qualify_name(&self, name: &str) -> String {
if let Some(ref ns) = self.current_namespace {
format!("{}.{}", ns, name)
} else {
name.to_string()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse_and_visit(source: &[u8]) -> CSharpVisitor<'_> {
use tree_sitter::Parser;
let mut parser = Parser::new();
parser
.set_language(&tree_sitter_c_sharp::language())
.unwrap();
let tree = parser.parse(source, None).unwrap();
let mut visitor = CSharpVisitor::new(source, ParserConfig::default());
visitor.visit_node(tree.root_node());
visitor
}
#[test]
fn test_visitor_basics() {
let visitor = CSharpVisitor::new(b"", ParserConfig::default());
assert_eq!(visitor.functions.len(), 0);
assert_eq!(visitor.classes.len(), 0);
assert_eq!(visitor.traits.len(), 0);
}
#[test]
fn test_visitor_class_extraction() {
let source = b"public class Person { public string Name; }";
let visitor = parse_and_visit(source);
assert_eq!(visitor.classes.len(), 1);
assert_eq!(visitor.classes[0].name, "Person");
}
#[test]
fn test_visitor_interface_extraction() {
let source = b"public interface IReader { string Read(); }";
let visitor = parse_and_visit(source);
assert_eq!(visitor.traits.len(), 1);
assert_eq!(visitor.traits[0].name, "IReader");
}
#[test]
fn test_visitor_method_extraction() {
let source = b"public class Calculator { public int Add(int a, int b) { return a + b; } }";
let visitor = parse_and_visit(source);
assert_eq!(visitor.classes.len(), 1);
assert_eq!(visitor.functions.len(), 1);
assert_eq!(visitor.functions[0].name, "Add");
assert_eq!(
visitor.functions[0].parent_class,
Some("Calculator".to_string())
);
}
#[test]
fn test_visitor_using_extraction() {
let source = b"using System;\nusing System.Collections.Generic;";
let visitor = parse_and_visit(source);
assert_eq!(visitor.imports.len(), 2);
assert_eq!(visitor.imports[0].imported, "System");
assert_eq!(visitor.imports[1].imported, "System.Collections.Generic");
}
#[test]
fn test_debug_tree_structure() {
use tree_sitter::Parser;
fn print_tree(source: &[u8], node: tree_sitter::Node, indent: usize) {
let indent_str = " ".repeat(indent);
let text = node
.utf8_text(source)
.unwrap_or("")
.lines()
.next()
.unwrap_or("");
let text_display = if text.len() > 50 { &text[..50] } else { text };
println!("{}{}: {:?}", indent_str, node.kind(), text_display);
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
print_tree(source, child, indent + 1);
}
}
println!("\n=== inheritance ===");
let source = b"class Dog : Animal {}";
let mut parser = Parser::new();
parser
.set_language(&tree_sitter_c_sharp::language())
.unwrap();
let tree = parser.parse(source, None).unwrap();
print_tree(source, tree.root_node(), 0);
println!("\n=== generic type ===");
let source2 = b"public class Container<T> { private T value; }";
let tree2 = parser.parse(source2, None).unwrap();
print_tree(source2, tree2.root_node(), 0);
}
#[test]
fn test_visitor_inheritance() {
let source = b"class Animal {}\nclass Dog : Animal {}";
let visitor = parse_and_visit(source);
assert_eq!(visitor.classes.len(), 2);
assert_eq!(visitor.inheritance.len(), 1);
assert_eq!(visitor.inheritance[0].child, "Dog");
assert_eq!(visitor.inheritance[0].parent, "Animal");
}
#[test]
fn test_visitor_implements() {
let source = b"interface IShape { double Area(); }\nclass Circle : IShape { public double Area() { return 0.0; } }";
let visitor = parse_and_visit(source);
assert_eq!(visitor.traits.len(), 1);
assert_eq!(visitor.classes.len(), 1);
assert_eq!(visitor.implementations.len(), 1);
assert_eq!(visitor.implementations[0].implementor, "Circle");
assert_eq!(visitor.implementations[0].trait_name, "IShape");
}
#[test]
fn test_visitor_enum() {
let source = b"public enum Status { Pending, Active, Completed }";
let visitor = parse_and_visit(source);
assert_eq!(visitor.classes.len(), 1);
assert_eq!(visitor.classes[0].name, "Status");
assert!(visitor.classes[0].attributes.contains(&"enum".to_string()));
}
#[test]
fn test_visitor_struct() {
let source = b"public struct Point { public int X; public int Y; }";
let visitor = parse_and_visit(source);
assert_eq!(visitor.classes.len(), 1);
assert_eq!(visitor.classes[0].name, "Point");
assert!(visitor.classes[0]
.attributes
.contains(&"struct".to_string()));
}
#[test]
fn test_visitor_record() {
let source = b"public record Person(string Name, int Age);";
let visitor = parse_and_visit(source);
assert_eq!(visitor.classes.len(), 1);
assert_eq!(visitor.classes[0].name, "Person");
assert!(visitor.classes[0]
.attributes
.contains(&"record".to_string()));
}
#[test]
fn test_visitor_namespace() {
let source = b"namespace MyApp.Models { public class User {} }";
let visitor = parse_and_visit(source);
assert_eq!(visitor.classes.len(), 1);
assert_eq!(visitor.classes[0].name, "MyApp.Models.User");
}
#[test]
fn test_visitor_abstract_class() {
let source = b"public abstract class BaseController { public abstract void Handle(); }";
let visitor = parse_and_visit(source);
assert_eq!(visitor.classes.len(), 1);
assert!(visitor.classes[0].is_abstract);
}
#[test]
fn test_visitor_static_method() {
let source = b"public class Helper { public static string Format(string s) { return s; } }";
let visitor = parse_and_visit(source);
assert_eq!(visitor.functions.len(), 1);
assert!(visitor.functions[0].is_static);
}
#[test]
fn test_visitor_async_method() {
let source = b"public class Service { public async Task DoWork() { } }";
let visitor = parse_and_visit(source);
assert_eq!(visitor.functions.len(), 1);
assert!(visitor.functions[0].is_async);
}
#[test]
fn test_visitor_visibility_modifiers() {
let source = b"public class Foo { private void Bar() {} protected void Baz() {} public void Qux() {} }";
let visitor = parse_and_visit(source);
assert_eq!(visitor.functions.len(), 3);
assert_eq!(visitor.functions[0].visibility, "private");
assert_eq!(visitor.functions[1].visibility, "protected");
assert_eq!(visitor.functions[2].visibility, "public");
}
#[test]
fn test_visitor_method_call_extraction() {
let source = b"
public class MyClass
{
public void Caller()
{
Helper();
Process();
}
public void Helper() {}
public void Process() {}
}
";
let visitor = parse_and_visit(source);
assert_eq!(visitor.classes.len(), 1);
assert_eq!(visitor.functions.len(), 3);
assert_eq!(visitor.calls.len(), 2);
assert!(visitor
.calls
.iter()
.any(|c| c.caller == "Caller" && c.callee == "Helper"));
assert!(visitor
.calls
.iter()
.any(|c| c.caller == "Caller" && c.callee == "Process"));
}
#[test]
fn test_visitor_this_method_call() {
let source = b"
public class MyClass
{
public void Caller()
{
this.Helper();
}
public void Helper() {}
}
";
let visitor = parse_and_visit(source);
assert_eq!(visitor.calls.len(), 1);
assert_eq!(visitor.calls[0].caller, "Caller");
assert_eq!(visitor.calls[0].callee, "Helper");
}
#[test]
fn test_visitor_static_call_extraction() {
let source = b"
public class Calculator
{
public void Calculate()
{
Math.Abs(-1);
Helper.Format();
}
}
";
let visitor = parse_and_visit(source);
assert_eq!(visitor.calls.len(), 2);
assert!(visitor
.calls
.iter()
.any(|c| c.caller == "Calculate" && c.callee == "Math.Abs"));
assert!(visitor
.calls
.iter()
.any(|c| c.caller == "Calculate" && c.callee == "Helper.Format"));
}
#[test]
fn test_visitor_constructor_call() {
let source = b"
public class Factory
{
public void Create()
{
new List<int>();
new Dictionary<string, int>();
}
}
";
let visitor = parse_and_visit(source);
assert_eq!(visitor.calls.len(), 2);
assert!(visitor
.calls
.iter()
.any(|c| c.caller == "Create" && c.callee.starts_with("new ")));
}
#[test]
fn test_visitor_test_attribute() {
let source = b"
using NUnit.Framework;
public class MyTest
{
[Test]
public void TestSomething() {}
}
";
let visitor = parse_and_visit(source);
assert_eq!(visitor.functions.len(), 1);
assert!(visitor.functions[0].is_test);
}
#[test]
fn test_visitor_generic_class() {
let source = b"public class Container<T> { private T value; }";
let visitor = parse_and_visit(source);
assert_eq!(visitor.classes.len(), 1);
assert!(!visitor.classes[0].type_parameters.is_empty());
}
#[test]
fn test_visitor_call_line_numbers() {
let source = b"
public class Test
{
void Caller()
{
Helper();
}
void Helper() {}
}
";
let visitor = parse_and_visit(source);
assert_eq!(visitor.calls.len(), 1);
assert_eq!(visitor.calls[0].caller, "Caller");
assert_eq!(visitor.calls[0].callee, "Helper");
assert_eq!(visitor.calls[0].call_site_line, 6);
assert!(visitor.calls[0].is_direct);
}
}