#![cfg(all(
feature = "lang-bash",
feature = "lang-batch",
feature = "lang-cobol",
feature = "lang-dart",
feature = "lang-fortran",
feature = "lang-gwbasic",
feature = "lang-lua",
feature = "lang-msbasic2",
feature = "lang-nix",
feature = "lang-objc",
feature = "lang-pascal",
feature = "lang-perl",
feature = "lang-php",
feature = "lang-powershell",
feature = "lang-protobuf",
feature = "lang-qbasic",
feature = "lang-ruby",
feature = "lang-vbnet",
feature = "lang-zig"
))]
use tokensave::extraction::LanguageExtractor;
use tokensave::types::*;
fn read_fixture(name: &str) -> String {
let path = format!("tests/fixtures/{}", name);
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("Failed to read {}: {}", path, e))
}
#[test]
fn test_fixture_typescript() {
let source = read_fixture("sample.ts");
let extractor = tokensave::extraction::TypeScriptExtractor;
let result = extractor.extract("sample.ts", &source);
assert!(result.errors.is_empty(), "TS errors: {:?}", result.errors);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let imports: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert!(
imports.len() >= 2,
"expected >= 2 imports, got {}",
imports.len()
);
let consts: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Const)
.collect();
assert!(consts.iter().any(|n| n.name == "MAX_RETRIES"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::TypeAlias && n.name == "UserId"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Interface && n.name == "IUser"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Enum && n.name == "Role"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Function && n.name == "log"));
let class = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Class && n.name == "UserService");
assert!(class.is_some(), "UserService class not found");
assert_eq!(class.unwrap().visibility, Visibility::Pub);
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert!(methods.len() >= 2, "expected >= 2 methods");
let fetch = methods.iter().find(|m| m.name == "fetchProfile");
assert!(fetch.is_some(), "fetchProfile method not found");
assert!(fetch.unwrap().is_async, "fetchProfile should be async");
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::ArrowFunction && n.name == "createUser"));
assert!(
!result.unresolved_refs.is_empty(),
"expected call site refs"
);
assert!(result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls));
assert!(result.edges.iter().any(|e| e.kind == EdgeKind::Contains));
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Extends),
"expected Extends ref for UserService"
);
}
#[test]
fn test_fixture_javascript() {
let source = read_fixture("sample.js");
let extractor = tokensave::extraction::TypeScriptExtractor;
let result = extractor.extract("sample.js", &source);
assert!(result.errors.is_empty(), "JS errors: {:?}", result.errors);
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Handler"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "JsonHandler"));
let fetch_fn = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "fetchData");
assert!(fetch_fn.is_some());
assert!(fetch_fn.unwrap().is_async);
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::ArrowFunction && n.name == "double"));
}
#[test]
fn test_fixture_javascript_mjs() {
let source = read_fixture("sample.mjs");
let extractor = tokensave::extraction::TypeScriptExtractor;
let result = extractor.extract("sample.mjs", &source);
assert!(result.errors.is_empty(), "MJS errors: {:?}", result.errors);
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::ArrowFunction && n.name == "add"));
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Function && n.name.contains("test adds two numbers")),
"missing node:test callback scope: {:?}",
result.nodes.iter().map(|n| &n.name).collect::<Vec<_>>()
);
}
#[test]
fn test_fixture_python() {
let source = read_fixture("sample.py");
let extractor = tokensave::extraction::PythonExtractor;
let result = extractor.extract("sample.py", &source);
assert!(
result.errors.is_empty(),
"Python errors: {:?}",
result.errors
);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let imports: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert!(
imports.len() >= 3,
"expected >= 3 imports, got {}",
imports.len()
);
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Const && n.name == "MAX_CONNECTIONS"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Const && n.name == "DEFAULT_TIMEOUT"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Function && n.name == "log"));
let log_fn = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "log")
.unwrap();
assert!(log_fn.docstring.is_some(), "log() should have docstring");
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Decorator));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Base"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Connection"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Pool"));
let conn = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Class && n.name == "Connection")
.unwrap();
assert!(conn.docstring.is_some(), "Connection should have docstring");
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert!(
methods.len() >= 5,
"expected >= 5 methods, got {}",
methods.len()
);
let connect = methods.iter().find(|m| m.name == "connect");
assert!(connect.is_some(), "connect method not found");
assert!(connect.unwrap().is_async, "connect should be async");
let internal = methods.iter().find(|m| m.name == "_internal_method");
assert!(internal.is_some());
assert_eq!(internal.unwrap().visibility, Visibility::Private);
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Config"));
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Extends),
"expected Extends refs for class inheritance"
);
assert!(result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls));
let log_sig = log_fn.signature.as_ref().unwrap();
assert!(
log_sig.contains("message"),
"log signature should contain 'message', got: {}",
log_sig
);
let retry_fn = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "retry")
.expect("retry function not found");
let wrapper_fn = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "wrapper")
.expect("nested closure `wrapper` should be indexed as a Function");
assert!(
result.edges.iter().any(|e| e.kind == EdgeKind::Contains
&& e.source == retry_fn.id
&& e.target == wrapper_fn.id),
"retry should Contains wrapper"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Uses && r.reference_name == "log"),
"dict value referencing `log` should produce a Uses ref"
);
}
#[test]
fn test_fixture_c() {
let source = read_fixture("sample.c");
let extractor = tokensave::extraction::CExtractor;
let result = extractor.extract("sample.c", &source);
assert!(result.errors.is_empty(), "C errors: {:?}", result.errors);
let includes: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Include)
.collect();
assert!(includes.len() >= 3, "expected >= 3 includes");
let defs: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::PreprocessorDef)
.collect();
assert!(defs.iter().any(|n| n.name == "MAX_BUFFER_SIZE"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Typedef && n.name == "Point"));
let fields: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Field)
.collect();
assert!(fields.len() >= 2, "expected struct fields");
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Union));
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Enum));
let variants: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::EnumVariant)
.collect();
assert!(variants.len() >= 4, "expected >= 4 enum variants");
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Typedef && n.name == "Callback"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Function && n.name == "point_distance"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Function && n.name == "main"));
let set_err = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "set_error");
assert!(set_err.is_some());
assert_eq!(set_err.unwrap().visibility, Visibility::Private);
let dist_fn = result
.nodes
.iter()
.find(|n| n.name == "point_distance")
.unwrap();
assert!(
dist_fn.docstring.is_some(),
"point_distance should have docstring"
);
assert!(result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls));
}
#[test]
fn test_fixture_c_header() {
let source = read_fixture("sample.h");
let extractor = tokensave::extraction::CExtractor;
let result = extractor.extract("sample.h", &source);
assert!(
result.errors.is_empty(),
"C header errors: {:?}",
result.errors
);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::PreprocessorDef && n.name == "API_VERSION"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Typedef && n.name == "Rect"));
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Enum));
}
#[test]
fn test_fixture_cpp() {
let source = read_fixture("sample.cpp");
let extractor = tokensave::extraction::CppExtractor;
let result = extractor.extract("sample.cpp", &source);
assert!(result.errors.is_empty(), "C++ errors: {:?}", result.errors);
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Namespace && n.name == "geom"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Struct && n.name == "Vec2"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Shape"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Circle"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Rectangle"));
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Template));
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert!(methods.len() >= 4, "expected >= 4 methods");
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Enum));
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Union));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Typedef && n.name == "EntityId"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Typedef && n.name == "EntityId"));
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Include));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::PreprocessorDef && n.name == "DEFAULT_CAPACITY"));
let helper = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "internal_helper");
assert!(helper.is_some());
assert_eq!(helper.unwrap().visibility, Visibility::Private);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Extends),
"expected Extends refs for class inheritance"
);
assert!(result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls));
}
#[test]
fn test_fixture_kotlin() {
let source = read_fixture("sample.kt");
let extractor = tokensave::extraction::KotlinExtractor;
let result = extractor.extract("sample.kt", &source);
assert!(
result.errors.is_empty(),
"Kotlin errors: {:?}",
result.errors
);
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::KotlinPackage));
let imports: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert!(imports.len() >= 2, "expected >= 2 imports");
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::DataClass && n.name == "Point"));
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::SealedClass));
let iface = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Interface || n.kind == NodeKind::Trait);
assert!(iface.is_some(), "Repository interface not found");
let has_annotation = result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Decorator || n.kind == NodeKind::AnnotationUsage);
assert!(has_annotation, "expected annotation nodes");
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Entity"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "User"));
let properties: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Property)
.collect();
assert!(properties.len() >= 2, "expected >= 2 properties");
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::CompanionObject));
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Enum));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::KotlinObject && n.name == "Logger"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Function && n.name.contains("toSlug")));
let helper = result.nodes.iter().find(|n| n.name == "helperFunction");
if let Some(h) = helper {
assert_eq!(
h.visibility,
Visibility::PubSuper,
"protected should be PubSuper"
);
}
assert!(result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls));
}
#[cfg(feature = "lang-dart")]
#[test]
fn test_fixture_dart() {
let source = read_fixture("sample.dart");
let extractor = tokensave::extraction::DartExtractor;
let result = extractor.extract("sample.dart", &source);
assert!(result.errors.is_empty(), "Dart errors: {:?}", result.errors);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Library));
let imports: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert!(imports.len() >= 2, "expected >= 2 imports");
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Enum));
let serializable = result.nodes.iter().find(|n| n.name == "Serializable");
assert!(serializable.is_some(), "Serializable not found");
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Mixin && n.name == "Timestamped"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "User"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Extension && n.name == "StringUtils"));
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert!(methods.len() >= 2, "expected >= 2 methods");
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Constructor));
let privates: Vec<_> = result
.nodes
.iter()
.filter(|n| n.visibility == Visibility::Private)
.collect();
assert!(!privates.is_empty(), "expected private members");
let process = result.nodes.iter().find(|n| n.name == "processUsers");
if let Some(p) = process {
assert!(p.is_async, "processUsers should be async");
}
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::TypeAlias));
assert!(result.edges.iter().any(|e| e.kind == EdgeKind::Contains));
}
#[test]
fn test_fixture_csharp() {
let source = read_fixture("sample.cs");
let extractor = tokensave::extraction::CSharpExtractor;
let result = extractor.extract("sample.cs", &source);
assert!(result.errors.is_empty(), "C# errors: {:?}", result.errors);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Namespace));
let usings: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert!(usings.len() >= 3, "expected >= 3 using directives");
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Enum));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Record && n.name == "AppConfig"));
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Delegate));
let ifaces: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Interface)
.collect();
assert!(ifaces.len() >= 2, "expected >= 2 interfaces");
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::AnnotationUsage || n.kind == NodeKind::Decorator));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Entity"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "User"));
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert!(methods.len() >= 3, "expected >= 3 methods");
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Constructor));
let props: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::CSharpProperty)
.collect();
assert!(props.len() >= 2, "expected >= 2 properties");
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Event));
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Field));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Struct && n.name == "Point"));
assert!(result
.nodes
.iter()
.any(|n| n.visibility == Visibility::Private));
assert!(result
.nodes
.iter()
.any(|n| n.visibility == Visibility::PubCrate));
let fetch = methods.iter().find(|m| m.name == "FetchProfileAsync");
if let Some(f) = fetch {
assert!(f.is_async, "FetchProfileAsync should be async");
}
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Extends
|| r.reference_kind == EdgeKind::Implements),
"expected inheritance refs"
);
assert!(result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls));
}
#[cfg(feature = "lang-php")]
#[test]
fn test_fixture_php() {
let source = read_fixture("sample.php");
let extractor = tokensave::extraction::PhpExtractor;
let result = extractor.extract("sample.php", &source);
assert!(result.errors.is_empty(), "PHP errors: {:?}", result.errors);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
assert!(
result.nodes.iter().any(|n| n.kind == NodeKind::Module),
"expected a namespace/module node"
);
let imports: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert!(
imports.len() >= 2,
"expected >= 2 Use nodes (trait uses), got {}",
imports.len()
);
let traits: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Trait)
.collect();
assert!(
traits.len() >= 2,
"expected >= 2 Trait nodes (interface + trait), got {}",
traits.len()
);
assert!(
traits.iter().any(|n| n.name == "ConnectionInterface"),
"ConnectionInterface not found"
);
assert!(
traits.iter().any(|n| n.name == "Timestamps"),
"Timestamps trait not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Connection"),
"Connection class not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Pool"),
"Pool class not found"
);
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert!(
methods.len() >= 3,
"expected >= 3 methods, got {}",
methods.len()
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Enum && n.name == "ConnectionState"),
"ConnectionState enum not found"
);
let fields: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Field)
.collect();
assert!(!fields.is_empty(), "expected property/field nodes");
assert!(
result
.nodes
.iter()
.any(|n| n.visibility == Visibility::Private),
"expected at least one private member"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Extends),
"expected Extends ref for Pool extends Connection"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
assert!(
result.edges.iter().any(|e| e.kind == EdgeKind::Contains),
"expected Contains edges"
);
}
#[cfg(feature = "lang-pascal")]
#[test]
fn test_fixture_pascal() {
let source = read_fixture("sample.pas");
let extractor = tokensave::extraction::PascalExtractor;
let result = extractor.extract("sample.pas", &source);
assert!(
result.errors.is_empty(),
"Pascal errors: {:?}",
result.errors
);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::PascalUnit));
let uses: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert!(uses.len() >= 2, "expected >= 2 uses");
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Const && n.name == "MaxRetries"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::PascalRecord && n.name == "TPoint"));
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Interface));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "TEntity"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "TUser"));
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::Constructor));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Function && n.name == "PointDistance"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Procedure && n.name == "LogMessage"));
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert!(methods.len() >= 2, "expected >= 2 methods");
let properties: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Property)
.collect();
assert!(!properties.is_empty(), "expected >= 1 property");
assert!(result
.nodes
.iter()
.any(|n| n.visibility == Visibility::Private));
assert!(result.edges.iter().any(|e| e.kind == EdgeKind::Contains));
}
#[cfg(feature = "lang-ruby")]
#[test]
fn test_fixture_ruby() {
let source = read_fixture("sample.rb");
let extractor = tokensave::extraction::RubyExtractor;
let result = extractor.extract("sample.rb", &source);
assert!(result.errors.is_empty(), "Ruby errors: {:?}", result.errors);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Module && n.name == "Networking"),
"Networking module not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Const && n.name == "MAX_CONNECTIONS"),
"MAX_CONNECTIONS constant not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Const && n.name == "DEFAULT_TIMEOUT"),
"DEFAULT_TIMEOUT constant not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Base"),
"Base class not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Connection"),
"Connection class not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Pool"),
"Pool class not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Config"),
"nested Config class not found"
);
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert!(
methods.len() >= 3,
"expected >= 3 methods, got {}",
methods.len()
);
assert!(
result.nodes.iter().any(
|n| (n.kind == NodeKind::Function || n.kind == NodeKind::Method) && n.name == "log"
),
"log function/method not found"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Extends),
"expected Extends refs for class inheritance"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
assert!(result.edges.iter().any(|e| e.kind == EdgeKind::Contains));
}
#[test]
fn test_fixture_swift() {
let source = read_fixture("sample.swift");
let extractor = tokensave::extraction::SwiftExtractor;
let result = extractor.extract("sample.swift", &source);
assert!(
result.errors.is_empty(),
"Swift errors: {:?}",
result.errors
);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let imports: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert!(
imports.len() >= 2,
"expected >= 2 imports, got {}",
imports.len()
);
assert!(imports.iter().any(|n| n.name == "Foundation"));
assert!(imports.iter().any(|n| n.name == "UIKit"));
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Const && n.name == "maxConnections"),
"maxConnections constant not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::TypeAlias && n.name == "CompletionHandler"),
"CompletionHandler typealias not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Enum && n.name == "LogLevel"),
"LogLevel enum not found"
);
let variants: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::EnumVariant)
.collect();
assert!(
variants.len() >= 4,
"expected >= 4 enum variants, got {}",
variants.len()
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Interface && n.name == "Serializable"),
"Serializable protocol not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Base"),
"Base class not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Connection"),
"Connection class not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Struct && n.name == "Point"),
"Point struct not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Extension && n.name == "String"),
"String extension not found"
);
assert!(
result.nodes.iter().any(|n| n.kind == NodeKind::Constructor),
"expected at least one Constructor node"
);
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert!(
methods.len() >= 3,
"expected >= 3 methods, got {}",
methods.len()
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Function && n.name == "processUsers"),
"processUsers function not found"
);
let props: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Property)
.collect();
assert!(
props.len() >= 2,
"expected >= 2 properties, got {}",
props.len()
);
let base = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Class && n.name == "Base")
.unwrap();
assert!(base.docstring.is_some(), "Base class should have docstring");
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Extends),
"expected Extends refs for class inheritance"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Extends && r.reference_name == "Base"),
"expected Extends ref to Base"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
assert!(result.edges.iter().any(|e| e.kind == EdgeKind::Contains));
let connect = result.nodes.iter().find(|n| n.name == "connect");
if let Some(c) = connect {
assert!(c.is_async, "connect should be async");
}
assert!(
result
.nodes
.iter()
.any(|n| n.visibility == Visibility::Private),
"expected at least one private member"
);
}
#[cfg(feature = "lang-bash")]
#[test]
fn test_fixture_bash() {
let source = read_fixture("sample.sh");
let extractor = tokensave::extraction::BashExtractor;
let result = extractor.extract("sample.sh", &source);
assert!(result.errors.is_empty(), "Bash errors: {:?}", result.errors);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let fns: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Function)
.collect();
assert_eq!(fns.len(), 5, "expected 5 functions, got {}", fns.len());
assert!(fns.iter().any(|n| n.name == "log"));
assert!(fns.iter().any(|n| n.name == "validate_config"));
assert!(fns.iter().any(|n| n.name == "connect"));
assert!(fns.iter().any(|n| n.name == "disconnect"));
assert!(fns.iter().any(|n| n.name == "main"));
let consts: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Const)
.collect();
assert_eq!(consts.len(), 2, "expected 2 consts, got {}", consts.len());
assert!(consts.iter().any(|n| n.name == "MAX_RETRIES"));
assert!(consts.iter().any(|n| n.name == "DEFAULT_PORT"));
let uses: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert_eq!(uses.len(), 1, "expected 1 Use node, got {}", uses.len());
assert_eq!(uses[0].name, "./utils.sh");
let log_fn = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "log")
.unwrap();
assert!(log_fn.docstring.is_some(), "log should have docstring");
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
assert!(result.edges.iter().any(|e| e.kind == EdgeKind::Contains));
}
#[cfg(feature = "lang-lua")]
#[test]
fn test_fixture_lua() {
let source = read_fixture("sample.lua");
let extractor = tokensave::extraction::LuaExtractor;
let result = extractor.extract("sample.lua", &source);
assert!(result.errors.is_empty(), "Lua errors: {:?}", result.errors);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let uses: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert_eq!(uses.len(), 2, "expected 2 Use nodes, got {}", uses.len());
assert!(uses.iter().any(|n| n.name == "json"));
assert!(uses.iter().any(|n| n.name == "socket"));
let consts: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Const)
.collect();
assert_eq!(consts.len(), 2, "expected 2 consts, got {}", consts.len());
assert!(consts.iter().any(|n| n.name == "MAX_RETRIES"));
assert!(consts.iter().any(|n| n.name == "DEFAULT_PORT"));
let fns: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Function)
.collect();
assert_eq!(fns.len(), 3, "expected 3 functions, got {}", fns.len());
assert!(fns.iter().any(|n| n.name == "log"));
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert_eq!(
methods.len(),
5,
"expected 5 methods, got {}",
methods.len()
);
assert!(methods.iter().any(|n| n.name == "connect"));
assert!(methods.iter().any(|n| n.name == "disconnect"));
assert!(methods.iter().any(|n| n.name == "isConnected"));
assert!(methods.iter().any(|n| n.name == "acquire"));
assert!(methods.iter().any(|n| n.name == "release"));
let lua_log_fn = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "log")
.unwrap();
assert!(lua_log_fn.docstring.is_some(), "log should have docstring");
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
assert!(result.edges.iter().any(|e| e.kind == EdgeKind::Contains));
}
#[cfg(feature = "lang-zig")]
#[test]
fn test_fixture_zig() {
let source = read_fixture("sample.zig");
let extractor = tokensave::extraction::ZigExtractor;
let result = extractor.extract("sample.zig", &source);
assert!(result.errors.is_empty(), "Zig errors: {:?}", result.errors);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let imports: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert_eq!(
imports.len(),
2,
"expected 2 imports, got {}",
imports.len()
);
assert!(imports.iter().any(|n| n.name == "std"));
let consts: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Const)
.collect();
assert!(
consts.iter().any(|n| n.name == "max_connections"),
"max_connections constant not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Enum && n.name == "LogLevel"),
"LogLevel enum not found"
);
let variants: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::EnumVariant)
.collect();
assert_eq!(
variants.len(),
4,
"expected 4 enum variants, got {}",
variants.len()
);
assert!(variants.iter().any(|v| v.name == "debug"));
assert!(variants.iter().any(|v| v.name == "info"));
assert!(variants.iter().any(|v| v.name == "warning"));
assert!(variants.iter().any(|v| v.name == "err"));
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Struct && n.name == "Point"),
"Point struct not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Struct && n.name == "Connection"),
"Connection struct not found"
);
let fields: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Field)
.collect();
assert!(
fields.len() >= 5,
"expected >= 5 fields, got {}",
fields.len()
);
assert!(fields.iter().any(|f| f.name == "x"));
assert!(fields.iter().any(|f| f.name == "host"));
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert!(
methods.len() >= 6,
"expected >= 6 methods, got {}",
methods.len()
);
assert!(methods.iter().any(|m| m.name == "distance"));
assert!(methods.iter().any(|m| m.name == "origin"));
assert!(methods.iter().any(|m| m.name == "init"));
assert!(methods.iter().any(|m| m.name == "connect"));
assert!(methods.iter().any(|m| m.name == "disconnect"));
assert!(methods.iter().any(|m| m.name == "isConnected"));
let fns: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Function)
.collect();
assert!(
fns.iter().any(|f| f.name == "log"),
"log function not found"
);
assert!(
fns.iter().any(|f| f.name == "processConnections"),
"processConnections function not found"
);
assert!(
fns.iter().any(|f| f.name == "point distance"),
"test 'point distance' not found"
);
let log_fn = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "log")
.unwrap();
assert_eq!(log_fn.visibility, Visibility::Pub, "log should be pub");
let process_fn = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "processConnections")
.unwrap();
assert_eq!(
process_fn.visibility,
Visibility::Pub,
"processConnections should be pub"
);
let point = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Struct && n.name == "Point")
.unwrap();
assert!(point.docstring.is_some(), "Point should have docstring");
assert!(point.docstring.as_ref().unwrap().contains("2D point"));
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
assert!(result.edges.iter().any(|e| e.kind == EdgeKind::Contains));
}
#[cfg(feature = "lang-protobuf")]
#[test]
fn test_fixture_proto() {
let source = read_fixture("sample.proto");
let extractor = tokensave::extraction::ProtoExtractor;
let result = extractor.extract("sample.proto", &source);
assert!(
result.errors.is_empty(),
"Proto errors: {:?}",
result.errors
);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let pkgs: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Package)
.collect();
assert_eq!(pkgs.len(), 1);
assert_eq!(pkgs[0].name, "networking");
let imports: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert_eq!(
imports.len(),
2,
"expected 2 imports, got {}",
imports.len()
);
let msgs: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::ProtoMessage)
.collect();
assert!(
msgs.len() >= 7,
"expected >= 7 messages, got {}",
msgs.len()
);
assert!(msgs.iter().any(|m| m.name == "Endpoint"));
assert!(msgs.iter().any(|m| m.name == "ConnectionConfig"));
assert!(msgs.iter().any(|m| m.name == "AuthConfig"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Enum && n.name == "LogLevel"));
let variants: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::EnumVariant)
.collect();
assert_eq!(
variants.len(),
5,
"expected 5 enum variants, got {}",
variants.len()
);
let services: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::ProtoService)
.collect();
assert_eq!(services.len(), 1);
assert_eq!(services[0].name, "ConnectionService");
let rpcs: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::ProtoRpc)
.collect();
assert_eq!(rpcs.len(), 3, "expected 3 rpcs, got {}", rpcs.len());
assert!(rpcs.iter().any(|r| r.name == "Connect"));
assert!(rpcs.iter().any(|r| r.name == "Disconnect"));
assert!(rpcs.iter().any(|r| r.name == "HealthCheck"));
let fields: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Field)
.collect();
assert!(
fields.len() >= 15,
"expected >= 15 fields, got {}",
fields.len()
);
let endpoint = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::ProtoMessage && n.name == "Endpoint")
.unwrap();
assert!(
endpoint.docstring.is_some(),
"Endpoint should have docstring"
);
let log_level = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Enum && n.name == "LogLevel")
.unwrap();
assert!(
log_level.docstring.is_some(),
"LogLevel should have docstring"
);
let contains: Vec<_> = result
.edges
.iter()
.filter(|e| e.kind == EdgeKind::Contains)
.collect();
assert!(
contains.len() >= 10,
"expected >= 10 Contains edges, got {}",
contains.len()
);
}
#[cfg(feature = "lang-nix")]
#[test]
fn test_fixture_nix() {
let source = read_fixture("sample.nix");
let extractor = tokensave::extraction::NixExtractor;
let result = extractor.extract("sample.nix", &source);
assert!(result.errors.is_empty(), "Nix errors: {:?}", result.errors);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let fns: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Function)
.collect();
assert!(
fns.iter().any(|f| f.name == "log"),
"log function not found"
);
assert!(
fns.iter().any(|f| f.name == "mkConnection"),
"mkConnection function not found"
);
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Const && n.name == "defaultPort"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Const && n.name == "maxRetries"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Module && n.name == "networking"));
assert!(fns.iter().any(|f| f.name == "mkPool"), "mkPool not found");
assert!(
fns.iter().any(|f| f.name == "validateConfig"),
"validateConfig not found"
);
let log_fn = fns.iter().find(|f| f.name == "log").unwrap();
assert!(log_fn.docstring.is_some(), "log should have docstring");
let net = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Module && n.name == "networking")
.unwrap();
assert!(net.docstring.is_some(), "networking should have docstring");
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected call refs"
);
let contains: Vec<_> = result
.edges
.iter()
.filter(|e| e.kind == EdgeKind::Contains)
.collect();
assert!(
contains.len() >= 5,
"expected >= 5 Contains edges, got {}",
contains.len()
);
let uses: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert!(
uses.iter().any(|u| u.name == "networking"),
"inherit networking Use not found"
);
for node in &result.nodes {
assert_eq!(
node.visibility,
Visibility::Pub,
"node {} should be Pub",
node.name
);
}
}
#[cfg(feature = "lang-vbnet")]
#[test]
fn test_fixture_vbnet() {
let source = read_fixture("sample.vb");
let extractor = tokensave::extraction::VbNetExtractor;
let result = extractor.extract("sample.vb", &source);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let imports: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert_eq!(
imports.len(),
2,
"expected 2 imports, got {}",
imports.len()
);
assert!(imports.iter().any(|u| u.name == "System"));
assert!(imports
.iter()
.any(|u| u.name == "System.Collections.Generic"));
let consts: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Const)
.collect();
assert!(
consts.iter().any(|c| c.name == "MaxConnections"),
"MaxConnections const not found"
);
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Enum && n.name == "LogLevel"));
let variants: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::EnumVariant)
.collect();
assert!(
variants.len() >= 4,
"expected >= 4 enum variants, got {}",
variants.len()
);
assert!(variants.iter().any(|v| v.name == "Debug"));
assert!(variants.iter().any(|v| v.name == "Info"));
assert!(variants.iter().any(|v| v.name == "Warning"));
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Interface && n.name == "ISerializable"),
"ISerializable interface not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Base"),
"Base class not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Connection"),
"Connection class not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Struct && n.name == "Point"),
"Point struct not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Module && n.name == "Helpers"),
"Helpers module not found"
);
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert!(
methods.len() >= 5,
"expected >= 5 methods, got {}",
methods.len()
);
assert!(
result.nodes.iter().any(|n| n.kind == NodeKind::Constructor),
"expected at least one Constructor node"
);
let props: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Property)
.collect();
assert!(
props.len() >= 2,
"expected >= 2 properties, got {}",
props.len()
);
let base = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Class && n.name == "Base")
.unwrap();
assert!(base.docstring.is_some(), "Base class should have docstring");
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Extends && r.reference_name == "Base"),
"expected Extends ref to Base"
);
assert!(
result.unresolved_refs.iter().any(
|r| r.reference_kind == EdgeKind::Implements && r.reference_name == "ISerializable"
),
"expected Implements ref to ISerializable"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
assert!(result.edges.iter().any(|e| e.kind == EdgeKind::Contains));
}
#[cfg(feature = "lang-powershell")]
#[test]
fn test_fixture_powershell() {
let source = read_fixture("sample.ps1");
let extractor = tokensave::extraction::PowerShellExtractor;
let result = extractor.extract("sample.ps1", &source);
assert!(
result.errors.is_empty(),
"PowerShell errors: {:?}",
result.errors
);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let fns: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Function)
.collect();
assert_eq!(fns.len(), 5, "expected 5 functions, got {}", fns.len());
assert!(fns.iter().any(|n| n.name == "Write-Log"));
assert!(fns.iter().any(|n| n.name == "Test-Config"));
assert!(fns.iter().any(|n| n.name == "Connect-Server"));
assert!(fns.iter().any(|n| n.name == "Disconnect-Server"));
assert!(fns.iter().any(|n| n.name == "Main"));
let consts: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Const)
.collect();
assert_eq!(consts.len(), 2, "expected 2 consts, got {}", consts.len());
assert!(consts.iter().any(|n| n.name == "MaxRetries"));
assert!(consts.iter().any(|n| n.name == "DefaultPort"));
let uses: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert_eq!(uses.len(), 2, "expected 2 Use nodes, got {}", uses.len());
assert!(uses.iter().any(|n| n.name == "ActiveDirectory"));
assert!(uses.iter().any(|n| n.name.contains("Utils.ps1")));
let write_log = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "Write-Log")
.unwrap();
assert!(
write_log.docstring.is_some(),
"Write-Log should have docstring"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
assert!(result.edges.iter().any(|e| e.kind == EdgeKind::Contains));
}
#[cfg(feature = "lang-batch")]
#[test]
fn test_fixture_batch() {
let source = read_fixture("sample.bat");
let extractor = tokensave::extraction::BatchExtractor;
let result = extractor.extract("sample.bat", &source);
assert!(
result.errors.is_empty(),
"Batch errors: {:?}",
result.errors
);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let fns: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Function)
.collect();
assert_eq!(fns.len(), 5, "expected 5 functions, got {}", fns.len());
assert!(fns.iter().any(|n| n.name == "Log"));
assert!(fns.iter().any(|n| n.name == "ValidateConfig"));
assert!(fns.iter().any(|n| n.name == "Connect"));
assert!(fns.iter().any(|n| n.name == "Disconnect"));
assert!(fns.iter().any(|n| n.name == "Main"));
let consts: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Const)
.collect();
assert_eq!(consts.len(), 2, "expected 2 consts, got {}", consts.len());
assert!(consts.iter().any(|n| n.name == "MAX_RETRIES"));
assert!(consts.iter().any(|n| n.name == "DEFAULT_PORT"));
let log_fn = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "Log")
.unwrap();
assert!(log_fn.docstring.is_some(), "Log should have docstring");
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
assert!(result.edges.iter().any(|e| e.kind == EdgeKind::Contains));
}
#[cfg(feature = "lang-perl")]
#[test]
fn test_fixture_perl() {
let source = read_fixture("sample.pl");
let extractor = tokensave::extraction::PerlExtractor;
let result = extractor.extract("sample.pl", &source);
assert!(result.errors.is_empty(), "Perl errors: {:?}", result.errors);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let imports: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Use)
.collect();
assert_eq!(
imports.len(),
4,
"expected 4 imports, got {}",
imports.len()
);
assert!(imports.iter().any(|n| n.name == "strict"));
assert!(imports.iter().any(|n| n.name == "warnings"));
assert!(imports.iter().any(|n| n.name == "File::Path"));
assert!(imports.iter().any(|n| n.name == "Carp"));
let consts: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Const)
.collect();
assert_eq!(consts.len(), 2, "expected 2 consts, got {}", consts.len());
assert!(consts.iter().any(|n| n.name == "MAX_RETRIES"));
assert!(consts.iter().any(|n| n.name == "DEFAULT_PORT"));
let modules: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Module)
.collect();
assert_eq!(
modules.len(),
2,
"expected 2 modules, got {}",
modules.len()
);
assert!(modules.iter().any(|n| n.name == "Connection"));
assert!(modules.iter().any(|n| n.name == "Pool"));
let fns: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Function)
.collect();
assert_eq!(fns.len(), 2, "expected 2 functions, got {}", fns.len());
assert!(fns.iter().any(|n| n.name == "log_message"));
assert!(fns.iter().any(|n| n.name == "validate_config"));
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert_eq!(
methods.len(),
7,
"expected 7 methods, got {}",
methods.len()
);
assert!(methods.iter().any(|n| n.name == "connect"));
assert!(methods.iter().any(|n| n.name == "disconnect"));
assert!(methods.iter().any(|n| n.name == "is_connected"));
assert!(methods.iter().any(|n| n.name == "acquire"));
assert!(methods.iter().any(|n| n.name == "release"));
let log_fn = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "log_message")
.unwrap();
assert!(
log_fn.docstring.is_some(),
"log_message should have docstring"
);
let max_retries = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Const && n.name == "MAX_RETRIES")
.unwrap();
assert!(
max_retries.docstring.is_some(),
"MAX_RETRIES should have docstring"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
let contains: Vec<_> = result
.edges
.iter()
.filter(|e| e.kind == EdgeKind::Contains)
.collect();
assert!(
contains.len() >= 15,
"expected >= 15 Contains edges, got {}",
contains.len()
);
}
#[cfg(feature = "lang-objc")]
#[test]
fn test_fixture_objc() {
let source = read_fixture("sample.m");
let extractor = tokensave::extraction::ObjcExtractor;
let result = extractor.extract("sample.m", &source);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let includes: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Include)
.collect();
assert_eq!(
includes.len(),
2,
"expected 2 includes, got {}",
includes.len()
);
let defs: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::PreprocessorDef)
.collect();
assert_eq!(defs.len(), 2, "expected 2 preprocessor defs");
assert!(defs.iter().any(|n| n.name == "MAX_RETRIES"));
assert!(defs.iter().any(|n| n.name == "DEFAULT_PORT"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Enum && n.name == "LogLevel"));
let variants: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::EnumVariant)
.collect();
assert_eq!(variants.len(), 4, "expected 4 enum variants");
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Interface && n.name == "Serializable"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Base"));
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Class && n.name == "Connection"));
let base = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Class && n.name == "Base")
.unwrap();
assert!(base.docstring.is_some(), "Base should have docstring");
let impls: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Impl)
.collect();
assert_eq!(impls.len(), 2, "expected 2 implementation blocks");
let props: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Property)
.collect();
assert!(
props.len() >= 3,
"expected >= 3 properties, got {}",
props.len()
);
let methods: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Method)
.collect();
assert!(
methods.len() >= 6,
"expected >= 6 methods, got {}",
methods.len()
);
assert!(result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Function && n.name == "logMessage"));
let log_fn = result
.nodes
.iter()
.find(|n| n.kind == NodeKind::Function && n.name == "logMessage")
.unwrap();
assert!(
log_fn.docstring.is_some(),
"logMessage should have docstring"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Extends && r.reference_name == "NSObject"),
"expected Extends ref to NSObject"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Extends && r.reference_name == "Base"),
"expected Extends ref to Base"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Implements),
"expected Implements refs for protocol conformance"
);
assert!(result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls));
let contains: Vec<_> = result
.edges
.iter()
.filter(|e| e.kind == EdgeKind::Contains)
.collect();
assert!(
contains.len() >= 15,
"expected >= 15 Contains edges, got {}",
contains.len()
);
}
#[cfg(feature = "lang-fortran")]
#[test]
fn test_fixture_fortran() {
let source = read_fixture("sample.f90");
let extractor = tokensave::extraction::FortranExtractor;
let result = extractor.extract("sample.f90", &source);
assert!(
result.errors.is_empty(),
"Fortran errors: {:?}",
result.errors
);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Module && n.name == "networking"),
"networking module not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Function && n.name == "main"),
"program main not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Const && n.name == "MAX_RETRIES"),
"MAX_RETRIES constant not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Const && n.name == "DEFAULT_PORT"),
"DEFAULT_PORT constant not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Struct && n.name == "Endpoint"),
"Endpoint type not found"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Struct && n.name == "PooledEndpoint"),
"PooledEndpoint type not found"
);
let fields: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Field)
.collect();
assert!(
fields.len() >= 4,
"expected >= 4 fields, got {}",
fields.len()
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Interface && n.name == "Connectable"),
"Connectable interface not found"
);
let fns: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Function)
.collect();
assert!(
fns.iter().any(|f| f.name == "log_message"),
"log_message not found"
);
assert!(
fns.iter().any(|f| f.name == "create_endpoint"),
"create_endpoint not found"
);
assert!(
fns.iter().any(|f| f.name == "connect_endpoint"),
"connect_endpoint not found"
);
assert!(
fns.iter().any(|f| f.name == "disconnect_endpoint"),
"disconnect_endpoint not found"
);
assert!(
fns.iter().any(|f| f.name == "is_connected"),
"is_connected not found"
);
let log_msg = fns.iter().find(|f| f.name == "log_message").unwrap();
assert!(
log_msg.docstring.is_some(),
"log_message should have docstring"
);
assert!(
result
.nodes
.iter()
.any(|n| n.kind == NodeKind::Use && n.name == "networking"),
"use networking not found"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Extends && r.reference_name == "Endpoint"),
"expected Extends ref for PooledEndpoint -> Endpoint"
);
assert!(result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls));
let contains: Vec<_> = result
.edges
.iter()
.filter(|e| e.kind == EdgeKind::Contains)
.collect();
assert!(
contains.len() >= 5,
"expected >= 5 Contains edges, got {}",
contains.len()
);
}
#[cfg(feature = "lang-cobol")]
#[test]
fn test_fixture_cobol() {
let source = read_fixture("sample.cob");
let extractor = tokensave::extraction::CobolExtractor;
let result = extractor.extract("sample.cob", &source);
assert!(
result.errors.is_empty(),
"COBOL errors: {:?}",
result.errors
);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let modules: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Module)
.collect();
assert_eq!(modules.len(), 1, "expected 1 module, got {}", modules.len());
assert_eq!(modules[0].name, "NETWORKING");
let fns: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Function)
.collect();
assert_eq!(fns.len(), 5, "expected 5 functions, got {}", fns.len());
assert!(
fns.iter().any(|f| f.name == "MAIN-PROGRAM"),
"MAIN-PROGRAM not found"
);
assert!(
fns.iter().any(|f| f.name == "VALIDATE-CONFIG"),
"VALIDATE-CONFIG not found"
);
assert!(
fns.iter().any(|f| f.name == "LOG-MESSAGE"),
"LOG-MESSAGE not found"
);
assert!(
fns.iter().any(|f| f.name == "CONNECT-SERVER"),
"CONNECT-SERVER not found"
);
assert!(
fns.iter().any(|f| f.name == "DISCONNECT-SERVER"),
"DISCONNECT-SERVER not found"
);
let consts: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Const)
.collect();
assert!(
consts.iter().any(|c| c.name == "WS-MAX-RETRIES"),
"WS-MAX-RETRIES const not found"
);
assert!(
consts.iter().any(|c| c.name == "WS-DEFAULT-PORT"),
"WS-DEFAULT-PORT const not found"
);
let fields: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Field)
.collect();
assert!(
fields.iter().any(|f| f.name == "WS-HOST"),
"WS-HOST field not found"
);
let validate = fns.iter().find(|f| f.name == "VALIDATE-CONFIG").unwrap();
assert!(
validate.docstring.is_some(),
"VALIDATE-CONFIG should have docstring"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
let contains: Vec<_> = result
.edges
.iter()
.filter(|e| e.kind == EdgeKind::Contains)
.collect();
assert!(
contains.len() >= 10,
"expected >= 10 Contains edges, got {}",
contains.len()
);
}
#[cfg(feature = "lang-msbasic2")]
#[test]
fn test_fixture_msbasic2() {
let source = read_fixture("sample.bas");
let extractor = tokensave::extraction::MsBasic2Extractor;
let result = extractor.extract("sample.bas", &source);
assert!(
result.errors.is_empty(),
"MS BASIC 2.0 errors: {:?}",
result.errors
);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let consts: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Const)
.collect();
assert_eq!(consts.len(), 2, "expected 2 consts, got {}", consts.len());
assert!(consts.iter().any(|c| c.name == "MR"), "MR const not found");
assert!(consts.iter().any(|c| c.name == "DP"), "DP const not found");
let fns: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Function)
.collect();
assert_eq!(fns.len(), 3, "expected 3 functions, got {}", fns.len());
assert!(
fns.iter().any(|f| f.name == "LOG_A_MESSAGE"),
"LOG_A_MESSAGE not found"
);
assert!(
fns.iter().any(|f| f.name == "CONNECT_TO_SERVER"),
"CONNECT_TO_SERVER not found"
);
assert!(
fns.iter().any(|f| f.name == "DISCONNECT"),
"DISCONNECT not found"
);
let log_fn = fns.iter().find(|f| f.name == "LOG_A_MESSAGE").unwrap();
assert!(
log_fn.docstring.is_some(),
"LOG_A_MESSAGE should have docstring"
);
let connect_fn = fns.iter().find(|f| f.name == "CONNECT_TO_SERVER").unwrap();
assert!(
connect_fn.loops >= 1,
"CONNECT_TO_SERVER should have >= 1 loop"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
let contains: Vec<_> = result
.edges
.iter()
.filter(|e| e.kind == EdgeKind::Contains)
.collect();
assert!(
contains.len() >= 5,
"expected >= 5 Contains edges, got {}",
contains.len()
);
}
#[cfg(feature = "lang-gwbasic")]
#[test]
fn test_fixture_gwbasic() {
let source = read_fixture("sample.gw");
let extractor = tokensave::extraction::GwBasicExtractor;
let result = extractor.extract("sample.gw", &source);
assert!(
result.errors.is_empty(),
"GW-BASIC errors: {:?}",
result.errors
);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let consts: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Const)
.collect();
assert_eq!(consts.len(), 2, "expected 2 consts, got {}", consts.len());
assert!(consts.iter().any(|c| c.name == "MR"), "MR const not found");
assert!(consts.iter().any(|c| c.name == "DP"), "DP const not found");
let fns: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Function)
.collect();
assert!(fns.len() >= 4, "expected >= 4 functions, got {}", fns.len());
assert!(fns.iter().any(|f| f.name == "FNLOG"), "FNLOG not found");
assert!(
fns.iter().any(|f| f.name == "VALIDATE_CONFIGURATION"),
"VALIDATE_CONFIGURATION not found"
);
assert!(
fns.iter().any(|f| f.name == "CONNECT_TO_SERVER"),
"CONNECT_TO_SERVER not found"
);
assert!(
fns.iter().any(|f| f.name == "DISCONNECT"),
"DISCONNECT not found"
);
let validate_fn = fns
.iter()
.find(|f| f.name == "VALIDATE_CONFIGURATION")
.unwrap();
assert!(
validate_fn.docstring.is_some(),
"VALIDATE_CONFIGURATION should have docstring"
);
let connect_fn = fns.iter().find(|f| f.name == "CONNECT_TO_SERVER").unwrap();
assert!(
connect_fn.loops >= 1,
"CONNECT_TO_SERVER should have >= 1 loop"
);
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
let contains: Vec<_> = result
.edges
.iter()
.filter(|e| e.kind == EdgeKind::Contains)
.collect();
assert!(
contains.len() >= 6,
"expected >= 6 Contains edges, got {}",
contains.len()
);
}
#[cfg(feature = "lang-qbasic")]
#[test]
fn test_fixture_qbasic() {
let source = read_fixture("sample.qb");
let extractor = tokensave::extraction::QBasicExtractor;
let result = extractor.extract("sample.qb", &source);
assert!(
result.errors.is_empty(),
"QBasic errors: {:?}",
result.errors
);
assert!(result.nodes.iter().any(|n| n.kind == NodeKind::File));
let structs: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Struct)
.collect();
assert_eq!(
structs.len(),
1,
"expected 1 struct (Endpoint), got {}",
structs.len()
);
assert_eq!(structs[0].name, "Endpoint");
let struct_fields: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Field && n.qualified_name.contains("Endpoint"))
.collect();
assert!(
struct_fields.len() >= 3,
"expected >= 3 Endpoint fields, got {}",
struct_fields.len()
);
let fns: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Function)
.collect();
assert!(fns.len() >= 5, "expected >= 5 functions, got {}", fns.len());
assert!(
fns.iter().any(|f| f.name == "LogMessage"),
"LogMessage not found"
);
assert!(
fns.iter().any(|f| f.name == "ValidateConfig"),
"ValidateConfig not found"
);
assert!(
fns.iter().any(|f| f.name == "ConnectServer"),
"ConnectServer not found"
);
assert!(
fns.iter().any(|f| f.name == "DisconnectServer"),
"DisconnectServer not found"
);
assert!(
fns.iter().any(|f| f.name == "IsConnected"),
"IsConnected not found"
);
let log_fn = fns.iter().find(|f| f.name == "LogMessage").unwrap();
assert!(
log_fn.docstring.is_some(),
"LogMessage should have docstring"
);
let validate_fn = fns.iter().find(|f| f.name == "ValidateConfig").unwrap();
assert!(
validate_fn.branches >= 1,
"ValidateConfig should have >= 1 branch"
);
let connect_fn = fns.iter().find(|f| f.name == "ConnectServer").unwrap();
assert!(connect_fn.loops >= 1, "ConnectServer should have >= 1 loop");
let consts: Vec<_> = result
.nodes
.iter()
.filter(|n| n.kind == NodeKind::Const)
.collect();
assert!(!consts.is_empty(), "expected at least 1 CONST node");
assert!(
result
.unresolved_refs
.iter()
.any(|r| r.reference_kind == EdgeKind::Calls),
"expected Calls refs"
);
let contains: Vec<_> = result
.edges
.iter()
.filter(|e| e.kind == EdgeKind::Contains)
.collect();
assert!(
contains.len() >= 10,
"expected >= 10 Contains edges, got {}",
contains.len()
);
}