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};
26use crate::path_validation::require_directory;
27
28/// Target type selection for CLI (T15 mitigation)
29#[derive(Debug, Clone, Copy, ValueEnum)]
30pub enum TargetTypeArg {
31    /// Function name - run impact analysis
32    Function,
33    /// File path - run importers + change-impact
34    File,
35    /// Module name - run importers
36    Module,
37}
38
39impl From<TargetTypeArg> for TargetType {
40    fn from(arg: TargetTypeArg) -> Self {
41        match arg {
42            TargetTypeArg::Function => TargetType::Function,
43            TargetTypeArg::File => TargetType::File,
44            TargetTypeArg::Module => TargetType::Module,
45        }
46    }
47}
48
49/// Analyze what breaks if a target is changed
50///
51/// Automatically detects whether target is a function, file, or module
52/// and runs appropriate sub-analyses.
53#[derive(Debug, Args)]
54pub struct WhatbreaksArgs {
55    /// Target to analyze (function name, file path, or module name)
56    pub target: String,
57
58    /// Project root directory (default: current directory)
59    #[arg(default_value = ".")]
60    pub path: PathBuf,
61
62    /// Force target type (overrides auto-detection)
63    #[arg(long = "type", short = 't', value_enum)]
64    pub target_type: Option<TargetTypeArg>,
65
66    /// Maximum depth for impact/caller traversal
67    #[arg(long, short = 'd', default_value = "3")]
68    pub depth: usize,
69
70    /// Skip slow analyses (diff-impact)
71    #[arg(long)]
72    pub quick: bool,
73
74    /// Programming language (auto-detect if not specified)
75    #[arg(long, short = 'l')]
76    pub lang: Option<Language>,
77}
78
79impl WhatbreaksArgs {
80    /// Run the whatbreaks command
81    pub fn run(&self, format: OutputFormat, quiet: bool) -> Result<()> {
82        let writer = OutputWriter::new(format, quiet);
83
84        // Validate path exists AND is a directory.
85        // cli-error-clarity-v2 (P2.BUG-4).
86        require_directory(&self.path, "whatbreaks")?;
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}