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,5 +1,3 @@
use std::collections::HashMap;
use archlens_domain::{
CodeElement, CodeGraph, DomainError, RelationshipKind, RenderOutput, RenderedFile,
ports::DiagramRenderer,
@@ -55,19 +53,7 @@ impl DiagramRenderer for AsciiRenderer {
return Ok(RenderOutput::single(file));
}
let mut grouped: HashMap<String, Vec<&CodeElement>> = HashMap::new();
let mut ungrouped: Vec<&CodeElement> = Vec::new();
for element in graph.elements() {
if let Some(module) = element.module() {
grouped
.entry(module.as_str().to_string())
.or_default()
.push(element);
} else {
ungrouped.push(element);
}
}
let (grouped, ungrouped) = graph.elements_by_module();
if !ungrouped.is_empty() {
lines.push(String::new());

View File

@@ -82,7 +82,7 @@ impl ProjectAnalyzer for CargoWorkspaceAnalyzer {
let mut element =
CodeElement::new(package_name, CodeElementKind::Project, file_path, 1)?;
if let Some(module) = infer_group(member_path) {
if let Some(module) = ModuleName::from_directory_group(member_path) {
element = element.with_module(module);
}
@@ -110,14 +110,3 @@ impl ProjectAnalyzer for CargoWorkspaceAnalyzer {
Ok(graph)
}
}
fn infer_group(member_path: &str) -> Option<ModuleName> {
let parts: Vec<&str> = member_path.split('/').collect();
if parts.len() < 3 {
return None;
}
let group = parts[parts.len() - 2];
let capitalized = format!("{}{}", group[..1].to_uppercase(), &group[1..]);
ModuleName::new(&capitalized).ok()
}

View File

@@ -1,7 +1,7 @@
use std::collections::{HashMap, HashSet};
use archlens_domain::{
CodeElement, CodeGraph, DiagramLevel, DomainError, RelationshipKind, RenderOutput,
CodeElement, CodeGraph, DiagramLevel, DomainError, ModuleName, RelationshipKind, RenderOutput,
RenderedFile, Visibility, ports::DiagramRenderer,
};
@@ -46,20 +46,7 @@ impl MermaidRenderer {
fn render_class_diagram(&self, graph: &CodeGraph) -> String {
let mut lines = vec!["classDiagram".to_string()];
let mut grouped: HashMap<String, Vec<&CodeElement>> = HashMap::new();
let mut ungrouped: Vec<&CodeElement> = Vec::new();
for element in graph.elements() {
if let Some(module) = element.module() {
grouped
.entry(module.as_str().to_string())
.or_default()
.push(element);
} else {
ungrouped.push(element);
}
}
let (grouped, ungrouped) = graph.elements_by_module();
let has_namespaces = !grouped.is_empty();
let mut seen: HashSet<String> = HashSet::new();
@@ -157,7 +144,7 @@ impl MermaidRenderer {
RelationshipKind::Import => {
let source_mod = file_to_module.get(rel.source());
let target_top = rel.target().split('.').next().unwrap_or("");
let target_mod = Self::capitalize(target_top);
let target_mod = ModuleName::capitalize(target_top);
if let Some(src) = source_mod
&& modules.contains(&target_mod)
@@ -201,29 +188,10 @@ impl MermaidRenderer {
lines.join("\n")
}
fn capitalize(s: &str) -> String {
if s.is_empty() {
return String::new();
}
format!("{}{}", s[..1].to_uppercase(), &s[1..])
}
fn render_project_flowchart(&self, graph: &CodeGraph) -> String {
let mut lines = vec!["graph TD".to_string()];
let mut grouped: HashMap<String, Vec<&CodeElement>> = HashMap::new();
let mut ungrouped: Vec<&CodeElement> = Vec::new();
for element in graph.elements() {
if let Some(module) = element.module() {
grouped
.entry(module.as_str().to_string())
.or_default()
.push(element);
} else {
ungrouped.push(element);
}
}
let (grouped, ungrouped) = graph.elements_by_module();
for element in &ungrouped {
let id = Self::sanitize_id(element.name());

View File

@@ -0,0 +1,5 @@
use archlens_domain::{AnalysisResult, DomainError, FilePath};
pub trait LanguageExtractor {
fn analyze(&self, source: &str, file_path: &FilePath) -> Result<AnalysisResult, DomainError>;
}

View File

@@ -1,3 +1,4 @@
mod language_extractor;
mod python;
mod rust;
mod tree_sitter_analyzer;

View File

@@ -7,6 +7,16 @@ use archlens_domain::{
Relationship, RelationshipKind,
};
use crate::language_extractor::LanguageExtractor;
pub struct PythonExtractor;
impl LanguageExtractor for PythonExtractor {
fn analyze(&self, source: &str, file_path: &FilePath) -> Result<AnalysisResult, DomainError> {
analyze(source, file_path)
}
}
pub fn analyze(source: &str, file_path: &FilePath) -> Result<AnalysisResult, DomainError> {
let mut parser = Parser::new();
parser

View File

@@ -42,6 +42,16 @@ use archlens_domain::{
Relationship, RelationshipKind, Visibility,
};
use crate::language_extractor::LanguageExtractor;
pub struct RustExtractor;
impl LanguageExtractor for RustExtractor {
fn analyze(&self, source: &str, file_path: &FilePath) -> Result<AnalysisResult, DomainError> {
analyze(source, file_path)
}
}
pub fn analyze(source: &str, file_path: &FilePath) -> Result<AnalysisResult, DomainError> {
let mut parser = Parser::new();
parser

View File

@@ -1,8 +1,13 @@
use archlens_domain::{AnalysisResult, DomainError, Language, SourceFile, ports::SourceAnalyzer};
use crate::{python, rust};
use crate::language_extractor::LanguageExtractor;
use crate::python::PythonExtractor;
use crate::rust::RustExtractor;
pub struct TreeSitterAnalyzer;
pub struct TreeSitterAnalyzer {
rust: RustExtractor,
python: PythonExtractor,
}
impl Default for TreeSitterAnalyzer {
fn default() -> Self {
@@ -12,7 +17,18 @@ impl Default for TreeSitterAnalyzer {
impl TreeSitterAnalyzer {
pub fn new() -> Self {
Self
Self {
rust: RustExtractor,
python: PythonExtractor,
}
}
fn extractor_for(&self, language: Language) -> Option<&dyn LanguageExtractor> {
match language {
Language::Rust => Some(&self.rust),
Language::Python => Some(&self.python),
Language::CSharp => None,
}
}
}
@@ -21,10 +37,9 @@ impl SourceAnalyzer for TreeSitterAnalyzer {
let source = std::fs::read_to_string(file.path().as_str())
.map_err(|e| DomainError::IoError(e.to_string()))?;
match file.language() {
Language::Rust => rust::analyze(&source, file.path()),
Language::Python => python::analyze(&source, file.path()),
Language::CSharp => Ok(AnalysisResult::empty()),
match self.extractor_for(file.language()) {
Some(extractor) => extractor.analyze(&source, file.path()),
None => Ok(AnalysisResult::empty()),
}
}
}