use super::{extract_doc_comment, field_text, node_text};
use crate::types::*;
pub fn extract(tree: &tree_sitter::Tree, source: &[u8]) -> (Vec<Symbol>, Vec<Import>) {
let root = tree.root_node();
let mut symbols = Vec::new();
let mut imports = Vec::new();
extract_children(&root, source, &mut symbols, &mut imports, None);
(symbols, imports)
}
fn extract_children(
node: &tree_sitter::Node,
source: &[u8],
symbols: &mut Vec<Symbol>,
imports: &mut Vec<Import>,
parent_name: Option<&str>,
) {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
match child.kind() {
"using_directive" => {
let text = node_text(&child, source).trim().to_string();
imports.push(Import {
path: text,
alias: None,
span: Span::from_node(&child),
});
}
"namespace_declaration" | "file_scoped_namespace_declaration" => {
let name = field_text(&child, "name", source)
.unwrap_or("(anonymous)")
.to_string();
let mut ns_sym = Symbol {
name: name.clone(),
kind: SymbolKind::Module,
span: Span::from_node(&child),
signature: format!("namespace {}", name),
doc_comment: extract_doc_comment(&child, source),
parent: parent_name.map(|s| s.to_string()),
children: Vec::new(),
};
if let Some(body) = child.child_by_field_name("body") {
let mut ns_children = Vec::new();
extract_children(&body, source, &mut ns_children, imports, Some(&name));
ns_sym.children = ns_children;
} else {
let mut ns_children = Vec::new();
let mut inner = child.walk();
for ns_child in child.children(&mut inner) {
extract_children(&ns_child, source, &mut ns_children, imports, Some(&name));
}
ns_sym.children = ns_children;
}
symbols.push(ns_sym);
}
"class_declaration" => {
if let Some(sym) = extract_type_decl(&child, source, SymbolKind::Class, parent_name)
{
symbols.push(sym);
}
}
"struct_declaration" => {
if let Some(sym) =
extract_type_decl(&child, source, SymbolKind::Struct, parent_name)
{
symbols.push(sym);
}
}
"interface_declaration" => {
if let Some(sym) =
extract_type_decl(&child, source, SymbolKind::Interface, parent_name)
{
symbols.push(sym);
}
}
"enum_declaration" => {
if let Some(sym) = extract_enum(&child, source, parent_name) {
symbols.push(sym);
}
}
"record_declaration" => {
if let Some(sym) = extract_type_decl(&child, source, SymbolKind::Class, parent_name)
{
symbols.push(sym);
}
}
"delegate_declaration" => {
if let Some(name) = field_text(&child, "name", source) {
symbols.push(Symbol {
name: name.to_string(),
kind: SymbolKind::TypeAlias,
span: Span::from_node(&child),
signature: node_text(&child, source)
.trim_end_matches(';')
.trim()
.to_string(),
doc_comment: extract_doc_comment(&child, source),
parent: parent_name.map(|s| s.to_string()),
children: Vec::new(),
});
}
}
"method_declaration" | "constructor_declaration" | "destructor_declaration" => {
if let Some(sym) = extract_method(&child, source, parent_name) {
symbols.push(sym);
}
}
"field_declaration" | "event_field_declaration" => {
extract_field(&child, source, symbols, parent_name);
}
"property_declaration" => {
if let Some(sym) = extract_property(&child, source, parent_name) {
symbols.push(sym);
}
}
"declaration_list" | "compilation_unit" => {
extract_children(&child, source, symbols, imports, parent_name);
}
_ => {}
}
}
}
fn extract_type_decl(
node: &tree_sitter::Node,
source: &[u8],
kind: SymbolKind,
parent_name: Option<&str>,
) -> Option<Symbol> {
let name = field_text(node, "name", source)?;
let signature = if let Some(body) = node.child_by_field_name("body") {
let sig = &source[node.start_byte()..body.start_byte()];
std::str::from_utf8(sig).unwrap_or("").trim().to_string()
} else {
node_text(node, source).to_string()
};
let mut children = Vec::new();
let mut imports = Vec::new();
if let Some(body) = node.child_by_field_name("body") {
extract_children(&body, source, &mut children, &mut imports, Some(name));
}
Some(Symbol {
name: name.to_string(),
kind,
span: Span::from_node(node),
signature,
doc_comment: extract_doc_comment(node, source),
parent: parent_name.map(|s| s.to_string()),
children,
})
}
fn extract_enum(
node: &tree_sitter::Node,
source: &[u8],
parent_name: Option<&str>,
) -> Option<Symbol> {
let name = field_text(node, "name", source)?;
let signature = if let Some(body) = node.child_by_field_name("body") {
let sig = &source[node.start_byte()..body.start_byte()];
std::str::from_utf8(sig).unwrap_or("").trim().to_string()
} else {
node_text(node, source).to_string()
};
let mut children = 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() == "enum_member_declaration" {
if let Some(member_name) = field_text(&child, "name", source) {
children.push(Symbol {
name: member_name.to_string(),
kind: SymbolKind::Const,
span: Span::from_node(&child),
signature: node_text(&child, source).to_string(),
doc_comment: extract_doc_comment(&child, source),
parent: Some(name.to_string()),
children: Vec::new(),
});
}
}
}
}
Some(Symbol {
name: name.to_string(),
kind: SymbolKind::Enum,
span: Span::from_node(node),
signature,
doc_comment: extract_doc_comment(node, source),
parent: parent_name.map(|s| s.to_string()),
children,
})
}
fn extract_method(
node: &tree_sitter::Node,
source: &[u8],
parent_name: Option<&str>,
) -> Option<Symbol> {
let name = field_text(node, "name", source).or_else(|| {
if node.kind() == "constructor_declaration" || node.kind() == "destructor_declaration" {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "identifier" {
return Some(node_text(&child, source));
}
}
}
None
})?;
let kind = if parent_name.is_some() {
SymbolKind::Method
} else {
SymbolKind::Function
};
let signature = if let Some(body) = node.child_by_field_name("body") {
let sig = &source[node.start_byte()..body.start_byte()];
std::str::from_utf8(sig).unwrap_or("").trim().to_string()
} else {
node_text(node, source)
.trim_end_matches(';')
.trim()
.to_string()
};
Some(Symbol {
name: name.to_string(),
kind,
span: Span::from_node(node),
signature,
doc_comment: extract_doc_comment(node, source),
parent: parent_name.map(|s| s.to_string()),
children: Vec::new(),
})
}
fn extract_field(
node: &tree_sitter::Node,
source: &[u8],
symbols: &mut Vec<Symbol>,
parent_name: Option<&str>,
) {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "variable_declaration" {
let mut inner = child.walk();
for decl in child.children(&mut inner) {
if decl.kind() == "variable_declarator" {
if let Some(name) = field_text(&decl, "name", source).or_else(|| {
let mut c = decl.walk();
for ch in decl.children(&mut c) {
if ch.kind() == "identifier" {
return Some(node_text(&ch, source));
}
}
None
}) {
symbols.push(Symbol {
name: name.to_string(),
kind: SymbolKind::Const,
span: Span::from_node(node),
signature: node_text(node, source)
.trim_end_matches(';')
.trim()
.to_string(),
doc_comment: extract_doc_comment(node, source),
parent: parent_name.map(|s| s.to_string()),
children: Vec::new(),
});
}
}
}
}
}
}
fn extract_property(
node: &tree_sitter::Node,
source: &[u8],
parent_name: Option<&str>,
) -> Option<Symbol> {
let name = field_text(node, "name", source)?;
let signature = node_text(node, source)
.lines()
.next()
.unwrap_or("")
.trim()
.to_string();
Some(Symbol {
name: name.to_string(),
kind: SymbolKind::Const, span: Span::from_node(node),
signature,
doc_comment: extract_doc_comment(node, source),
parent: parent_name.map(|s| s.to_string()),
children: Vec::new(),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn parse_csharp(source: &str) -> (Vec<Symbol>, Vec<Import>) {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&tree_sitter_c_sharp::LANGUAGE.into())
.unwrap();
let tree = parser.parse(source, None).unwrap();
extract(&tree, source.as_bytes())
}
#[test]
fn test_class_with_methods() {
let source = r#"
using System;
using System.Collections.Generic;
namespace MyApp
{
public class Calculator
{
private int _value;
public Calculator(int initial)
{
_value = initial;
}
public int Add(int x)
{
_value += x;
return _value;
}
public string Name { get; set; }
}
}
"#;
let (symbols, imports) = parse_csharp(source);
assert_eq!(imports.len(), 2);
assert!(imports[0].path.contains("System"));
assert_eq!(symbols.len(), 1);
assert_eq!(symbols[0].name, "MyApp");
assert_eq!(symbols[0].kind, SymbolKind::Module);
let ns_children = &symbols[0].children;
assert_eq!(ns_children.len(), 1);
assert_eq!(ns_children[0].name, "Calculator");
assert_eq!(ns_children[0].kind, SymbolKind::Class);
let class_children = &ns_children[0].children;
let names: Vec<&str> = class_children.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&"_value"));
assert!(names.contains(&"Calculator"));
assert!(names.contains(&"Add"));
assert!(names.contains(&"Name"));
}
#[test]
fn test_interface_and_enum() {
let source = r#"
public interface IService
{
void Execute();
string GetName();
}
public enum Status
{
Active,
Inactive,
Pending
}
"#;
let (symbols, _imports) = parse_csharp(source);
let iface = symbols.iter().find(|s| s.name == "IService").unwrap();
assert_eq!(iface.kind, SymbolKind::Interface);
assert_eq!(iface.children.len(), 2);
let enm = symbols.iter().find(|s| s.name == "Status").unwrap();
assert_eq!(enm.kind, SymbolKind::Enum);
assert_eq!(enm.children.len(), 3);
assert_eq!(enm.children[0].name, "Active");
}
#[test]
fn test_struct_and_record() {
let source = r#"
public struct Point
{
public int X;
public int Y;
}
public record Person(string Name, int Age);
"#;
let (symbols, _imports) = parse_csharp(source);
let point = symbols.iter().find(|s| s.name == "Point").unwrap();
assert_eq!(point.kind, SymbolKind::Struct);
let person = symbols.iter().find(|s| s.name == "Person").unwrap();
assert_eq!(person.kind, SymbolKind::Class);
}
#[test]
fn test_language_detection() {
assert_eq!(Language::from_extension("cs"), Some(Language::CSharp));
assert_eq!(Language::from_extension("csx"), Some(Language::CSharp));
assert_eq!(
Language::from_filename("Program.cs"),
Some(Language::CSharp)
);
}
}