use crate::extraction::tree_sitter_helpers::get_node_text;
use crate::extraction::tree_sitter_types::{
ClassClassification, ImportInfo, LanguageExtractor, Visibility,
};
use crate::web_tree_sitter::SyntaxNode;
pub struct CsharpExtractor;
pub const CSHARP_EXTRACTOR: CsharpExtractor = CsharpExtractor;
pub fn csharp_extractor() -> &'static CsharpExtractor {
&CSHARP_EXTRACTOR
}
pub fn blank_csharp_preprocessor_directives(source: &str) -> String {
if !source.contains('#') {
return source.to_string();
}
let mut out = String::with_capacity(source.len());
for line in source.split_inclusive('\n') {
let without_newline = line.strip_suffix('\n').unwrap_or(line);
let indent_len = without_newline
.chars()
.take_while(|c| *c == ' ' || *c == '\t')
.map(char::len_utf8)
.sum::<usize>();
let trimmed = &without_newline[indent_len..];
let is_conditional = ["#if", "#elif", "#else", "#endif"]
.iter()
.any(|prefix| trimmed.starts_with(prefix));
if is_conditional {
out.push_str(&without_newline[..indent_len]);
out.push_str(&" ".repeat(without_newline.len() - indent_len));
if line.ends_with('\n') {
out.push('\n');
}
} else {
out.push_str(line);
}
}
out
}
fn extract_csharp_return_type(node: &SyntaxNode, source: &str) -> Option<String> {
let type_node = node.child_for_field_name("returns")?;
if type_node.type_name() == "predefined_type" || type_node.type_name() == "array_type" {
return None;
}
let mut t = get_node_text(type_node, source)
.trim()
.trim_end_matches('?')
.to_string();
t = strip_angle_generics(&t);
let last = t.split('.').next_back()?.trim();
is_identifier(last).then(|| last.to_string())
}
fn strip_angle_generics(input: &str) -> String {
let mut out = String::new();
let mut depth = 0usize;
for ch in input.chars() {
match ch {
'<' => depth += 1,
'>' => depth = depth.saturating_sub(1),
_ if depth == 0 => out.push(ch),
_ => {}
}
}
out
}
fn is_identifier(value: &str) -> bool {
let mut chars = value.chars();
matches!(chars.next(), Some('_') | Some('A'..='Z') | Some('a'..='z'))
&& chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
}
impl LanguageExtractor for CsharpExtractor {
fn pre_parse(&self, source: &str) -> Option<String> {
Some(blank_csharp_preprocessor_directives(source))
}
fn function_types(&self) -> &'static [&'static str] {
&[]
}
fn class_types(&self) -> &'static [&'static str] {
&["class_declaration", "record_declaration"]
}
fn method_types(&self) -> &'static [&'static str] {
&["method_declaration", "constructor_declaration"]
}
fn interface_types(&self) -> &'static [&'static str] {
&["interface_declaration"]
}
fn struct_types(&self) -> &'static [&'static str] {
&["struct_declaration", "record_struct_declaration"]
}
fn classify_class_node(&self, node: &SyntaxNode) -> ClassClassification {
if node.type_name() == "record_declaration"
&& (0..node.child_count())
.any(|i| node.child(i).is_some_and(|c| c.type_name() == "struct"))
{
ClassClassification::Struct
} else {
ClassClassification::Class
}
}
fn enum_types(&self) -> &'static [&'static str] {
&["enum_declaration"]
}
fn enum_member_types(&self) -> &'static [&'static str] {
&["enum_member_declaration"]
}
fn type_alias_types(&self) -> &'static [&'static str] {
&[]
}
fn package_types(&self) -> &'static [&'static str] {
&["namespace_declaration", "file_scoped_namespace_declaration"]
}
fn extract_package(&self, node: &SyntaxNode, source: &str) -> Option<String> {
node.child_for_field_name("name")
.cloned()
.or_else(|| {
node.named_children()
.into_iter()
.find(|c| c.type_name() == "qualified_name" || c.type_name() == "identifier")
})
.map(|name| get_node_text(&name, source))
}
fn import_types(&self) -> &'static [&'static str] {
&["using_directive"]
}
fn call_types(&self) -> &'static [&'static str] {
&["invocation_expression"]
}
fn variable_types(&self) -> &'static [&'static str] {
&["local_declaration_statement"]
}
fn field_types(&self) -> &'static [&'static str] {
&["field_declaration"]
}
fn property_types(&self) -> &'static [&'static str] {
&["property_declaration"]
}
fn name_field(&self) -> &'static str {
"name"
}
fn body_field(&self) -> &'static str {
"body"
}
fn params_field(&self) -> &'static str {
"parameters"
}
fn return_field(&self) -> Option<&'static str> {
Some("type")
}
fn get_return_type(&self, node: &SyntaxNode, source: &str) -> Option<String> {
extract_csharp_return_type(node, source)
}
fn get_visibility(&self, node: &SyntaxNode) -> Option<Visibility> {
for i in 0..node.child_count() {
let Some(child) = node.child(i) else {
continue;
};
if child.type_name() == "modifier" {
return match child.text().as_str() {
"public" => Some(Visibility::Public),
"private" => Some(Visibility::Private),
"protected" => Some(Visibility::Protected),
"internal" => Some(Visibility::Internal),
_ => None,
};
}
}
Some(Visibility::Private)
}
fn is_static(&self, node: &SyntaxNode) -> bool {
(0..node.child_count()).any(|i| {
node.child(i)
.is_some_and(|c| c.type_name() == "modifier" && c.text() == "static")
})
}
fn is_const(&self, node: &SyntaxNode) -> bool {
let mut has_static = false;
let mut has_readonly = false;
for i in 0..node.child_count() {
let Some(child) = node.child(i) else {
continue;
};
if child.type_name() != "modifier" {
continue;
}
match child.text().as_str() {
"const" => return true,
"static" => has_static = true,
"readonly" => has_readonly = true,
_ => {}
}
}
has_static && has_readonly
}
fn is_async(&self, node: &SyntaxNode) -> bool {
(0..node.child_count()).any(|i| {
node.child(i)
.is_some_and(|c| c.type_name() == "modifier" && c.text() == "async")
})
}
fn extract_import(&self, node: &SyntaxNode, source: &str) -> Option<ImportInfo> {
let import_text = get_node_text(node, source).trim().to_string();
if let Some(qualified_name) = node
.named_children()
.into_iter()
.find(|c| c.type_name() == "qualified_name")
{
return Some(ImportInfo {
module_name: get_node_text(&qualified_name, source),
signature: import_text,
handled_refs: false,
});
}
let identifier = node
.named_children()
.into_iter()
.find(|c| c.type_name() == "identifier")?;
Some(ImportInfo {
module_name: get_node_text(&identifier, source),
signature: import_text,
handled_refs: false,
})
}
}