1use super::{Tool, ToolError, Result, OutputFormat, parse_output_format};
2use clap::{Arg, ArgMatches, Command};
3use std::path::Path;
4use std::fs;
5use std::collections::HashMap;
6use colored::*;
7use syn::{
8 parse_file, visit::Visit, ItemFn, Lifetime, TypeReference, FnArg, ReturnType,
9 GenericParam,
10};
11use quote::ToTokens;
12use serde::{Serialize, Deserialize};
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct FunctionLifetimeInfo {
15 pub name: String,
16 pub lifetimes: Vec<String>,
17 pub constraints: Vec<String>,
18 pub input_lifetimes: Vec<String>,
19 pub output_lifetimes: Vec<String>,
20 pub issues: Vec<String>,
21}
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct LifetimeNode {
24 pub id: usize,
25 pub name: String,
26 pub node_type: LifetimeNodeType,
27}
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct LifetimeEdge {
30 pub from: usize,
31 pub to: usize,
32 pub edge_type: LifetimeEdgeType,
33}
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub enum LifetimeNodeType {
36 Function,
37 Lifetime,
38 Borrow,
39}
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub enum LifetimeEdgeType {
42 Outlives,
43 Borrows,
44 References,
45}
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct LifetimeGraph {
48 pub nodes: Vec<LifetimeNode>,
49 pub edges: Vec<LifetimeEdge>,
50}
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct LifetimeIssue {
53 pub function: String,
54 pub issue: String,
55 pub severity: String,
56 pub line: usize,
57 pub suggestion: String,
58}
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct BorrowAnalysis {
61 pub immutable_borrows: usize,
62 pub mutable_borrows: usize,
63 pub borrow_errors: usize,
64 pub potential_race_conditions: usize,
65}
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct LifetimeSuggestion {
68 pub category: String,
69 pub description: String,
70 pub before: String,
71 pub after: String,
72 pub impact: String,
73}
74pub struct LifetimeVisualizerTool;
75impl LifetimeVisualizerTool {
76 pub fn new() -> Self {
77 Self
78 }
79 fn parse_lifetimes_in_file(
80 &self,
81 file_path: &str,
82 ) -> Result<Vec<FunctionLifetimeInfo>> {
83 if !Path::new(file_path).exists() {
84 return Err(
85 ToolError::InvalidArguments(format!("File not found: {}", file_path)),
86 );
87 }
88 let content = fs::read_to_string(file_path)?;
89 let ast = parse_file(&content)
90 .map_err(|e| ToolError::ExecutionFailed(
91 format!("Failed to parse Rust file: {}", e),
92 ))?;
93 let mut visitor = FunctionLifetimeVisitor::new();
94 visitor.visit_file(&ast);
95 Ok(visitor.functions)
96 }
97 fn build_lifetime_graph(
98 &self,
99 functions: &[FunctionLifetimeInfo],
100 ) -> Result<LifetimeGraph> {
101 let mut nodes = Vec::new();
102 let mut edges = Vec::new();
103 let mut node_id = 0;
104 for function in functions {
105 let function_id = node_id;
106 nodes
107 .push(LifetimeNode {
108 id: function_id,
109 name: function.name.clone(),
110 node_type: LifetimeNodeType::Function,
111 });
112 node_id += 1;
113 for lifetime in &function.lifetimes {
114 let lifetime_id = node_id;
115 nodes
116 .push(LifetimeNode {
117 id: lifetime_id,
118 name: lifetime.clone(),
119 node_type: LifetimeNodeType::Lifetime,
120 });
121 edges
122 .push(LifetimeEdge {
123 from: function_id,
124 to: lifetime_id,
125 edge_type: LifetimeEdgeType::References,
126 });
127 node_id += 1;
128 }
129 }
130 for function in functions {
131 for constraint in &function.constraints {
132 if let Some((from_lifetime, to_lifetime)) = self
133 .parse_constraint(constraint)
134 {
135 let from_id = nodes
136 .iter()
137 .find(|n| n.name == from_lifetime)
138 .map(|n| n.id);
139 let to_id = nodes
140 .iter()
141 .find(|n| n.name == to_lifetime)
142 .map(|n| n.id);
143 if let (Some(from), Some(to)) = (from_id, to_id) {
144 edges
145 .push(LifetimeEdge {
146 from,
147 to,
148 edge_type: LifetimeEdgeType::Outlives,
149 });
150 }
151 }
152 }
153 }
154 Ok(LifetimeGraph { nodes, edges })
155 }
156 fn parse_constraint(&self, constraint: &str) -> Option<(String, String)> {
157 let parts: Vec<&str> = constraint
158 .split(':')
159 .map(|s| s.trim().trim_matches('\''))
160 .collect();
161 if parts.len() == 2 {
162 Some((format!("'{}", parts[0]), format!("'{}", parts[1])))
163 } else {
164 None
165 }
166 }
167 fn detect_lifetime_issues(
168 &self,
169 functions: &[FunctionLifetimeInfo],
170 ) -> Vec<LifetimeIssue> {
171 let mut issues = Vec::new();
172 for function in functions {
173 if function.lifetimes.len() > 1 && function.constraints.is_empty() {
174 issues
175 .push(LifetimeIssue {
176 function: function.name.clone(),
177 issue: "Multiple lifetimes without explicit constraints"
178 .to_string(),
179 severity: "Medium".to_string(),
180 line: 0,
181 suggestion: format!(
182 "Add lifetime constraints like {}: {}", function
183 .lifetimes[0], function.lifetimes[1]
184 ),
185 });
186 }
187 if function.output_lifetimes.len() > 0 {
188 let unconstrained_outputs: Vec<_> = function
189 .output_lifetimes
190 .iter()
191 .filter(|lt| !function.input_lifetimes.contains(lt))
192 .collect();
193 if !unconstrained_outputs.is_empty() {
194 issues
195 .push(LifetimeIssue {
196 function: function.name.clone(),
197 issue: "Return lifetime not tied to input lifetime"
198 .to_string(),
199 severity: "High".to_string(),
200 line: 0,
201 suggestion: "Use HRTB (for<'a>) or tie return lifetime to input"
202 .to_string(),
203 });
204 }
205 }
206 if function.lifetimes.len() == 1 && function.input_lifetimes.len() == 1
207 && function.output_lifetimes.len() == 1
208 && function.input_lifetimes[0] == function.output_lifetimes[0]
209 {
210 issues
211 .push(LifetimeIssue {
212 function: function.name.clone(),
213 issue: "Unnecessary explicit lifetime - elision would work"
214 .to_string(),
215 severity: "Low".to_string(),
216 line: 0,
217 suggestion: "Remove explicit lifetimes and let the compiler elide them"
218 .to_string(),
219 });
220 }
221 }
222 issues
223 }
224 fn generate_visualization(
225 &self,
226 graph: &LifetimeGraph,
227 format: &str,
228 ) -> Result<String> {
229 match format {
230 "mermaid" => self.generate_mermaid_diagram(graph),
231 "dot" => self.generate_dot_diagram(graph),
232 "graphviz" => self.generate_graphviz_diagram(graph),
233 _ => self.generate_mermaid_diagram(graph),
234 }
235 }
236 fn generate_mermaid_diagram(&self, graph: &LifetimeGraph) -> Result<String> {
237 let mut diagram = String::from("graph TD\n");
238 for node in &graph.nodes {
239 let style = match node.node_type {
240 LifetimeNodeType::Function => format!("š¦ {}", node.name),
241 LifetimeNodeType::Lifetime => format!("š {}", node.name),
242 LifetimeNodeType::Borrow => format!("š {}", node.name),
243 };
244 let node_id = format!("N{}", node.id);
245 diagram.push_str(&format!(" {}[\"{}\"]\n", node_id, style));
246 }
247 for edge in &graph.edges {
248 let from_id = format!("N{}", edge.from);
249 let to_id = format!("N{}", edge.to);
250 let style = match edge.edge_type {
251 LifetimeEdgeType::Outlives => "-->",
252 LifetimeEdgeType::Borrows => "-.->",
253 LifetimeEdgeType::References => "==>",
254 };
255 diagram.push_str(&format!(" {}{} {}\n", from_id, style, to_id));
256 }
257 diagram.push_str("\n classDef function fill:#e1f5fe\n");
258 diagram.push_str(" classDef lifetime fill:#f3e5f5\n");
259 diagram.push_str(" classDef borrow fill:#e8f5e8\n");
260 diagram.push_str(" classDef issue fill:#ffebee\n");
261 diagram.push_str(" classDef warning fill:#fff3e0\n");
262 diagram.push_str(" classDef good fill:#e8f5e8\n");
263 Ok(diagram)
264 }
265 fn generate_dot_diagram(&self, graph: &LifetimeGraph) -> Result<String> {
266 let mut diagram = String::from("digraph LifetimeGraph {\n");
267 diagram.push_str(" rankdir=LR;\n");
268 diagram.push_str(" node [shape=box];\n\n");
269 for node in &graph.nodes {
270 let shape = match node.node_type {
271 LifetimeNodeType::Function => "box",
272 LifetimeNodeType::Lifetime => "ellipse",
273 LifetimeNodeType::Borrow => "diamond",
274 };
275 diagram
276 .push_str(
277 &format!(
278 " N{} [label=\"{}\", shape={}];\n", node.id, node.name, shape
279 ),
280 );
281 }
282 diagram.push_str("\n");
283 for edge in &graph.edges {
284 let style = match edge.edge_type {
285 LifetimeEdgeType::Outlives => "color=blue",
286 LifetimeEdgeType::Borrows => "color=green,style=dashed",
287 LifetimeEdgeType::References => "color=red,style=bold",
288 };
289 diagram
290 .push_str(&format!(" N{} -> N{} [{}];\n", edge.from, edge.to, style));
291 }
292 diagram.push_str("}\n");
293 Ok(diagram)
294 }
295 fn generate_graphviz_diagram(&self, graph: &LifetimeGraph) -> Result<String> {
296 self.generate_dot_diagram(graph)
297 }
298 fn analyze_borrow_patterns(&self, file_path: &str) -> Result<BorrowAnalysis> {
299 let content = fs::read_to_string(file_path)?;
300 let immutable_borrows = content.matches("& ").count()
301 + content.matches("&mut ").count();
302 let mutable_borrows = content.matches("&mut ").count();
303 let borrow_errors = content.matches("cannot borrow").count()
304 + content.matches("borrowed").count();
305 let potential_race_conditions = content.matches("Arc<Mutex<").count()
306 + content.matches("Arc<RwLock<").count();
307 Ok(BorrowAnalysis {
308 immutable_borrows,
309 mutable_borrows,
310 borrow_errors,
311 potential_race_conditions,
312 })
313 }
314 fn suggest_lifetime_improvements(
315 &self,
316 issues: &[LifetimeIssue],
317 ) -> Vec<LifetimeSuggestion> {
318 let mut suggestions = Vec::new();
319 for issue in issues {
320 match issue.severity.as_str() {
321 "High" => {
322 if issue.issue.contains("Return lifetime not tied") {
323 suggestions
324 .push(LifetimeSuggestion {
325 category: "HRTB".to_string(),
326 description: "Use Higher-Ranked Trait Bounds".to_string(),
327 before: "fn cache_get<'k>(key: &'k Key) -> Option<&'k Value>"
328 .to_string(),
329 after: "fn cache_get(key: &Key) -> Option<&Value>"
330 .to_string(),
331 impact: "High".to_string(),
332 });
333 }
334 }
335 "Medium" => {
336 if issue.issue.contains("Multiple lifetimes") {
337 suggestions
338 .push(LifetimeSuggestion {
339 category: "Constraints".to_string(),
340 description: "Add explicit lifetime constraints"
341 .to_string(),
342 before: "fn process<'a, 'b>(data: &'a mut Vec<T>, config: &'b Config)"
343 .to_string(),
344 after: "fn process<'a, 'b: 'a>(data: &'a mut Vec<T>, config: &'b Config)"
345 .to_string(),
346 impact: "Medium".to_string(),
347 });
348 }
349 }
350 "Low" => {
351 if issue.issue.contains("elision") {
352 suggestions
353 .push(LifetimeSuggestion {
354 category: "Elision".to_string(),
355 description: "Use lifetime elision".to_string(),
356 before: "fn get<'a>(&'a self) -> &'a str".to_string(),
357 after: "fn get(&self) -> &str".to_string(),
358 impact: "Low".to_string(),
359 });
360 }
361 }
362 _ => {}
363 }
364 }
365 suggestions
366 }
367}
368struct FunctionLifetimeVisitor {
369 functions: Vec<FunctionLifetimeInfo>,
370}
371impl FunctionLifetimeVisitor {
372 fn new() -> Self {
373 Self { functions: Vec::new() }
374 }
375}
376impl<'ast> Visit<'ast> for FunctionLifetimeVisitor {
377 fn visit_item_fn(&mut self, node: &'ast ItemFn) {
378 let lifetime_info = self.extract_function_lifetimes(node);
379 self.functions.push(lifetime_info);
380 syn::visit::visit_item_fn(self, node);
381 }
382}
383impl FunctionLifetimeVisitor {
384 fn extract_function_lifetimes(&self, node: &ItemFn) -> FunctionLifetimeInfo {
385 let mut lifetimes = Vec::new();
386 let mut constraints = Vec::new();
387 for param in &node.sig.generics.params {
388 if let GenericParam::Lifetime(lifetime_def) = param {
389 lifetimes.push(format!("'{}", lifetime_def.lifetime.ident));
390 }
391 }
392 for where_predicate in &node.sig.generics.where_clause {
393 for predicate in &where_predicate.predicates {
394 if let syn::WherePredicate::Lifetime(lifetime_pred) = predicate {
395 constraints
396 .push(
397 format!(
398 "{}: {}", lifetime_pred.lifetime.to_token_stream(),
399 lifetime_pred.bounds.to_token_stream()
400 ),
401 );
402 }
403 }
404 }
405 let input_lifetimes = self.extract_input_lifetimes(&node.sig.inputs);
406 let output_lifetimes = self.extract_output_lifetimes(&node.sig.output);
407 FunctionLifetimeInfo {
408 name: node.sig.ident.to_string(),
409 lifetimes,
410 constraints,
411 input_lifetimes,
412 output_lifetimes,
413 issues: Vec::new(),
414 }
415 }
416 fn extract_input_lifetimes(
417 &self,
418 inputs: &syn::punctuated::Punctuated<FnArg, syn::token::Comma>,
419 ) -> Vec<String> {
420 let mut lifetimes = Vec::new();
421 for arg in inputs {
422 match arg {
423 FnArg::Receiver(receiver) => {
424 if let Some((_, lifetime)) = &receiver.reference {
425 if let Some(lt) = lifetime {
426 lifetimes.push(format!("'{}", lt.ident));
427 }
428 }
429 }
430 FnArg::Typed(pat_type) => {
431 self.extract_lifetimes_from_type(&pat_type.ty, &mut lifetimes);
432 }
433 }
434 }
435 lifetimes
436 }
437 fn extract_output_lifetimes(&self, output: &ReturnType) -> Vec<String> {
438 let mut lifetimes = Vec::new();
439 if let ReturnType::Type(_, ty) = output {
440 self.extract_lifetimes_from_type(ty, &mut lifetimes);
441 }
442 lifetimes
443 }
444 fn extract_lifetimes_from_type(&self, ty: &syn::Type, lifetimes: &mut Vec<String>) {
445 match ty {
446 syn::Type::Reference(type_ref) => {
447 if let Some(lifetime) = &type_ref.lifetime {
448 lifetimes.push(format!("'{}", lifetime.ident));
449 }
450 self.extract_lifetimes_from_type(&type_ref.elem, lifetimes);
451 }
452 syn::Type::Slice(slice) => {
453 self.extract_lifetimes_from_type(&slice.elem, lifetimes);
454 }
455 syn::Type::Array(array) => {
456 self.extract_lifetimes_from_type(&array.elem, lifetimes);
457 }
458 syn::Type::Tuple(tuple) => {
459 for elem in &tuple.elems {
460 self.extract_lifetimes_from_type(elem, lifetimes);
461 }
462 }
463 _ => {}
464 }
465 }
466}
467impl Tool for LifetimeVisualizerTool {
468 fn name(&self) -> &'static str {
469 "lifetime-visualizer"
470 }
471 fn description(&self) -> &'static str {
472 "Visualize lifetime relationships in Rust code"
473 }
474 fn command(&self) -> Command {
475 Command::new(self.name())
476 .about(self.description())
477 .long_about(
478 "Visualize lifetime relationships in Rust code, helping developers understand borrowing and ownership patterns.\n\
479 \n\
480 This tool provides deep insights into lifetime patterns:\n\
481 ⢠Analyze lifetime annotations in functions\n\
482 ⢠Build lifetime dependency graphs\n\
483 ⢠Detect potential lifetime issues\n\
484 ⢠Generate visual lifetime flow diagrams\n\
485 \n\
486 EXAMPLES:\n\
487 cm tool lifetime-visualizer --input src/lib.rs --issues --suggest\n\
488 cm tool lifetime-visualizer --input src/main.rs --visualize --format mermaid\n\
489 cm tool lifetime-visualizer --input src/ --borrow-check",
490 )
491 .args(
492 &[
493 Arg::new("input")
494 .long("input")
495 .short('i')
496 .help("Input Rust file or directory to analyze")
497 .default_value("src/"),
498 Arg::new("function")
499 .long("function")
500 .short('f')
501 .help("Specific function to analyze"),
502 Arg::new("visualize")
503 .long("visualize")
504 .short('v')
505 .help("Generate lifetime visualization")
506 .action(clap::ArgAction::SetTrue),
507 Arg::new("issues")
508 .long("issues")
509 .help("Detect lifetime issues")
510 .action(clap::ArgAction::SetTrue),
511 Arg::new("suggest")
512 .long("suggest")
513 .help("Generate improvement suggestions")
514 .action(clap::ArgAction::SetTrue),
515 Arg::new("format")
516 .long("format")
517 .help("Visualization format: mermaid, dot, graphviz")
518 .default_value("mermaid"),
519 Arg::new("borrow-check")
520 .long("borrow-check")
521 .help("Analyze borrowing patterns")
522 .action(clap::ArgAction::SetTrue),
523 Arg::new("interactive")
524 .long("interactive")
525 .help("Interactive lifetime exploration")
526 .action(clap::ArgAction::SetTrue),
527 Arg::new("output")
528 .long("output")
529 .short('o')
530 .help("Output file for visualization")
531 .default_value("lifetimes.md"),
532 ],
533 )
534 .args(&super::common_options())
535 }
536 fn execute(&self, matches: &ArgMatches) -> Result<()> {
537 let input = matches.get_one::<String>("input").unwrap();
538 let specific_function = matches.get_one::<String>("function");
539 let visualize = matches.get_flag("visualize");
540 let detect_issues = matches.get_flag("issues");
541 let suggest = matches.get_flag("suggest");
542 let format = matches.get_one::<String>("format").unwrap();
543 let borrow_check = matches.get_flag("borrow-check");
544 let interactive = matches.get_flag("interactive");
545 let output_file = matches.get_one::<String>("output").unwrap();
546 let verbose = matches.get_flag("verbose");
547 let dry_run = matches.get_flag("dry-run");
548 let output_format = parse_output_format(matches);
549 if dry_run {
550 println!("š Would analyze lifetimes in: {}", input);
551 return Ok(());
552 }
553 let functions = if Path::new(input).is_file() {
554 self.parse_lifetimes_in_file(input)?
555 } else {
556 let mut all_functions = Vec::new();
557 let rust_files = self.find_rust_files(input)?;
558 for file in rust_files {
559 match self.parse_lifetimes_in_file(&file) {
560 Ok(mut functions) => all_functions.extend(functions),
561 Err(e) => {
562 if verbose {
563 println!("ā ļø Failed to parse {}: {}", file, e);
564 }
565 }
566 }
567 }
568 all_functions
569 };
570 let filtered_functions: Vec<_> = if let Some(func_name) = specific_function {
571 functions.into_iter().filter(|f| f.name == *func_name).collect()
572 } else {
573 functions
574 };
575 if filtered_functions.is_empty() {
576 if let Some(name) = specific_function {
577 println!("ā Function '{}' not found.", name.red());
578 } else {
579 println!("ā
No functions with explicit lifetimes found.");
580 }
581 return Ok(());
582 }
583 match output_format {
584 OutputFormat::Human => {
585 println!(
586 "š {} - {}", "Lifetime Analysis Report".bold(), self.description()
587 .cyan()
588 );
589 println!("\nš Files Analyzed: {}", input.bold());
590 println!(
591 "š Functions Found: {}", filtered_functions.len().to_string()
592 .cyan()
593 );
594 let total_lifetimes: usize = filtered_functions
595 .iter()
596 .map(|f| f.lifetimes.len())
597 .sum();
598 let total_constraints: usize = filtered_functions
599 .iter()
600 .map(|f| f.constraints.len())
601 .sum();
602 println!("\nš Lifetime Summary:");
603 println!("⢠Explicit lifetimes: {}", total_lifetimes);
604 println!("⢠Lifetime constraints: {}", total_constraints);
605 println!("⢠Complex relationships: {}", total_constraints);
606 let issues = if detect_issues {
607 self.detect_lifetime_issues(&filtered_functions)
608 } else {
609 Vec::new()
610 };
611 if !issues.is_empty() {
612 println!("\nā ļø Lifetime Issues Detected:");
613 for (i, issue) in issues.iter().enumerate() {
614 let severity_color = match issue.severity.as_str() {
615 "High" => issue.severity.red().bold(),
616 "Medium" => issue.severity.yellow().bold(),
617 "Low" => issue.severity.green().bold(),
618 _ => issue.severity.normal(),
619 };
620 println!("{}. Function: {}", i + 1, issue.function.bold());
621 println!(" [{}] {}", severity_color, issue.issue);
622 println!(" š” {}", issue.suggestion.cyan());
623 println!();
624 }
625 } else if detect_issues {
626 println!("\nā
No lifetime issues detected!");
627 }
628 if borrow_check {
629 println!("\nš Borrow Analysis:");
630 for function in &filtered_functions {
631 if function.name.contains("process")
632 || function.name.contains("cache")
633 {
634 match self.analyze_borrow_patterns(input) {
635 Ok(analysis) => {
636 println!(
637 "⢠Immutable borrows: {}", analysis.immutable_borrows
638 );
639 println!(
640 "⢠Mutable borrows: {}", analysis.mutable_borrows
641 );
642 println!(
643 "⢠Borrow checker errors: {}", analysis.borrow_errors
644 );
645 if analysis.potential_race_conditions > 0 {
646 println!(
647 "⢠Potential race conditions: {}", analysis
648 .potential_race_conditions.to_string().yellow()
649 );
650 }
651 }
652 Err(e) => {
653 if verbose {
654 println!(" ā ļø Borrow analysis failed: {}", e);
655 }
656 }
657 }
658 break;
659 }
660 }
661 }
662 if suggest && !issues.is_empty() {
663 let suggestions = self.suggest_lifetime_improvements(&issues);
664 if !suggestions.is_empty() {
665 println!("\nš” Improvement Suggestions:");
666 for (i, suggestion) in suggestions.iter().enumerate() {
667 let impact_color = match suggestion.impact.as_str() {
668 "High" => suggestion.impact.red().bold(),
669 "Medium" => suggestion.impact.yellow().bold(),
670 "Low" => suggestion.impact.green().bold(),
671 _ => suggestion.impact.normal(),
672 };
673 println!(
674 "{}. [{}] {}", i + 1, impact_color, suggestion.description
675 .bold()
676 );
677 println!(" Before: {}", suggestion.before.red());
678 println!(" After: {}", suggestion.after.green());
679 println!(" Category: {}", suggestion.category.cyan());
680 println!();
681 }
682 }
683 }
684 if visualize {
685 match self.build_lifetime_graph(&filtered_functions) {
686 Ok(graph) => {
687 match self.generate_visualization(&graph, format) {
688 Ok(visualization) => {
689 println!(
690 "\nš Lifetime Visualization ({}):", format.bold()
691 );
692 if format == "mermaid" {
693 println!("```mermaid");
694 println!("{}", visualization);
695 println!("```");
696 } else {
697 println!(
698 "Generated {} diagram with {} nodes and {} edges", format,
699 graph.nodes.len(), graph.edges.len()
700 );
701 println!("First few lines:");
702 for line in visualization.lines().take(10) {
703 println!(" {}", line);
704 }
705 }
706 if let Err(e) = fs::write(output_file, &visualization) {
707 if verbose {
708 println!("ā ļø Could not write visualization: {}", e);
709 }
710 } else {
711 println!("\nš¾ Visualization saved to: {}", output_file);
712 }
713 }
714 Err(e) => {
715 if verbose {
716 println!("ā ļø Could not generate visualization: {}", e);
717 }
718 }
719 }
720 }
721 Err(e) => {
722 if verbose {
723 println!("ā ļø Could not build lifetime graph: {}", e);
724 }
725 }
726 }
727 }
728 }
729 OutputFormat::Json => {
730 let issues = self.detect_lifetime_issues(&filtered_functions);
731 let mut json_output = serde_json::json!(
732 { "files_analyzed" : input, "functions_analyzed" : filtered_functions
733 .len(), "total_lifetimes" : filtered_functions.iter().map(| f | f
734 .lifetimes.len()).sum::< usize > (), "functions" :
735 filtered_functions, }
736 );
737 if !issues.is_empty() {
738 json_output["issues"] = serde_json::to_value(&issues).unwrap();
739 }
740 if suggest {
741 let suggestions = self.suggest_lifetime_improvements(&issues);
742 json_output["suggestions"] = serde_json::to_value(&suggestions)
743 .unwrap();
744 }
745 if visualize {
746 if let Ok(graph) = self.build_lifetime_graph(&filtered_functions) {
747 json_output["lifetime_graph"] = serde_json::to_value(&graph)
748 .unwrap();
749 }
750 }
751 println!("{}", serde_json::to_string_pretty(& json_output).unwrap());
752 }
753 OutputFormat::Table => {
754 println!(
755 "āā Lifetime Analysis āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
756 );
757 println!("ā Files: {:<45} ā", input);
758 println!("ā Functions: {:<40} ā", filtered_functions.len());
759 let total_lifetimes: usize = filtered_functions
760 .iter()
761 .map(|f| f.lifetimes.len())
762 .sum();
763 println!("ā Total Lifetimes: {:<34} ā", total_lifetimes);
764 if detect_issues {
765 let issues = self.detect_lifetime_issues(&filtered_functions);
766 println!("ā Issues Found: {:<37} ā", issues.len());
767 }
768 if visualize {
769 println!(
770 "ā Visualization: {:<35} ā", format!("ā ({})", format)
771 );
772 }
773 println!(
774 "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā"
775 );
776 }
777 }
778 Ok(())
779 }
780}
781impl LifetimeVisualizerTool {
782 fn find_rust_files(&self, dir: &str) -> Result<Vec<String>> {
783 let mut rust_files = Vec::new();
784 fn visit_dir(dir: &str, files: &mut Vec<String>) -> Result<()> {
785 let entries = fs::read_dir(dir)?;
786 for entry in entries {
787 let entry = entry?;
788 let path = entry.path();
789 if path.is_dir() {
790 if let Some(dir_name) = path.file_name() {
791 if dir_name != "target" && dir_name != ".git" {
792 visit_dir(&path.to_string_lossy(), files)?;
793 }
794 }
795 } else if let Some(ext) = path.extension() {
796 if ext == "rs" {
797 files.push(path.to_string_lossy().to_string());
798 }
799 }
800 }
801 Ok(())
802 }
803 visit_dir(dir, &mut rust_files)?;
804 Ok(rust_files)
805 }
806}