Skip to main content

tldr_cli/commands/
whatbreaks.rs

1//! Whatbreaks command - unified impact analysis wrapper
2//!
3//! Auto-detects target type (function/file/module) and runs appropriate
4//! sub-analyses to answer: "What breaks if I change X?"
5//!
6//! # Sub-Analyses by Target Type
7//!
8//! - **Function**: Runs `impact` analysis to find callers
9//! - **File**: Runs `importers` + `change-impact` analysis
10//! - **Module**: Runs `importers` analysis
11//!
12//! # Premortem Mitigations
13//! - T14: CLI registration follows existing pattern
14//! - T15: --type flag for disambiguation
15//! - T18: Text formatting follows spec style guide
16
17use std::path::PathBuf;
18
19use anyhow::Result;
20use clap::{Args, ValueEnum};
21
22use tldr_core::analysis::whatbreaks::{whatbreaks_analysis, TargetType, WhatbreaksOptions};
23use tldr_core::Language;
24
25use crate::output::{format_whatbreaks_text, OutputFormat, OutputWriter};
26
27/// Target type selection for CLI (T15 mitigation)
28#[derive(Debug, Clone, Copy, ValueEnum)]
29pub enum TargetTypeArg {
30    /// Function name - run impact analysis
31    Function,
32    /// File path - run importers + change-impact
33    File,
34    /// Module name - run importers
35    Module,
36}
37
38impl From<TargetTypeArg> for TargetType {
39    fn from(arg: TargetTypeArg) -> Self {
40        match arg {
41            TargetTypeArg::Function => TargetType::Function,
42            TargetTypeArg::File => TargetType::File,
43            TargetTypeArg::Module => TargetType::Module,
44        }
45    }
46}
47
48/// Analyze what breaks if a target is changed
49///
50/// Automatically detects whether target is a function, file, or module
51/// and runs appropriate sub-analyses.
52#[derive(Debug, Args)]
53pub struct WhatbreaksArgs {
54    /// Target to analyze (function name, file path, or module name)
55    pub target: String,
56
57    /// Project root directory (default: current directory)
58    #[arg(default_value = ".")]
59    pub path: PathBuf,
60
61    /// Force target type (overrides auto-detection)
62    #[arg(long = "type", short = 't', value_enum)]
63    pub target_type: Option<TargetTypeArg>,
64
65    /// Maximum depth for impact/caller traversal
66    #[arg(long, short = 'd', default_value = "3")]
67    pub depth: usize,
68
69    /// Skip slow analyses (diff-impact)
70    #[arg(long)]
71    pub quick: bool,
72
73    /// Programming language (auto-detect if not specified)
74    #[arg(long, short = 'l')]
75    pub lang: Option<Language>,
76}
77
78impl WhatbreaksArgs {
79    /// Run the whatbreaks command
80    pub fn run(&self, format: OutputFormat, quiet: bool) -> Result<()> {
81        let writer = OutputWriter::new(format, quiet);
82
83        // Validate path exists
84        if !self.path.exists() {
85            anyhow::bail!("Path not found: {}", self.path.display());
86        }
87
88        writer.progress(&format!(
89            "Analyzing what breaks if '{}' changes...",
90            self.target
91        ));
92
93        // Build options
94        let options = WhatbreaksOptions {
95            depth: self.depth,
96            quick: self.quick,
97            language: self.lang,
98            force_type: self.target_type.map(|t| t.into()),
99        };
100
101        // Run analysis
102        let report = whatbreaks_analysis(&self.target, &self.path, &options)?;
103
104        writer.progress(&format!(
105            "Target type: {} ({})",
106            report.target_type, report.detection_reason
107        ));
108
109        // Output based on format
110        if writer.is_text() {
111            let text = format_whatbreaks_text(&report);
112            writer.write_text(&text)?;
113        } else {
114            writer.write(&report)?;
115        }
116
117        Ok(())
118    }
119}