refactor: deepen modules, consolidate inference, delete dead code

- Extract build_graph/load_config/create_renderer in presentation (393→~250 lines)
- Move module inference into ModuleName::from_path(), delete 3 scattered copies
- Move resolve_relationships/filter_external_imports into CodeGraph
- Add LanguageExtractor trait in tree-sitter adapter
- Add CodeGraph::elements_by_module(), replace 6 identical grouping loops
- Delete dead RenderDiagrams query
This commit is contained in:
2026-06-16 16:34:41 +02:00
parent dc8ecd983a
commit d28b00c697
15 changed files with 322 additions and 460 deletions

View File

@@ -1,6 +1,6 @@
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use crate::{CodeElement, ModuleName, Relationship};
use crate::{CodeElement, ModuleName, Relationship, RelationshipKind};
#[derive(Debug, Clone)]
pub struct CodeGraph {
@@ -53,6 +53,105 @@ impl CodeGraph {
modules
}
pub fn elements_by_module(&self) -> (HashMap<String, Vec<&CodeElement>>, Vec<&CodeElement>) {
let mut grouped: HashMap<String, Vec<&CodeElement>> = HashMap::new();
let mut ungrouped: Vec<&CodeElement> = Vec::new();
for element in &self.elements {
if let Some(module) = element.module() {
grouped
.entry(module.as_str().to_string())
.or_default()
.push(element);
} else {
ungrouped.push(element);
}
}
(grouped, ungrouped)
}
pub fn resolve_relationships(self) -> CodeGraph {
let mut file_types: HashMap<String, HashSet<String>> = HashMap::new();
let mut name_modules: HashMap<&str, HashSet<Option<&str>>> = HashMap::new();
let all_type_names: HashSet<&str> = self.elements.iter().map(|e| e.name()).collect();
for element in &self.elements {
file_types
.entry(element.file_path().as_str().to_string())
.or_default()
.insert(element.name().to_string());
name_modules
.entry(element.name())
.or_default()
.insert(element.module().map(|m| m.as_str()));
}
let mut resolved = CodeGraph::new();
for element in &self.elements {
resolved.add_element(element.clone());
}
for rel in &self.relationships {
match rel.kind() {
RelationshipKind::Import => {
resolved.add_relationship(rel.clone());
}
_ => {
if !all_type_names.contains(rel.source())
|| !all_type_names.contains(rel.target())
{
continue;
}
if let Some(src_file) = rel.source_file() {
let file_key = src_file.as_str().to_string();
if let Some(types_in_file) = file_types.get(&file_key)
&& types_in_file.contains(rel.target())
{
resolved.add_relationship(rel.clone());
continue;
}
}
let tgt_modules = &name_modules[rel.target()];
if tgt_modules.len() == 1 {
resolved.add_relationship(rel.clone());
}
}
}
}
resolved
}
pub fn filter_external_imports(self, known_modules: &HashSet<String>) -> CodeGraph {
let module_names: HashSet<String> = self
.modules()
.iter()
.map(|m| m.as_str().to_lowercase())
.collect();
let all_known: HashSet<&str> = known_modules
.iter()
.map(|s| s.as_str())
.chain(module_names.iter().map(|s| s.as_str()))
.collect();
let mut filtered = CodeGraph::new();
for element in &self.elements {
filtered.add_element(element.clone());
}
for rel in &self.relationships {
if rel.kind() == RelationshipKind::Import {
let target_top = rel.target().split('.').next().unwrap_or("").to_lowercase();
if !all_known.contains(target_top.as_str()) {
continue;
}
}
filtered.add_relationship(rel.clone());
}
filtered
}
pub fn subgraph_by_module(&self, module: &ModuleName) -> CodeGraph {
let filtered_elements: Vec<CodeElement> = self
.elements

View File

@@ -1,3 +1,6 @@
use std::collections::HashMap;
use std::path::Path;
use crate::DomainError;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -12,6 +15,60 @@ impl ModuleName {
Ok(Self(trimmed.to_string()))
}
pub fn from_path(
file_path: &str,
root: &Path,
module_mappings: &HashMap<String, String>,
) -> Option<Self> {
let relative = file_path
.strip_prefix(root.to_str().unwrap_or(""))
.unwrap_or(file_path)
.trim_start_matches('/');
for (pattern, module_name) in module_mappings {
if relative.starts_with(pattern.as_str()) {
return Self::new(module_name).ok();
}
}
let parts: Vec<&str> = relative.split('/').collect();
if parts.len() <= 1 {
return None;
}
let module_dir = if (parts[0] == "crates" || parts[0] == "src") && parts.len() > 2 {
parts[1]
} else if parts[0] != "src" && parts.len() > 1 {
parts[0]
} else {
return None;
};
Self::new(&Self::capitalize(module_dir)).ok()
}
pub fn from_directory_group(member_path: &str) -> Option<Self> {
let parts: Vec<&str> = member_path.split('/').collect();
if parts.len() < 3 {
return None;
}
let group = parts[parts.len() - 2];
Self::new(&Self::capitalize(group)).ok()
}
pub fn capitalize(s: &str) -> String {
s.split('-')
.map(|seg| {
if seg.is_empty() {
String::new()
} else {
format!("{}{}", seg[..1].to_uppercase(), &seg[1..])
}
})
.collect::<Vec<_>>()
.join("-")
}
pub fn as_str(&self) -> &str {
&self.0
}