use tokensave::extraction::CSharpExtractor;
use tokensave::extraction::LanguageExtractor;
use tokensave::types::*;
#[test]
fn test_cs_file_node_is_root() {
let source = "public class Main {}";
let extractor = CSharpExtractor;
let result = extractor.extract("src/Main.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let files: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::File)
.collect();
assert_eq!(files.len(), 1);
assert_eq!(files[0].name, "src/Main.cs");
}
#[test]
fn test_cs_namespace() {
let source = r#"
namespace MyApp.Models
{
public class User {}
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let namespaces: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Namespace)
.collect();
assert_eq!(namespaces.len(), 1);
assert_eq!(namespaces[0].name, "MyApp.Models");
}
#[test]
fn test_cs_using_directive() {
let source = r#"
using System;
using System.Collections.Generic;
using System.Linq;
public class Foo {}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let uses: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert_eq!(uses.len(), 3);
assert!(uses.iter().any(|u| u.name == "System"));
assert!(uses.iter().any(|u| u.name == "System.Collections.Generic"));
}
#[test]
fn test_cs_class() {
let source = r#"
namespace TestApp
{
/// <summary>
/// A simple calculator.
/// </summary>
public class Calculator
{
public int Add(int a, int b) { return a + b; }
}
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("Calculator.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let classes: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Class)
.collect();
assert_eq!(classes.len(), 1);
assert_eq!(classes[0].name, "Calculator");
assert_eq!(classes[0].visibility, Visibility::Pub);
assert!(
classes[0]
.docstring
.as_ref()
.unwrap()
.contains("simple calculator"),
"docstring: {:?}",
classes[0].docstring
);
}
#[test]
fn test_cs_struct() {
let source = r#"
public struct Point
{
public int X;
public int Y;
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let structs: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Struct)
.collect();
assert_eq!(structs.len(), 1);
assert_eq!(structs[0].name, "Point");
assert_eq!(structs[0].visibility, Visibility::Pub);
}
#[test]
fn test_cs_interface() {
let source = r#"
public interface IDrawable
{
void Draw();
double Area();
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let ifaces: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Interface)
.collect();
assert_eq!(ifaces.len(), 1);
assert_eq!(ifaces[0].name, "IDrawable");
}
#[test]
fn test_cs_enum_with_members() {
let source = r#"
public enum Color
{
Red,
Green,
Blue
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let enums: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Enum)
.collect();
assert_eq!(enums.len(), 1);
assert_eq!(enums[0].name, "Color");
let variants: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::EnumVariant)
.collect();
assert_eq!(variants.len(), 3);
assert!(variants.iter().any(|v| v.name == "Red"));
assert!(variants.iter().any(|v| v.name == "Green"));
assert!(variants.iter().any(|v| v.name == "Blue"));
}
#[test]
fn test_cs_method() {
let source = r#"
public class Foo
{
public void DoSomething() {}
private int Compute(int x) { return x * 2; }
protected string GetName() { return "foo"; }
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert_eq!(methods.len(), 3);
let do_something = methods.iter().find(|m| m.name == "DoSomething").unwrap();
assert_eq!(do_something.visibility, Visibility::Pub);
let compute = methods.iter().find(|m| m.name == "Compute").unwrap();
assert_eq!(compute.visibility, Visibility::Private);
let get_name = methods.iter().find(|m| m.name == "GetName").unwrap();
assert_eq!(get_name.visibility, Visibility::PubSuper);
}
#[test]
fn test_cs_constructor() {
let source = r#"
public class Person
{
private string _name;
public Person(string name)
{
_name = name;
}
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let constructors: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Constructor)
.collect();
assert_eq!(constructors.len(), 1);
assert_eq!(constructors[0].name, "Person");
}
#[test]
fn test_cs_property() {
let source = r#"
public class Config
{
public string Name { get; set; }
private int Port { get; set; }
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let props: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::CSharpProperty)
.collect();
assert_eq!(props.len(), 2);
let name_prop = props.iter().find(|p| p.name == "Name").unwrap();
assert_eq!(name_prop.visibility, Visibility::Pub);
let port_prop = props.iter().find(|p| p.name == "Port").unwrap();
assert_eq!(port_prop.visibility, Visibility::Private);
}
#[test]
fn test_cs_field() {
let source = r#"
public class Config
{
public static readonly int MaxSize = 1024;
private string _name;
internal int _port;
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let fields: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Field)
.collect();
assert_eq!(
fields.len(),
3,
"fields: {:?}",
fields.iter().map(|f| &f.name).collect::<Vec<_>>()
);
let max_size = fields.iter().find(|f| f.name == "MaxSize").unwrap();
assert_eq!(max_size.visibility, Visibility::Pub);
let name_field = fields.iter().find(|f| f.name == "_name").unwrap();
assert_eq!(name_field.visibility, Visibility::Private);
let port_field = fields.iter().find(|f| f.name == "_port").unwrap();
assert_eq!(port_field.visibility, Visibility::PubCrate);
}
#[test]
fn test_cs_record() {
let source = r#"
public record Person(string Name, int Age);
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let records: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Record)
.collect();
assert_eq!(records.len(), 1);
assert_eq!(records[0].name, "Person");
assert_eq!(records[0].visibility, Visibility::Pub);
}
#[test]
fn test_cs_delegate() {
let source = r#"
public delegate void EventHandler(object sender, EventArgs e);
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let delegates: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Delegate)
.collect();
assert_eq!(delegates.len(), 1);
assert_eq!(delegates[0].name, "EventHandler");
assert_eq!(delegates[0].visibility, Visibility::Pub);
}
#[test]
fn test_cs_event() {
let source = r#"
public class Button
{
public event EventHandler Click;
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let events: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Event)
.collect();
assert_eq!(
events.len(),
1,
"events: {:?}",
result
.nodes
.iter()
.map(|n| (&n.kind, &n.name))
.collect::<Vec<_>>()
);
assert_eq!(events[0].name, "Click");
assert_eq!(events[0].visibility, Visibility::Pub);
}
#[test]
fn test_cs_attribute() {
let source = r#"
public class Foo
{
[Obsolete("Use NewMethod instead")]
public void OldMethod() {}
[Serializable]
public void NewMethod() {}
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let annots: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::AnnotationUsage)
.collect();
assert!(
annots.len() >= 2,
"should extract attribute usages, got: {:?}",
annots.iter().map(|a| &a.name).collect::<Vec<_>>()
);
let has_annotates = result.edges.iter().any(|e| e.kind == EdgeKind::Annotates)
|| result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Annotates);
assert!(has_annotates, "should have Annotates edges");
}
#[test]
fn test_cs_inheritance() {
let source = r#"
public interface IAnimal
{
void Speak();
}
public class Animal
{
public virtual void Eat() {}
}
public class Dog : Animal, IAnimal
{
public void Speak() {}
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let has_extends = result.edges.iter().any(|e| e.kind == EdgeKind::Extends)
|| result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Extends);
assert!(has_extends, "should detect extends");
let has_implements = result.edges.iter().any(|e| e.kind == EdgeKind::Implements)
|| result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Implements);
assert!(has_implements, "should detect implements");
}
#[test]
fn test_cs_visibility() {
let source = r#"
public class Foo
{
public void PubMethod() {}
private void PrivMethod() {}
internal void InternalMethod() {}
protected void ProtectedMethod() {}
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
let pub_m = methods.iter().find(|m| m.name == "PubMethod").unwrap();
assert_eq!(pub_m.visibility, Visibility::Pub);
let priv_m = methods.iter().find(|m| m.name == "PrivMethod").unwrap();
assert_eq!(priv_m.visibility, Visibility::Private);
let internal_m = methods.iter().find(|m| m.name == "InternalMethod").unwrap();
assert_eq!(internal_m.visibility, Visibility::PubCrate);
let protected_m = methods
.iter()
.find(|m| m.name == "ProtectedMethod")
.unwrap();
assert_eq!(protected_m.visibility, Visibility::PubSuper);
}
#[test]
fn test_cs_xml_doc_comment() {
let source = r#"
public class Foo
{
/// <summary>
/// Adds two numbers together.
/// </summary>
/// <param name="a">First number</param>
/// <param name="b">Second number</param>
/// <returns>The sum</returns>
public int Add(int a, int b) { return a + b; }
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert_eq!(methods.len(), 1);
let docstring = methods[0].docstring.as_ref().unwrap();
assert!(
docstring.contains("Adds two numbers"),
"docstring should contain cleaned text: {:?}",
docstring
);
assert!(
!docstring.contains("<summary>"),
"docstring should not contain XML tags: {:?}",
docstring
);
}
#[test]
fn test_cs_call_sites() {
let source = r#"
public class App
{
public void Run()
{
Console.WriteLine("hello");
Helper();
var list = new List<string>();
}
private void Helper() {}
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let call_refs: Vec<_> = result
.unresolved_refs
.iter()
.filter(|r| r.reference_kind == EdgeKind::Calls)
.collect();
assert!(
!call_refs.is_empty(),
"should have call refs, got: {:?}",
result.unresolved_refs
);
}
#[test]
fn test_cs_async_method() {
let source = r#"
public class Service
{
public async Task<string> FetchDataAsync()
{
return await Task.FromResult("data");
}
public void SyncMethod() {}
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
let async_method = methods.iter().find(|m| m.name == "FetchDataAsync").unwrap();
assert!(async_method.is_async, "FetchDataAsync should be async");
let sync_method = methods.iter().find(|m| m.name == "SyncMethod").unwrap();
assert!(!sync_method.is_async, "SyncMethod should not be async");
}
#[test]
fn test_cs_contains_edges() {
let source = r#"
public class Foo
{
private int _x;
public string Name { get; set; }
public void Bar() {}
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("test.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let contains: Vec<_> = result
.edges
.iter()
.filter(|e| e.kind == EdgeKind::Contains)
.collect();
assert!(
contains.len() >= 4,
"should have Contains edges: {}",
contains.len()
);
}
#[test]
fn test_cs_extensions() {
let extractor = CSharpExtractor;
assert_eq!(extractor.extensions(), &["cs"]);
assert_eq!(extractor.language_name(), "C#");
}
#[test]
fn test_cs_qualified_names() {
let source = r#"
namespace MyApp
{
public class Service
{
public void Run() {}
}
}
"#;
let extractor = CSharpExtractor;
let result = extractor.extract("src/Service.cs", source);
assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert_eq!(methods.len(), 1);
assert!(
methods[0].qualified_name.contains("MyApp"),
"qualified_name should contain namespace: {}",
methods[0].qualified_name
);
assert!(
methods[0].qualified_name.contains("Service"),
"qualified_name should contain class: {}",
methods[0].qualified_name
);
assert!(
methods[0].qualified_name.contains("Run"),
"qualified_name should contain method: {}",
methods[0].qualified_name
);
}