feat: implement all P1/P2/P3/P4 improvements from issue backlog
Some checks failed
CI / Check / Test (push) Failing after 1m33s
Architecture Docs / Generate diagrams (push) Successful in 3m21s

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>)
This commit is contained in:
2026-06-17 09:50:50 +02:00
parent 27197062eb
commit fdd85011a4
42 changed files with 2767 additions and 92 deletions

View File

@@ -58,6 +58,100 @@ fn respects_exclude_patterns() {
assert!(!files.iter().any(|f| f.path().as_str().contains("billing")));
}
#[test]
fn excludes_python_test_prefix_files_by_default() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("orders.py"), "class Order: pass").unwrap();
fs::write(dir.path().join("test_orders.py"), "class TestOrder: pass").unwrap();
let discovery = WalkdirDiscovery::new();
let files = discovery
.discover(dir.path(), &AnalysisConfig::default())
.unwrap();
assert_eq!(files.len(), 1);
assert!(files[0].path().as_str().ends_with("orders.py"));
}
#[test]
fn excludes_python_test_suffix_files_by_default() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("orders.py"), "class Order: pass").unwrap();
fs::write(dir.path().join("orders_test.py"), "class OrderTest: pass").unwrap();
let discovery = WalkdirDiscovery::new();
let files = discovery
.discover(dir.path(), &AnalysisConfig::default())
.unwrap();
assert_eq!(files.len(), 1);
assert!(files[0].path().as_str().ends_with("orders.py"));
}
#[test]
fn excludes_files_in_tests_directory_by_default() {
let dir = tempfile::tempdir().unwrap();
fs::create_dir_all(dir.path().join("tests")).unwrap();
fs::write(dir.path().join("orders.py"), "class Order: pass").unwrap();
fs::write(dir.path().join("tests/helpers.py"), "class Helper: pass").unwrap();
let discovery = WalkdirDiscovery::new();
let files = discovery
.discover(dir.path(), &AnalysisConfig::default())
.unwrap();
assert_eq!(files.len(), 1);
assert!(files[0].path().as_str().ends_with("orders.py"));
}
#[test]
fn excludes_rust_test_files_by_default() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("orders.rs"), "struct Order;").unwrap();
fs::write(dir.path().join("orders_tests.rs"), "struct OrdersTests;").unwrap();
let discovery = WalkdirDiscovery::new();
let files = discovery
.discover(dir.path(), &AnalysisConfig::default())
.unwrap();
assert_eq!(files.len(), 1);
assert!(files[0].path().as_str().ends_with("orders.rs"));
}
#[test]
fn excludes_rust_files_in_tests_directory_by_default() {
let dir = tempfile::tempdir().unwrap();
fs::create_dir_all(dir.path().join("tests")).unwrap();
fs::write(dir.path().join("lib.rs"), "struct Lib;").unwrap();
fs::write(
dir.path().join("tests/integration.rs"),
"struct IntegrationTest;",
)
.unwrap();
let discovery = WalkdirDiscovery::new();
let files = discovery
.discover(dir.path(), &AnalysisConfig::default())
.unwrap();
assert_eq!(files.len(), 1);
assert!(files[0].path().as_str().ends_with("lib.rs"));
}
#[test]
fn include_tests_flag_re_enables_test_files() {
let dir = tempfile::tempdir().unwrap();
fs::write(dir.path().join("orders.py"), "class Order: pass").unwrap();
fs::write(dir.path().join("test_orders.py"), "class TestOrder: pass").unwrap();
let config = AnalysisConfig::default().with_include_tests(true);
let discovery = WalkdirDiscovery::new();
let files = discovery.discover(dir.path(), &config).unwrap();
assert_eq!(files.len(), 2);
}
#[test]
fn empty_directory_returns_no_files() {
let dir = tempfile::tempdir().unwrap();