refactor: extract module_edges() to CodeGraph domain — removes duplication from Mermaid and D2 renderers
Some checks failed
CI / Check / Test (push) Failing after 42s
Architecture Docs / Generate diagrams (push) Successful in 3m20s

This commit is contained in:
2026-06-17 10:48:59 +02:00
parent 11a5656efc
commit 1447dc74bb
4 changed files with 133 additions and 117 deletions

View File

@@ -92,49 +92,14 @@ fn render_type(graph: &CodeGraph) -> String {
}
fn render_module(graph: &CodeGraph) -> String {
use archlens_domain::RelationshipKind;
use std::collections::{HashMap, HashSet};
let mut lines = Vec::new();
let mut modules: HashSet<String> = HashSet::new();
let mut name_to_module: HashMap<&str, &str> = HashMap::new();
for el in graph.elements() {
if let Some(m) = el.module() {
modules.insert(m.as_str().to_string());
name_to_module.insert(el.qualified_name(), m.as_str());
name_to_module.insert(el.name(), m.as_str());
}
for module in graph.modules() {
let id = sanitize(module.as_str());
lines.push(format!("{id}: {}", module.as_str()));
}
for module in &modules {
let id = sanitize(module);
lines.push(format!("{id}: {module}"));
}
let mut edges: HashSet<(String, String)> = HashSet::new();
for rel in graph.relationships() {
if rel.kind() == RelationshipKind::Import {
continue;
}
// Direct module-to-module edge (from merged project deps)
if modules.contains(rel.source())
&& modules.contains(rel.target())
&& rel.source() != rel.target()
{
edges.insert((rel.source().to_string(), rel.target().to_string()));
continue;
}
let src_mod = name_to_module.get(rel.source());
let tgt_mod = name_to_module.get(rel.target());
if let (Some(s), Some(t)) = (src_mod, tgt_mod)
&& s != t
{
edges.insert((s.to_string(), t.to_string()));
}
}
for (src, tgt) in &edges {
for (src, tgt) in graph.module_edges().keys() {
lines.push(format!("{} -> {}", sanitize(src), sanitize(tgt)));
}

View File

@@ -1,4 +1,4 @@
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
use archlens_domain::{
CodeElement, CodeGraph, DiagramLevel, DomainError, ModuleName, RelationshipKind, RenderOutput,
@@ -146,85 +146,12 @@ impl MermaidRenderer {
fn render_module_flowchart(&self, graph: &CodeGraph) -> String {
let mut lines = vec!["graph TD".to_string()];
let mut name_to_modules: HashMap<&str, HashSet<&str>> = HashMap::new();
let mut file_to_module: HashMap<String, String> = HashMap::new();
let mut modules: HashSet<String> = HashSet::new();
for element in graph.elements() {
if let Some(module) = element.module() {
// Index both bare name and qualified name for lookup
name_to_modules
.entry(element.name())
.or_default()
.insert(module.as_str());
name_to_modules
.entry(element.qualified_name())
.or_default()
.insert(module.as_str());
modules.insert(module.as_str().to_string());
let file_stem = std::path::Path::new(element.file_path().as_str())
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("");
if !file_stem.is_empty() {
file_to_module.insert(file_stem.to_string(), module.as_str().to_string());
}
}
for module in graph.modules() {
let m = module.as_str();
lines.push(format!(" {m}[{m}]"));
}
for module in &modules {
lines.push(format!(" {module}[{module}]"));
}
let mut module_edges: HashMap<(String, String), usize> = HashMap::new();
for rel in graph.relationships() {
match rel.kind() {
RelationshipKind::Import => {
let source_mod = file_to_module.get(rel.source());
let target_top = rel.target().split('.').next().unwrap_or("");
let target_mod = ModuleName::capitalize(target_top);
if let Some(src) = source_mod
&& modules.contains(&target_mod)
&& *src != target_mod
{
*module_edges.entry((src.clone(), target_mod)).or_insert(0) += 1;
}
}
_ => {
if modules.contains(rel.source())
&& modules.contains(rel.target())
&& rel.source() != rel.target()
{
*module_edges
.entry((rel.source().to_string(), rel.target().to_string()))
.or_insert(0) += 1;
continue;
}
let src_mods = name_to_modules.get(rel.source());
let tgt_mods = name_to_modules.get(rel.target());
if let (Some(src_set), Some(tgt_set)) = (src_mods, tgt_mods) {
for src_mod in src_set {
if tgt_set.contains(src_mod) {
continue;
}
for tgt_mod in tgt_set {
if src_mod != tgt_mod {
*module_edges
.entry((src_mod.to_string(), tgt_mod.to_string()))
.or_insert(0) += 1;
}
}
}
}
}
}
}
for ((source, target), count) in &module_edges {
for ((source, target), count) in &graph.module_edges() {
let line = if self.show_weights {
let label = if *count == 1 {
"1 dep".to_string()