Skip to main content

cargo_mate/tools/
lifetime_visualizer.rs

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}