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>)
81 lines
2.5 KiB
Rust
81 lines
2.5 KiB
Rust
use archlens_domain::{
|
|
BoundaryRule, CodeElement, CodeElementKind, CodeGraph, FilePath, ModuleName, Relationship,
|
|
RelationshipKind, RuleViolation, check_boundary_rules,
|
|
};
|
|
|
|
fn make_element(name: &str, module: &str) -> CodeElement {
|
|
CodeElement::new(
|
|
name,
|
|
CodeElementKind::Class,
|
|
FilePath::new(&format!("src/{name}.rs")).unwrap(),
|
|
1,
|
|
)
|
|
.unwrap()
|
|
.with_module(ModuleName::new(module).unwrap())
|
|
}
|
|
|
|
fn graph_with_edge(
|
|
src_name: &str,
|
|
src_module: &str,
|
|
tgt_name: &str,
|
|
tgt_module: &str,
|
|
) -> CodeGraph {
|
|
let mut graph = CodeGraph::new();
|
|
graph.add_element(make_element(src_name, src_module));
|
|
graph.add_element(make_element(tgt_name, tgt_module));
|
|
graph.add_relationship(
|
|
Relationship::new(src_name, tgt_name, RelationshipKind::Composition).unwrap(),
|
|
);
|
|
graph.qualify()
|
|
}
|
|
|
|
#[test]
|
|
fn boundary_rule_parses_source_and_target() {
|
|
let rule = BoundaryRule::parse("Application --> Domain").unwrap();
|
|
assert_eq!(rule.source(), "Application");
|
|
assert_eq!(rule.target(), "Domain");
|
|
}
|
|
|
|
#[test]
|
|
fn check_returns_denied_violation_when_deny_rule_matches_edge() {
|
|
let graph = graph_with_edge("Service", "Domain", "Adapter", "Adapters");
|
|
|
|
let deny = vec![BoundaryRule::parse("Domain --> Adapters").unwrap()];
|
|
let violations = check_boundary_rules(&graph, &[], &deny);
|
|
|
|
assert_eq!(violations.len(), 1);
|
|
assert_eq!(violations[0].source_module(), "Domain");
|
|
assert_eq!(violations[0].target_module(), "Adapters");
|
|
assert!(matches!(
|
|
violations[0].kind(),
|
|
archlens_domain::RuleKind::Denied
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn check_returns_no_violation_when_edge_matches_allow_rule() {
|
|
let graph = graph_with_edge("Service", "Application", "Order", "Domain");
|
|
|
|
let allow = vec![BoundaryRule::parse("Application --> Domain").unwrap()];
|
|
let violations = check_boundary_rules(&graph, &allow, &[]);
|
|
|
|
assert!(
|
|
violations.is_empty(),
|
|
"expected no violations, got: {}",
|
|
violations.len()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn check_returns_not_allowed_when_edge_absent_from_allow_list() {
|
|
let graph = graph_with_edge("Repo", "Adapters", "Order", "Domain");
|
|
|
|
// Only Application --> Domain is allowed; Adapters --> Domain is not in the list
|
|
let allow = vec![BoundaryRule::parse("Application --> Domain").unwrap()];
|
|
let violations = check_boundary_rules(&graph, &allow, &[]);
|
|
|
|
assert_eq!(violations.len(), 1);
|
|
assert_eq!(violations[0].source_module(), "Adapters");
|
|
assert_eq!(violations[0].target_module(), "Domain");
|
|
}
|