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