use anyhow::Result;
use serde::Serialize;
use std::collections::HashMap;
use tree_sitter::Language;
use crate::core::graph::{Edge, Symbol};
use crate::core::parser::SupportedLanguage;
use crate::languages::{make_edge, resolve_scope_id, LanguagePlugin};
pub struct CSharpPlugin;
impl LanguagePlugin for CSharpPlugin {
fn language(&self) -> SupportedLanguage {
SupportedLanguage::CSharp
}
fn extensions(&self) -> &[&str] {
&["cs"]
}
fn ts_language(&self) -> Language {
tree_sitter_c_sharp::language()
}
fn symbol_query_source(&self) -> &str {
include_str!("../queries/csharp/symbols.scm")
}
fn edge_query_source(&self) -> &str {
include_str!("../queries/csharp/edges.scm")
}
fn infer_symbol_kind(&self, node_kind: &str) -> &str {
match node_kind {
"class_declaration" => "class",
"method_declaration" => "method",
"constructor_declaration" => "method",
"property_declaration" => "property",
"interface_declaration" => "interface",
"enum_declaration" => "enum",
"struct_declaration" => "struct",
"record_declaration" => "class",
"delegate_declaration" => "type",
"enum_member_declaration" => "variant",
_ => "function",
}
}
fn scope_node_types(&self) -> &[&str] {
&[
"method_declaration",
"constructor_declaration",
"class_declaration",
"struct_declaration",
"interface_declaration",
"record_declaration",
]
}
fn class_body_node_types(&self) -> &[&str] {
&["declaration_list", "enum_member_declaration_list"]
}
fn class_decl_node_types(&self) -> &[&str] {
&[
"class_declaration",
"struct_declaration",
"interface_declaration",
"record_declaration",
"enum_declaration",
]
}
fn extract_metadata(
&self,
node: &tree_sitter::Node,
source: &str,
kind: &str,
) -> Result<String> {
extract_metadata(node, source, kind)
}
fn extract_edge(
&self,
pattern_index: usize,
captures: &HashMap<String, (String, u32)>,
file_path: &str,
enclosing_scope_id: Option<&str>,
) -> Vec<Edge> {
extract_cs_edge(pattern_index, captures, file_path, enclosing_scope_id)
}
fn generic_name_stopwords(&self) -> &[&str] {
&[
"ToString",
"GetHashCode",
"Equals",
"Dispose",
"GetType",
"Main",
]
}
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct CSharpMetadata {
pub access: String,
pub is_async: bool,
pub is_static: bool,
pub is_abstract: bool,
pub is_virtual: bool,
pub is_override: bool,
pub is_sealed: bool,
pub is_readonly: bool,
pub is_partial: bool,
pub return_type: Option<String>,
pub parameters: Vec<CSharpParameterInfo>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CSharpParameterInfo {
pub name: String,
#[serde(rename = "type")]
pub type_annotation: Option<String>,
pub optional: bool,
}
pub fn extract_metadata(node: &tree_sitter::Node, source: &str, kind: &str) -> Result<String> {
let mut meta = CSharpMetadata::default();
let mut child_cursor = node.walk();
for child in node.children(&mut child_cursor) {
if child.kind() == "modifier" {
if let Ok(text) = child.utf8_text(source.as_bytes()) {
match text {
"public" => meta.access = "public".to_string(),
"private" => meta.access = "private".to_string(),
"protected" => {
if meta.access == "internal" {
meta.access = "protected internal".to_string();
} else {
meta.access = "protected".to_string();
}
}
"internal" => {
if meta.access == "protected" {
meta.access = "protected internal".to_string();
} else {
meta.access = "internal".to_string();
}
}
"async" => meta.is_async = true,
"static" => meta.is_static = true,
"abstract" => meta.is_abstract = true,
"virtual" => meta.is_virtual = true,
"override" => meta.is_override = true,
"sealed" => meta.is_sealed = true,
"readonly" => meta.is_readonly = true,
"partial" => meta.is_partial = true,
_ => {}
}
}
}
}
if meta.access.is_empty() {
meta.access = match kind {
"class" | "interface" | "struct" | "enum" => "internal".to_string(),
_ => "private".to_string(),
};
}
if let Some(returns_node) = node.child_by_field_name("returns") {
if let Ok(text) = returns_node.utf8_text(source.as_bytes()) {
meta.return_type = Some(text.trim().to_string());
}
}
if kind == "property" {
if let Some(type_node) = node.child_by_field_name("type") {
if let Ok(text) = type_node.utf8_text(source.as_bytes()) {
meta.return_type = Some(text.trim().to_string());
}
}
}
if kind == "function" || kind == "method" || kind == "constructor" {
if let Some(params_node) = node.child_by_field_name("parameters") {
meta.parameters = extract_parameters(¶ms_node, source);
}
}
let json = serde_json::to_string(&meta)?;
Ok(json)
}
fn extract_parameters(params_node: &tree_sitter::Node, source: &str) -> Vec<CSharpParameterInfo> {
let mut params = Vec::new();
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")
.and_then(|n| n.utf8_text(source.as_bytes()).ok())
.unwrap_or_default()
.to_string();
let type_annotation = child
.child_by_field_name("type")
.and_then(|n| n.utf8_text(source.as_bytes()).ok())
.map(|t| t.trim().to_string());
let mut param_cursor = child.walk();
let has_default = child
.children(&mut param_cursor)
.any(|c| c.kind() == "equals_value_clause");
let optional = has_default;
if !name.is_empty() {
params.push(CSharpParameterInfo {
name,
type_annotation,
optional,
});
}
}
}
params
}
#[allow(dead_code)]
pub fn merge_partial_classes(symbols: &mut [Symbol]) -> Vec<String> {
use std::collections::HashMap;
let mut removals = Vec::new();
let mut class_groups: HashMap<String, Vec<usize>> = HashMap::new();
for (i, symbol) in symbols.iter().enumerate() {
if symbol.kind == "class" {
let is_partial = symbol.metadata.contains("\"is_partial\":true");
if is_partial {
class_groups.entry(symbol.name.clone()).or_default().push(i);
}
}
}
for indices in class_groups.values() {
if indices.len() <= 1 {
continue;
}
let primary_id = symbols[indices[0]].id.clone();
let secondary_ids: Vec<String> = indices[1..]
.iter()
.map(|&i| symbols[i].id.clone())
.collect();
for symbol in symbols.iter_mut() {
if let Some(ref parent) = symbol.parent_id {
if secondary_ids.contains(parent) {
symbol.parent_id = Some(primary_id.clone());
}
}
}
removals.extend(secondary_ids);
}
removals
}
fn extract_cs_edge(
pattern: usize,
captures: &HashMap<String, (String, u32)>,
file_path: &str,
enclosing_scope_id: Option<&str>,
) -> Vec<Edge> {
let mut edges = Vec::new();
let from_fn = resolve_scope_id(enclosing_scope_id, file_path, "function");
let from_cls = resolve_scope_id(enclosing_scope_id, file_path, "class");
let module_fn = || format!("{file_path}::__module__::function");
match pattern {
0 | 1 => {
if let Some((imported_name, line)) = captures.get("imported_name") {
edges.push(make_edge(
module_fn(),
imported_name,
"imports",
file_path,
*line,
));
}
}
2 => {
if let (Some((object, line)), Some((method, _))) =
(captures.get("object"), captures.get("method"))
{
edges.push(make_edge(
from_fn.clone(),
format!("{object}.{method}"),
"calls",
file_path,
*line,
));
}
}
3 => {
if let Some((callee, line)) = captures.get("callee") {
edges.push(make_edge(
from_fn.clone(),
callee,
"calls",
file_path,
*line,
));
}
}
4 => {
if let Some((class_name, line)) = captures.get("class_name") {
edges.push(make_edge(
from_fn.clone(),
class_name,
"instantiates",
file_path,
*line,
));
}
}
5 | 8 => {
if let Some((method, line)) = captures.get("method") {
edges.push(make_edge(
from_fn.clone(),
method,
"calls",
file_path,
*line,
));
}
}
6 | 7 => {
if let Some((base_type, line)) = captures.get("base_type") {
edges.push(make_edge(
from_cls.clone(),
base_type,
"implements",
file_path,
*line,
));
}
}
9 => {
if let Some((variant_ref, line)) = captures.get("variant_ref") {
edges.push(make_edge(
from_fn.clone(),
variant_ref,
"references",
file_path,
*line,
));
}
}
_ => {}
}
edges
}