tldr_cli/commands/
debt.rs1use std::path::PathBuf;
9
10use anyhow::Result;
11use clap::Args;
12
13use tldr_core::quality::debt::{analyze_debt, DebtOptions, DebtReport};
14use tldr_core::Language;
15
16use crate::output::{OutputFormat, OutputWriter};
17
18const VALID_CATEGORIES: [&str; 6] = [
20 "reliability",
21 "security",
22 "maintainability",
23 "efficiency",
24 "changeability",
25 "testability",
26];
27
28#[derive(Debug, Args)]
30pub struct DebtArgs {
31 #[arg(default_value = ".")]
33 pub path: PathBuf,
34
35 #[arg(short = 'c', long, value_parser = ["reliability", "security", "maintainability", "efficiency", "changeability", "testability"])]
37 pub category: Option<String>,
38
39 #[arg(short = 'k', long, default_value = "20")]
41 pub top: usize,
42
43 #[arg(long)]
45 pub min_debt: Option<u32>,
46
47 #[arg(long)]
49 pub hourly_rate: Option<f64>,
50}
51
52impl DebtArgs {
53 pub fn run(&self, format: OutputFormat, quiet: bool, lang: Option<Language>) -> Result<()> {
57 let writer = OutputWriter::new(format, quiet);
58
59 if !self.path.exists() {
61 anyhow::bail!("Path not found: {}", self.path.display());
62 }
63
64 if let Some(ref cat) = self.category {
66 if !VALID_CATEGORIES.contains(&cat.as_str()) {
67 anyhow::bail!(
68 "Invalid category '{}'. Valid categories: {}",
69 cat,
70 VALID_CATEGORIES.join(", ")
71 );
72 }
73 }
74
75 writer.progress(&format!(
76 "Analyzing technical debt in {}...",
77 self.path.display()
78 ));
79
80 let language = lang;
82
83 let options = DebtOptions {
84 path: self.path.clone(),
85 category_filter: self.category.clone(),
86 language,
87 top_k: self.top,
88 min_debt: self.min_debt.unwrap_or(0),
89 hourly_rate: self.hourly_rate,
90 };
91
92 let report = analyze_debt(options)?;
93
94 if writer.is_text() {
96 let text = report.to_text();
97 writer.write_text(&text)?;
98 } else {
99 writer.write(&report)?;
100 }
101
102 Ok(())
103 }
104}
105
106#[allow(dead_code)]
108fn parse_language(lang: &str) -> Option<Language> {
109 match lang.to_lowercase().as_str() {
110 "python" | "py" => Some(Language::Python),
111 "typescript" | "ts" => Some(Language::TypeScript),
112 "javascript" | "js" => Some(Language::JavaScript),
113 "rust" | "rs" => Some(Language::Rust),
114 "go" => Some(Language::Go),
115 "java" => Some(Language::Java),
116 "c" => Some(Language::C),
117 "cpp" | "c++" => Some(Language::Cpp),
118 "ruby" | "rb" => Some(Language::Ruby),
119 "php" => Some(Language::Php),
120 "swift" => Some(Language::Swift),
121 "kotlin" | "kt" => Some(Language::Kotlin),
122 "scala" => Some(Language::Scala),
123 "csharp" | "cs" | "c#" => Some(Language::CSharp),
124 "lua" => Some(Language::Lua),
125 "luau" => Some(Language::Luau),
126 "elixir" | "ex" => Some(Language::Elixir),
127 "ocaml" | "ml" => Some(Language::Ocaml),
128 _ => None,
129 }
130}
131
132#[allow(dead_code)]
134fn format_debt_text(report: &DebtReport) -> String {
135 report.to_text()
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn test_parse_language_python() {
144 assert_eq!(parse_language("python"), Some(Language::Python));
145 assert_eq!(parse_language("py"), Some(Language::Python));
146 assert_eq!(parse_language("Python"), Some(Language::Python));
147 }
148
149 #[test]
150 fn test_parse_language_typescript() {
151 assert_eq!(parse_language("typescript"), Some(Language::TypeScript));
152 assert_eq!(parse_language("ts"), Some(Language::TypeScript));
153 }
154
155 #[test]
156 fn test_parse_language_unknown() {
157 assert_eq!(parse_language("unknown"), None);
158 assert_eq!(parse_language(""), None);
159 }
160
161 #[test]
162 fn test_valid_categories() {
163 assert!(VALID_CATEGORIES.contains(&"reliability"));
164 assert!(VALID_CATEGORIES.contains(&"maintainability"));
165 assert!(!VALID_CATEGORIES.contains(&"invalid"));
166 }
167}