garbage_code_hunter/personas/
mod.rs1use crate::analyzer::{CodeAnalyzer, CodeIssue};
4use crate::common::OutputFormat;
5use anyhow::Result;
6use colored::Colorize;
7use std::path::Path;
8
9#[derive(Debug, Clone, Copy)]
11pub enum Persona {
12 LinuxKernel,
13 SiliconValley,
14 JapaneseEnterprise,
15 RustFanatic,
16}
17
18impl Persona {
19 pub fn parse_persona(s: &str) -> Option<Self> {
20 match s.to_lowercase().as_str() {
21 "linux-kernel" | "linux" => Some(Self::LinuxKernel),
22 "silicon-valley" | "sv" => Some(Self::SiliconValley),
23 "japanese-enterprise" | "jp" => Some(Self::JapaneseEnterprise),
24 "rust-fanatic" | "rust" => Some(Self::RustFanatic),
25 _ => None,
26 }
27 }
28
29 pub fn name(&self) -> &'static str {
30 match self {
31 Self::LinuxKernel => "Grumpy Linux Kernel Maintainer",
32 Self::SiliconValley => "Silicon Valley Bro CTO",
33 Self::JapaneseEnterprise => "Japanese Enterprise Engineer",
34 Self::RustFanatic => "Rust Evangelist",
35 }
36 }
37
38 pub fn intro(&self) -> &'static str {
39 match self {
40 Self::LinuxKernel => "Who wrote this garbage?",
41 Self::SiliconValley => "Bro, this code has zero 10x energy.",
42 Self::JapaneseEnterprise => {
43 "The following code contains areas for improvement. (It's actually terrible.)"
44 }
45 Self::RustFanatic => "unsafe detected. friendship terminated.",
46 }
47 }
48}
49
50pub fn run(path: &Path, persona: Persona, format: &OutputFormat, lang: &str) -> Result<String> {
52 let analyzer = CodeAnalyzer::new(&[], lang);
53 let issues = analyzer.analyze_path(path);
54
55 let output = match format {
56 OutputFormat::Terminal => format_terminal(&issues, persona, lang),
57 OutputFormat::Json => format_json(&issues, persona),
58 };
59
60 Ok(output)
61}
62
63fn format_terminal(issues: &[CodeIssue], persona: Persona, _lang: &str) -> String {
64 let mut out = String::new();
65
66 out.push_str(&format!("\n{} {}\n", persona.name().bold(), "\u{1f3ad}"));
67 out.push_str(&format!("{}\n\n", "\u{2501}".repeat(40)));
68 out.push_str(&format!(" \"{}\"\n\n", persona.intro().italic()));
69
70 if issues.is_empty() {
71 out.push_str(&format!(" {}\n", persona.no_issues_roast()));
72 return out;
73 }
74
75 for (i, issue) in issues.iter().take(10).enumerate() {
77 let roast = persona.roast_issue(issue, i);
78 let file_short = issue
79 .file_path
80 .file_name()
81 .map(|f| f.to_string_lossy().to_string())
82 .unwrap_or_else(|| issue.file_path.display().to_string());
83 out.push_str(&format!(
84 " {}. {}:{} — {}\n",
85 i + 1,
86 file_short.dimmed(),
87 issue.line,
88 roast
89 ));
90 }
91
92 out.push_str(&format!("\n {}\n", persona.closing()));
93
94 out
95}
96
97fn format_json(issues: &[CodeIssue], persona: Persona) -> String {
98 serde_json::json!({
99 "persona": persona.name(),
100 "intro": persona.intro(),
101 "total_issues": issues.len(),
102 "roasts": issues.iter().take(10).enumerate().map(|(i, issue)| {
103 serde_json::json!({
104 "file": issue.file_path.display().to_string(),
105 "line": issue.line,
106 "rule": issue.rule_name,
107 "roast": persona.roast_issue(issue, i),
108 })
109 }).collect::<Vec<_>>(),
110 "closing": persona.closing(),
111 })
112 .to_string()
113}
114
115impl Persona {
116 fn no_issues_roast(&self) -> &'static str {
117 match self {
118 Self::LinuxKernel => {
119 "No issues? Either the code is actually good, or my scanner is broken."
120 }
121 Self::SiliconValley => {
122 "Clean code? This doesn't look like it was built in a hackathon."
123 }
124 Self::JapaneseEnterprise => {
125 "No issues found. Please submit form 27B/6 to verify this is correct."
126 }
127 Self::RustFanatic => {
128 "No issues detected. This code has been blessed by the borrow checker."
129 }
130 }
131 }
132
133 fn roast_issue(&self, issue: &CodeIssue, _index: usize) -> String {
134 let rule = &issue.rule_name;
135 match self {
136 Self::LinuxKernel => match rule.to_lowercase().as_str() {
137 r if r.contains("unwrap") => {
138 "Who taught you error handling? A toddler?".to_string()
139 }
140 r if r.contains("name") => {
141 "Single-letter variables are for mathematicians, not programmers.".to_string()
142 }
143 r if r.contains("nest") => {
144 "Your indentation is deeper than my disappointment.".to_string()
145 }
146 r if r.contains("magic") => {
147 "Magic numbers? This isn't a card game. Use constants.".to_string()
148 }
149 r if r.contains("duplicat") => {
150 "Copy-paste is not a design pattern. Refactor this mess.".to_string()
151 }
152 r if r.contains("long") => {
153 "This function is longer than my patience. Split it.".to_string()
154 }
155 _ => "This would get you banned from LKML.".to_string(),
156 },
157 Self::SiliconValley => match rule.to_lowercase().as_str() {
158 r if r.contains("unwrap") => {
159 "Bro, just `.unwrap()` everything? That's not very blockchain of you."
160 .to_string()
161 }
162 r if r.contains("name") => {
163 "Variables named `x`? Not very 10x engineer of you.".to_string()
164 }
165 r if r.contains("nest") => {
166 "This nesting is deeper than our seed round cap table.".to_string()
167 }
168 r if r.contains("magic") => {
169 "Random numbers in code? That's not how you do A/B testing, bro.".to_string()
170 }
171 r if r.contains("duplicat") => {
172 "Duplication? That's not scaling, that's copy-paste with extra steps.".to_string()
173 }
174 r if r.contains("long") => {
175 "This function is longer than our last pivot pitch deck.".to_string()
176 }
177 _ => "This code wouldn't pass a Series A due diligence.".to_string(),
178 },
179 Self::JapaneseEnterprise => match rule.to_lowercase().as_str() {
180 r if r.contains("unwrap") => {
181 "The unwrap() usage suggests a certain... boldness. (Please fix immediately.)"
182 .to_string()
183 }
184 r if r.contains("name") => {
185 "Variable naming could benefit from additional consideration. (It's terrible.)"
186 .to_string()
187 }
188 r if r.contains("nest") => {
189 "The indentation level indicates room for structural improvement. (Rewrite it.)"
190 .to_string()
191 }
192 r if r.contains("magic") => {
193 "Unexplained numbers detected. Please submit a constants specification document. (Seriously.)"
194 .to_string()
195 }
196 r if r.contains("duplicat") => {
197 "Code duplication found. This violates section 4.2 of our coding standards. (How did this pass review?)"
198 .to_string()
199 }
200 r if r.contains("long") => {
201 "This function exceeds the recommended line count. (It exceeds all reasonable limits.)"
202 .to_string()
203 }
204 _ => "This code has potential for growth. (It needs to be completely rewritten.)"
205 .to_string(),
206 },
207 Self::RustFanatic => match rule.to_lowercase().as_str() {
208 r if r.contains("unwrap") => {
209 "UNSAFE DETECTED. The borrow checker weeps.".to_string()
210 }
211 r if r.contains("name") => {
212 "Meaningless names? This is why people still use Go.".to_string()
213 }
214 r if r.contains("nest") => {
215 "This nesting would be unnecessary with proper pattern matching.".to_string()
216 }
217 r if r.contains("magic") => {
218 "Magic numbers? A true Rustacean uses const and type-safe enums.".to_string()
219 }
220 r if r.contains("duplicat") => {
221 "DRY violation detected. The Rust gods demand a trait implementation.".to_string()
222 }
223 r if r.contains("long") => {
224 "This function is so long, it needs its own crate. Split with mod.".to_string()
225 }
226 _ => "This code is why C++ developers laugh at us.".to_string(),
227 },
228 }
229 }
230
231 fn closing(&self) -> &'static str {
232 match self {
233 Self::LinuxKernel => "Fix this or I'll NAK your patch.",
234 Self::SiliconValley => {
235 "Let's circle back and ideate on a solution. Maybe over kombucha."
236 }
237 Self::JapaneseEnterprise => {
238 "Please address these findings at your earliest convenience. (By tomorrow.)"
239 }
240 Self::RustFanatic => "Rewrite it in Rust. Oh wait, it IS Rust. Then rewrite it BETTER.",
241 }
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_persona_from_str() {
251 assert!(Persona::parse_persona("linux-kernel").is_some());
252 assert!(Persona::parse_persona("rust").is_some());
253 assert!(Persona::parse_persona("unknown").is_none());
254 }
255
256 #[test]
257 fn test_persona_name() {
258 assert_eq!(
259 Persona::LinuxKernel.name(),
260 "Grumpy Linux Kernel Maintainer"
261 );
262 }
263
264 #[test]
265 fn test_run_on_current_dir() {
266 let result = run(
267 std::path::Path::new("."),
268 Persona::LinuxKernel,
269 &OutputFormat::Terminal,
270 "en-US",
271 );
272 assert!(result.is_ok());
273 }
274
275 #[test]
276 fn test_all_persona_names() {
277 assert_eq!(Persona::SiliconValley.name(), "Silicon Valley Bro CTO");
278 assert_eq!(
279 Persona::JapaneseEnterprise.name(),
280 "Japanese Enterprise Engineer"
281 );
282 assert_eq!(Persona::RustFanatic.name(), "Rust Evangelist");
283 }
284
285 #[test]
286 fn test_all_persona_intros() {
287 assert!(!Persona::LinuxKernel.intro().is_empty());
288 assert!(!Persona::SiliconValley.intro().is_empty());
289 assert!(!Persona::JapaneseEnterprise.intro().is_empty());
290 assert!(!Persona::RustFanatic.intro().is_empty());
291 }
292
293 #[test]
294 fn test_persona_from_str_all_variants() {
295 assert!(Persona::parse_persona("linux-kernel").is_some());
296 assert!(Persona::parse_persona("linux").is_some());
297 assert!(Persona::parse_persona("silicon-valley").is_some());
298 assert!(Persona::parse_persona("sv").is_some());
299 assert!(Persona::parse_persona("japanese-enterprise").is_some());
300 assert!(Persona::parse_persona("jp").is_some());
301 assert!(Persona::parse_persona("rust-fanatic").is_some());
302 assert!(Persona::parse_persona("rust").is_some());
303 assert!(Persona::parse_persona("unknown").is_none());
304 assert!(Persona::parse_persona("").is_none());
305 }
306}