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