clean up
This commit is contained in:
@@ -236,7 +236,10 @@ impl DiagramRenderer for MermaidRenderer {
|
||||
let content = if cross_deps.is_empty() {
|
||||
base
|
||||
} else {
|
||||
let src_id = format!("{}_module", module.as_str().to_lowercase().replace('-', "_"));
|
||||
let src_id = format!(
|
||||
"{}_module",
|
||||
module.as_str().to_lowercase().replace('-', "_")
|
||||
);
|
||||
let mut extra = format!(
|
||||
" class {src_id}[\"{}\"] {{\n <<module>>\n }}\n",
|
||||
module.as_str()
|
||||
@@ -250,7 +253,11 @@ impl DiagramRenderer for MermaidRenderer {
|
||||
" class {dep_id}[\"{}\"] {{\n <<module>>\n }}\n",
|
||||
dep_mod.as_str()
|
||||
));
|
||||
let label = if *count == 1 { "1 dep".to_string() } else { format!("{count} deps") };
|
||||
let label = if *count == 1 {
|
||||
"1 dep".to_string()
|
||||
} else {
|
||||
format!("{count} deps")
|
||||
};
|
||||
extra.push_str(&format!(" {src_id} --> {dep_id} : {label}\n"));
|
||||
}
|
||||
format!("{base}\n{extra}")
|
||||
|
||||
@@ -36,7 +36,8 @@ impl ExtractionContext {
|
||||
}
|
||||
|
||||
pub fn add_relationship(&mut self, rel: Relationship) {
|
||||
self.relationships.push(rel.with_source_file(self.file_path.clone()));
|
||||
self.relationships
|
||||
.push(rel.with_source_file(self.file_path.clone()));
|
||||
}
|
||||
|
||||
pub fn add_warning(&mut self, file_path: FilePath, line: usize, message: &str) {
|
||||
@@ -51,6 +52,10 @@ impl ExtractionContext {
|
||||
|
||||
/// Consumes the context and returns the completed `AnalysisResult`.
|
||||
pub fn into_result(self) -> Result<AnalysisResult, DomainError> {
|
||||
Ok(AnalysisResult::new(self.elements, self.relationships, self.warnings))
|
||||
Ok(AnalysisResult::new(
|
||||
self.elements,
|
||||
self.relationships,
|
||||
self.warnings,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +303,12 @@ fn extract_python_params(params_node: &Node, source: &str) -> String {
|
||||
parts.join(", ")
|
||||
}
|
||||
|
||||
fn collect_constructor_params(body: &Node, source: &str, class_name: &str, ctx: &mut ExtractionContext) {
|
||||
fn collect_constructor_params(
|
||||
body: &Node,
|
||||
source: &str,
|
||||
class_name: &str,
|
||||
ctx: &mut ExtractionContext,
|
||||
) {
|
||||
let mut cursor = body.walk();
|
||||
for child in body.children(&mut cursor) {
|
||||
if child.kind() != "function_definition" {
|
||||
@@ -342,7 +347,12 @@ fn collect_typed_fields(body: &Node, source: &str, class_name: &str, ctx: &mut E
|
||||
collect_typed_fields_recursive(body, source, class_name, ctx);
|
||||
}
|
||||
|
||||
fn collect_typed_fields_recursive(node: &Node, source: &str, class_name: &str, ctx: &mut ExtractionContext) {
|
||||
fn collect_typed_fields_recursive(
|
||||
node: &Node,
|
||||
source: &str,
|
||||
class_name: &str,
|
||||
ctx: &mut ExtractionContext,
|
||||
) {
|
||||
let mut cursor = node.walk();
|
||||
for child in node.children(&mut cursor) {
|
||||
if (child.kind() == "assignment" || child.kind() == "typed_assignment")
|
||||
|
||||
@@ -1,9 +1,38 @@
|
||||
use tree_sitter::{Node, Parser};
|
||||
|
||||
const RUST_PRIMITIVES: &[&str] = &[
|
||||
"bool", "char", "str", "String", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16",
|
||||
"i32", "i64", "i128", "isize", "f32", "f64", "Vec", "Option", "Result", "Box", "Rc", "Arc",
|
||||
"HashMap", "HashSet", "BTreeMap", "BTreeSet", "PhantomData", "Pin", "Cow", "Self",
|
||||
"bool",
|
||||
"char",
|
||||
"str",
|
||||
"String",
|
||||
"u8",
|
||||
"u16",
|
||||
"u32",
|
||||
"u64",
|
||||
"u128",
|
||||
"usize",
|
||||
"i8",
|
||||
"i16",
|
||||
"i32",
|
||||
"i64",
|
||||
"i128",
|
||||
"isize",
|
||||
"f32",
|
||||
"f64",
|
||||
"Vec",
|
||||
"Option",
|
||||
"Result",
|
||||
"Box",
|
||||
"Rc",
|
||||
"Arc",
|
||||
"HashMap",
|
||||
"HashSet",
|
||||
"BTreeMap",
|
||||
"BTreeSet",
|
||||
"PhantomData",
|
||||
"Pin",
|
||||
"Cow",
|
||||
"Self",
|
||||
];
|
||||
|
||||
use archlens_domain::{
|
||||
@@ -143,8 +172,12 @@ fn extract_fields(node: &Node, source: &str) -> Vec<String> {
|
||||
let mut cursor = body.walk();
|
||||
for child in body.children(&mut cursor) {
|
||||
if child.kind() == "field_declaration" {
|
||||
let name = child.child_by_field_name("name").map(|n| &source[n.byte_range()]);
|
||||
let ty = child.child_by_field_name("type").map(|n| extract_base_type(&n, source));
|
||||
let name = child
|
||||
.child_by_field_name("name")
|
||||
.map(|n| &source[n.byte_range()]);
|
||||
let ty = child
|
||||
.child_by_field_name("type")
|
||||
.map(|n| extract_base_type(&n, source));
|
||||
if let (Some(name), Some(ty)) = (name, ty) {
|
||||
fields.push(format!("{name}: {ty}"));
|
||||
}
|
||||
@@ -174,7 +207,11 @@ fn extract_methods(root: &Node, source: &str, type_name: &str) -> Vec<String> {
|
||||
&& let Some(name_node) = item.child_by_field_name("name")
|
||||
{
|
||||
let fn_name = &source[name_node.byte_range()];
|
||||
let vis = if detect_visibility(&item, source) == Visibility::Public { "+" } else { "-" };
|
||||
let vis = if detect_visibility(&item, source) == Visibility::Public {
|
||||
"+"
|
||||
} else {
|
||||
"-"
|
||||
};
|
||||
let params = extract_fn_params(&item, source);
|
||||
let ret = extract_fn_return(&item, source);
|
||||
let sig = if ret.is_empty() {
|
||||
@@ -258,7 +295,11 @@ fn collect_use_imports(node: &Node, source: &str, ctx: &mut ExtractionContext) {
|
||||
}
|
||||
if let Some(arg) = child.child_by_field_name("argument") {
|
||||
let text = &source[arg.byte_range()];
|
||||
let path = text.split('{').next().unwrap_or(text).trim_end_matches("::");
|
||||
let path = text
|
||||
.split('{')
|
||||
.next()
|
||||
.unwrap_or(text)
|
||||
.trim_end_matches("::");
|
||||
|
||||
if (path.starts_with("crate::") || path.starts_with("super::"))
|
||||
&& let Ok(rel) = Relationship::new(&file_name, path, RelationshipKind::Import)
|
||||
|
||||
@@ -56,7 +56,11 @@ where
|
||||
el
|
||||
})
|
||||
.collect();
|
||||
(elements, result.relationships().to_vec(), result.warnings().to_vec())
|
||||
(
|
||||
elements,
|
||||
result.relationships().to_vec(),
|
||||
result.warnings().to_vec(),
|
||||
)
|
||||
}
|
||||
Err(err) => {
|
||||
let mut warnings = Vec::new();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use archlens_domain::{
|
||||
BoundaryRule, DomainError, NormalizedGraph, RenderedFile, RenderOutput,
|
||||
check_boundary_rules,
|
||||
BoundaryRule, DomainError, NormalizedGraph, RenderOutput, RenderedFile, check_boundary_rules,
|
||||
ports::DiagramRenderer,
|
||||
};
|
||||
|
||||
@@ -72,9 +71,7 @@ pub fn write_split(
|
||||
output_dir: &Option<PathBuf>,
|
||||
ext: &str,
|
||||
) -> Result<(), DomainError> {
|
||||
let dir = output_dir
|
||||
.clone()
|
||||
.unwrap_or_else(|| PathBuf::from("."));
|
||||
let dir = output_dir.clone().unwrap_or_else(|| PathBuf::from("."));
|
||||
|
||||
let overview = renderer.render(graph.as_graph())?;
|
||||
let overview_file = RenderedFile::new(
|
||||
@@ -89,7 +86,11 @@ pub fn write_split(
|
||||
let module_output = renderer.render_for_module(&subgraph, &module, &cross_deps)?;
|
||||
let module_file = RenderedFile::new(
|
||||
&format!("{}.{ext}", module.as_str().to_lowercase()),
|
||||
module_output.files().first().map(|f| f.content()).unwrap_or(""),
|
||||
module_output
|
||||
.files()
|
||||
.first()
|
||||
.map(|f| f.content())
|
||||
.unwrap_or(""),
|
||||
)?;
|
||||
write_file_to_dir(&dir, module_file)?;
|
||||
}
|
||||
@@ -99,10 +100,8 @@ pub fn write_split(
|
||||
|
||||
fn write_file_to_dir(dir: &PathBuf, file: RenderedFile) -> Result<(), DomainError> {
|
||||
let path = dir.join(file.name());
|
||||
std::fs::create_dir_all(dir)
|
||||
.map_err(|e| DomainError::IoError(e.to_string()))?;
|
||||
std::fs::write(&path, file.content())
|
||||
.map_err(|e| DomainError::IoError(e.to_string()))?;
|
||||
std::fs::create_dir_all(dir).map_err(|e| DomainError::IoError(e.to_string()))?;
|
||||
std::fs::write(&path, file.content()).map_err(|e| DomainError::IoError(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -111,11 +110,9 @@ fn write_to_output(rendered: RenderOutput, output: &Option<PathBuf>) -> Result<(
|
||||
match output {
|
||||
Some(path) => {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)
|
||||
.map_err(|e| DomainError::IoError(e.to_string()))?;
|
||||
std::fs::create_dir_all(parent).map_err(|e| DomainError::IoError(e.to_string()))?;
|
||||
}
|
||||
std::fs::write(path, content)
|
||||
.map_err(|e| DomainError::IoError(e.to_string()))
|
||||
std::fs::write(path, content).map_err(|e| DomainError::IoError(e.to_string()))
|
||||
}
|
||||
None => {
|
||||
print!("{content}");
|
||||
|
||||
@@ -298,8 +298,11 @@ impl CodeGraph {
|
||||
crate_to_module.insert(element.name().to_string(), module);
|
||||
}
|
||||
|
||||
let graph_modules: HashSet<String> =
|
||||
self.modules().iter().map(|m| m.as_str().to_string()).collect();
|
||||
let graph_modules: HashSet<String> = self
|
||||
.modules()
|
||||
.iter()
|
||||
.map(|m| m.as_str().to_string())
|
||||
.collect();
|
||||
|
||||
for rel in project_graph.relationships() {
|
||||
let src_module = crate_to_module.get(rel.source());
|
||||
@@ -308,8 +311,7 @@ impl CodeGraph {
|
||||
&& src != tgt
|
||||
&& graph_modules.contains(src)
|
||||
&& graph_modules.contains(tgt)
|
||||
&& let Ok(edge) =
|
||||
Relationship::new(src, tgt, RelationshipKind::Composition)
|
||||
&& let Ok(edge) = Relationship::new(src, tgt, RelationshipKind::Composition)
|
||||
{
|
||||
self.add_relationship(edge);
|
||||
}
|
||||
|
||||
@@ -44,9 +44,7 @@ impl NormalizedGraph {
|
||||
self.0.modules()
|
||||
}
|
||||
|
||||
pub fn elements_by_module(
|
||||
&self,
|
||||
) -> (HashMap<String, Vec<&CodeElement>>, Vec<&CodeElement>) {
|
||||
pub fn elements_by_module(&self) -> (HashMap<String, Vec<&CodeElement>>, Vec<&CodeElement>) {
|
||||
self.0.elements_by_module()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,6 @@ pub use value_objects::graph::{CodeElementKind, RelationshipKind, Visibility};
|
||||
pub use value_objects::output::{DiagramLevel, OutputConfig, RenderOutput, RenderedFile};
|
||||
pub use value_objects::rules::{BoundaryRule, RuleKind, RuleViolation, check_boundary_rules};
|
||||
pub use value_objects::source::{
|
||||
FilePath, Language, ModuleAssignment, ModuleName, SourceFile,
|
||||
normalize_cargo_package, normalize_python_package,
|
||||
FilePath, Language, ModuleAssignment, ModuleName, SourceFile, normalize_cargo_package,
|
||||
normalize_python_package,
|
||||
};
|
||||
|
||||
@@ -353,5 +353,8 @@ fn module_edges_excludes_intra_module_relationships() {
|
||||
let graph = graph.qualify();
|
||||
let edges = graph.module_edges();
|
||||
|
||||
assert!(edges.is_empty(), "intra-module relationships should not produce edges");
|
||||
assert!(
|
||||
edges.is_empty(),
|
||||
"intra-module relationships should not produce edges"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,12 @@ fn merge_project_edges_ignores_crates_with_no_matching_module() {
|
||||
.unwrap(),
|
||||
);
|
||||
project_graph.add_relationship(
|
||||
Relationship::new("external-lib", "myapp-domain", RelationshipKind::Composition).unwrap(),
|
||||
Relationship::new(
|
||||
"external-lib",
|
||||
"myapp-domain",
|
||||
RelationshipKind::Composition,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
graph.merge_project_edges(&project_graph);
|
||||
|
||||
@@ -43,13 +43,15 @@ pub fn run(args: Cli) -> Result<()> {
|
||||
let renderer = create_renderer(&args.format, level, !args.no_weights)?;
|
||||
|
||||
if args.check {
|
||||
let existing_path = args.output.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("--check requires --output to specify the file to check against"))?;
|
||||
let existing_path = args.output.as_ref().ok_or_else(|| {
|
||||
anyhow::anyhow!("--check requires --output to specify the file to check against")
|
||||
})?;
|
||||
let up_to_date = CheckFreshness {
|
||||
graph: &graph,
|
||||
renderer: &*renderer,
|
||||
existing_path: std::path::Path::new(existing_path),
|
||||
}.execute()?;
|
||||
}
|
||||
.execute()?;
|
||||
if up_to_date {
|
||||
println!("Architecture diagram is up to date.");
|
||||
} else {
|
||||
@@ -60,8 +62,14 @@ pub fn run(args: Cli) -> Result<()> {
|
||||
}
|
||||
|
||||
let (raw_allow, raw_deny) = config_loader.load_rules();
|
||||
let allow: Vec<BoundaryRule> = raw_allow.iter().filter_map(|s| BoundaryRule::parse(s)).collect();
|
||||
let deny: Vec<BoundaryRule> = raw_deny.iter().filter_map(|s| BoundaryRule::parse(s)).collect();
|
||||
let allow: Vec<BoundaryRule> = raw_allow
|
||||
.iter()
|
||||
.filter_map(|s| BoundaryRule::parse(s))
|
||||
.collect();
|
||||
let deny: Vec<BoundaryRule> = raw_deny
|
||||
.iter()
|
||||
.filter_map(|s| BoundaryRule::parse(s))
|
||||
.collect();
|
||||
let output_dir = args.output.as_ref().map(std::path::PathBuf::from);
|
||||
|
||||
let use_case = GenerateDiagram {
|
||||
@@ -76,7 +84,10 @@ pub fn run(args: Cli) -> Result<()> {
|
||||
|
||||
let violations = use_case.check_violations_only();
|
||||
if args.strict && !violations.is_empty() {
|
||||
bail!("{} boundary rule violation(s) in strict mode", violations.len());
|
||||
bail!(
|
||||
"{} boundary rule violation(s) in strict mode",
|
||||
violations.len()
|
||||
);
|
||||
}
|
||||
use_case.execute()?;
|
||||
|
||||
@@ -135,10 +146,18 @@ fn build_graph(args: &Cli, level: DiagramLevel) -> Result<NormalizedGraph> {
|
||||
|
||||
if !result.warnings().is_empty() {
|
||||
for warning in result.warnings() {
|
||||
eprintln!("WARNING: {}:{} {}", warning.file_path().as_str(), warning.line(), warning.message());
|
||||
eprintln!(
|
||||
"WARNING: {}:{} {}",
|
||||
warning.file_path().as_str(),
|
||||
warning.line(),
|
||||
warning.message()
|
||||
);
|
||||
}
|
||||
if args.strict {
|
||||
bail!("analysis produced {} warning(s) in strict mode", result.warnings().len());
|
||||
bail!(
|
||||
"analysis produced {} warning(s) in strict mode",
|
||||
result.warnings().len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +184,9 @@ fn create_renderer(
|
||||
show_weights: bool,
|
||||
) -> Result<Box<dyn archlens_domain::ports::DiagramRenderer>> {
|
||||
match format {
|
||||
"mermaid" => Ok(Box::new(MermaidRenderer::with_level(level).with_weights(show_weights))),
|
||||
"mermaid" => Ok(Box::new(
|
||||
MermaidRenderer::with_level(level).with_weights(show_weights),
|
||||
)),
|
||||
"ascii" => Ok(Box::new(AsciiRenderer::new())),
|
||||
"d2" => Ok(Box::new(D2Renderer::with_level(level))),
|
||||
"html" => Ok(Box::new(HtmlRenderer::new())),
|
||||
@@ -193,7 +214,8 @@ fn run_diff(args: &Cli, existing_path: &std::path::Path) -> Result<()> {
|
||||
graph: &graph,
|
||||
renderer: &*renderer,
|
||||
existing_path,
|
||||
}.execute()?;
|
||||
}
|
||||
.execute()?;
|
||||
|
||||
if diff.is_empty() {
|
||||
println!("No changes detected.");
|
||||
@@ -206,7 +228,11 @@ fn run_diff(args: &Cli, existing_path: &std::path::Path) -> Result<()> {
|
||||
for line in &diff.added {
|
||||
println!("{line}");
|
||||
}
|
||||
println!("\n{} added, {} removed", diff.added.len(), diff.removed.len());
|
||||
println!(
|
||||
"\n{} added, {} removed",
|
||||
diff.added.len(),
|
||||
diff.removed.len()
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@@ -286,7 +312,10 @@ fn get_changed_files(
|
||||
.map_err(|e| anyhow::anyhow!("git not found: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!("git diff failed: {}", String::from_utf8_lossy(&output.stderr));
|
||||
bail!(
|
||||
"git diff failed: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
let files = String::from_utf8_lossy(&output.stdout)
|
||||
@@ -324,8 +353,14 @@ fn run_watch(args: Cli) -> Result<()> {
|
||||
}
|
||||
|
||||
let (raw_allow, raw_deny) = config_loader.load_rules();
|
||||
let allow: Vec<BoundaryRule> = raw_allow.iter().filter_map(|s| BoundaryRule::parse(s)).collect();
|
||||
let deny: Vec<BoundaryRule> = raw_deny.iter().filter_map(|s| BoundaryRule::parse(s)).collect();
|
||||
let allow: Vec<BoundaryRule> = raw_allow
|
||||
.iter()
|
||||
.filter_map(|s| BoundaryRule::parse(s))
|
||||
.collect();
|
||||
let deny: Vec<BoundaryRule> = raw_deny
|
||||
.iter()
|
||||
.filter_map(|s| BoundaryRule::parse(s))
|
||||
.collect();
|
||||
if !allow.is_empty() || !deny.is_empty() {
|
||||
let use_case = GenerateDiagram {
|
||||
graph,
|
||||
@@ -343,7 +378,10 @@ fn run_watch(args: Cli) -> Result<()> {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
eprintln!("Watching {} for changes (Ctrl+C to stop)...", args.path.display());
|
||||
eprintln!(
|
||||
"Watching {} for changes (Ctrl+C to stop)...",
|
||||
args.path.display()
|
||||
);
|
||||
if let Err(e) = run_once(&args) {
|
||||
eprintln!("Error: {e}");
|
||||
} else {
|
||||
@@ -351,14 +389,18 @@ fn run_watch(args: Cli) -> Result<()> {
|
||||
}
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let mut watcher = recommended_watcher(move |res| { let _ = tx.send(res); })?;
|
||||
let mut watcher = recommended_watcher(move |res| {
|
||||
let _ = tx.send(res);
|
||||
})?;
|
||||
watcher.watch(&args.path, RecursiveMode::Recursive)?;
|
||||
|
||||
let mut last_run = Instant::now();
|
||||
loop {
|
||||
match rx.recv() {
|
||||
Ok(_) => {
|
||||
if last_run.elapsed() < debounce { continue; }
|
||||
if last_run.elapsed() < debounce {
|
||||
continue;
|
||||
}
|
||||
last_run = Instant::now();
|
||||
eprintln!("Change detected, regenerating...");
|
||||
if let Err(e) = run_once(&args) {
|
||||
@@ -367,7 +409,10 @@ fn run_watch(args: Cli) -> Result<()> {
|
||||
eprintln!("Diagram updated.");
|
||||
}
|
||||
}
|
||||
Err(e) => { eprintln!("Watch error: {e}"); break; }
|
||||
Err(e) => {
|
||||
eprintln!("Watch error: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user