Files
archlens/crates/adapters/tree-sitter/tests/rust_analyzer_tests.rs
Gabriel Kaszewski fdd85011a4
Some checks failed
CI / Check / Test (push) Failing after 1m33s
Architecture Docs / Generate diagrams (push) Successful in 3m21s
feat: implement all P1/P2/P3/P4 improvements from issue backlog
P1 correctness:
- filter test files by default (--include-tests to opt in)
- per-module diagrams show cross-module dependency arrows
- qualified type names (Module::TypeName) fix false edges from duplicate names

P2 output richness:
- method parameter types and return types in class diagrams (Rust + Python)
- Python pyproject.toml project analyzer (--level project for monorepos)

P3 unique value:
- boundary rules in archlens.toml ([rules] allow/deny, --strict enforcement)

P4 nice to have:
- dependency weight labels on module arrows (--no-weights to disable)
- --watch mode with 500ms debounce
- D2 renderer adapter (--format d2)
- interactive self-contained HTML viewer (--format html)
- git-aware incremental analysis (--since <ref>)
2026-06-17 09:51:45 +02:00

273 lines
7.4 KiB
Rust

use archlens_domain::{
CodeElementKind, FilePath, Language, RelationshipKind, SourceFile, ports::SourceAnalyzer,
};
use archlens_tree_sitter::TreeSitterAnalyzer;
fn analyze_rust(source: &str, filename: &str) -> archlens_domain::AnalysisResult {
let dir = tempfile::tempdir().unwrap();
let file_path = dir.path().join(filename);
std::fs::write(&file_path, source).unwrap();
let analyzer = TreeSitterAnalyzer::new();
let source_file = SourceFile::new(
FilePath::new(file_path.to_str().unwrap()).unwrap(),
Language::Rust,
);
analyzer.analyze_file(&source_file).unwrap()
}
#[test]
fn extracts_rust_struct() {
let result = analyze_rust("pub struct Order {\n id: u64,\n}", "order.rs");
assert_eq!(result.elements().len(), 1);
assert_eq!(result.elements()[0].name(), "Order");
assert_eq!(result.elements()[0].kind(), CodeElementKind::Struct);
}
#[test]
fn extracts_rust_enum() {
let result = analyze_rust(
"pub enum Status {\n Active,\n Inactive,\n}",
"status.rs",
);
assert_eq!(result.elements().len(), 1);
assert_eq!(result.elements()[0].name(), "Status");
assert_eq!(result.elements()[0].kind(), CodeElementKind::Enum);
}
#[test]
fn extracts_rust_trait() {
let result = analyze_rust("pub trait Repository {\n fn find(&self);\n}", "repo.rs");
assert_eq!(result.elements().len(), 1);
assert_eq!(result.elements()[0].name(), "Repository");
assert_eq!(result.elements()[0].kind(), CodeElementKind::Trait);
}
#[test]
fn extracts_composition_from_struct_fields() {
let source =
"pub struct Order {\n id: u64,\n}\npub struct OrderService {\n order: Order,\n}";
let result = analyze_rust(source, "service.rs");
assert_eq!(result.elements().len(), 2);
assert_eq!(result.relationships().len(), 1);
assert_eq!(result.relationships()[0].source(), "OrderService");
assert_eq!(result.relationships()[0].target(), "Order");
assert_eq!(
result.relationships()[0].kind(),
RelationshipKind::Composition
);
}
#[test]
fn extracts_inheritance_from_trait_impl() {
let source = "pub trait Printable {}\npub struct Order {}\nimpl Printable for Order {}";
let result = analyze_rust(source, "order.rs");
let inheritance: Vec<_> = result
.relationships()
.iter()
.filter(|r| r.kind() == RelationshipKind::Inheritance)
.collect();
assert_eq!(inheritance.len(), 1);
assert_eq!(inheritance[0].source(), "Order");
assert_eq!(inheritance[0].target(), "Printable");
}
#[test]
fn extracts_use_imports() {
let source =
"use crate::domain::Order;\nuse crate::ports::Repository;\n\npub struct Service {}";
let result = analyze_rust(source, "service.rs");
let imports: Vec<_> = result
.relationships()
.iter()
.filter(|r| r.kind() == RelationshipKind::Import)
.collect();
assert!(imports.iter().any(|r| r.target() == "crate::domain::Order"));
assert!(
imports
.iter()
.any(|r| r.target() == "crate::ports::Repository")
);
}
#[test]
fn filters_std_and_external_crate_imports() {
let source =
"use std::collections::HashMap;\nuse serde::Serialize;\nuse crate::models::Order;\n";
let result = analyze_rust(source, "lib.rs");
let imports: Vec<_> = result
.relationships()
.iter()
.filter(|r| r.kind() == RelationshipKind::Import)
.collect();
assert_eq!(imports.len(), 1);
assert_eq!(imports[0].target(), "crate::models::Order");
}
#[test]
fn extracts_mod_declarations() {
let source = "mod models;\nmod services;\npub struct App {}";
let result = analyze_rust(source, "lib.rs");
let imports: Vec<_> = result
.relationships()
.iter()
.filter(|r| r.kind() == RelationshipKind::Import)
.collect();
assert!(imports.iter().any(|r| r.target() == "crate::models"));
assert!(imports.iter().any(|r| r.target() == "crate::services"));
}
#[test]
fn extracts_rust_method_with_typed_params() {
let source = r#"
pub struct OrderService;
impl OrderService {
pub fn process(&self, order: Order, count: u64) {}
}
"#;
let result = analyze_rust(source, "service.rs");
let element = result
.elements()
.iter()
.find(|e| e.name() == "OrderService")
.unwrap();
assert!(
element
.methods()
.iter()
.any(|m| m.contains("order: Order") && m.contains("count: u64")),
"expected typed params in method, got: {:?}",
element.methods()
);
}
#[test]
fn extracts_rust_method_return_type() {
let source = r#"
pub struct OrderService;
impl OrderService {
pub fn get(&self) -> Order {}
}
"#;
let result = analyze_rust(source, "service.rs");
let element = result
.elements()
.iter()
.find(|e| e.name() == "OrderService")
.unwrap();
assert!(
element.methods().iter().any(|m| m.contains("-> Order")),
"expected return type in method, got: {:?}",
element.methods()
);
}
#[test]
fn extracts_rust_method_params_and_return() {
let source = r#"
pub struct OrderService;
impl OrderService {
pub fn process(&self, order: Order) -> Result<(), Error> {}
}
"#;
let result = analyze_rust(source, "service.rs");
let element = result
.elements()
.iter()
.find(|e| e.name() == "OrderService")
.unwrap();
let method = element
.methods()
.iter()
.find(|m| m.contains("process"))
.unwrap();
assert!(method.contains("order: Order"), "missing param: {method}");
assert!(method.contains("->"), "missing return arrow: {method}");
}
#[test]
fn extracts_rust_static_method_params() {
let source = r#"
pub struct Finder;
impl Finder {
pub fn detect(path: &str, count: usize) -> bool { false }
}
"#;
let result = analyze_rust(source, "finder.rs");
let element = result
.elements()
.iter()
.find(|e| e.name() == "Finder")
.unwrap();
let method = element
.methods()
.iter()
.find(|m| m.contains("detect"))
.unwrap();
assert!(method.contains("path"), "missing path param: {method}");
assert!(method.contains("count"), "missing count param: {method}");
}
#[test]
fn extracts_rust_private_method_params() {
let source = r#"
pub struct WalkdirDiscovery;
impl WalkdirDiscovery {
fn detect_language(path: &std::path::Path) -> Option<String> { None }
}
"#;
let result = analyze_rust(source, "discovery.rs");
let element = result
.elements()
.iter()
.find(|e| e.name() == "WalkdirDiscovery")
.unwrap();
let method = element
.methods()
.iter()
.find(|m| m.contains("detect_language"))
.unwrap();
assert!(method.contains("path"), "missing path param: {method}");
}
#[test]
fn extracts_rust_method_reference_param() {
let source = r#"
use std::path::Path;
pub struct WalkdirDiscovery;
impl WalkdirDiscovery {
fn detect_language(path: &Path) -> Option<String> { None }
}
"#;
let result = analyze_rust(source, "discovery.rs");
let element = result
.elements()
.iter()
.find(|e| e.name() == "WalkdirDiscovery")
.unwrap();
let method = element
.methods()
.iter()
.find(|m| m.contains("detect_language"))
.unwrap();
assert!(method.contains("path"), "missing path param: {method}");
}