Skip to main content

garbage_code_hunter/personality/
profiles.rs

1//! Personality profiles based on code issue patterns.
2
3use super::Personality;
4use crate::analyzer::CodeIssue;
5
6/// Analyze issues and determine a personality profile.
7pub fn analyze(issues: &[CodeIssue]) -> Personality {
8    let total = issues.len() as f64;
9
10    if total == 0.0 {
11        return Personality {
12            title: "The Perfectionist",
13            emoji: "\u{1f45f}",
14            traits: vec![
15                "No issues detected — suspiciously clean code",
16                "Probably over-engineers everything",
17                "Definitely has a linter on save",
18                "Has never shipped a bug (or a feature on time)",
19            ],
20            advice: vec![
21                "Ship something imperfect once in a while",
22                "Your code is great but your deadlines are crying",
23                "Perfect is the enemy of shipped",
24            ],
25            score: 100.0,
26        };
27    }
28
29    // Count issue types
30    let mut unwrap_count = 0u32;
31    let mut naming_count = 0u32;
32    let mut nesting_count = 0u32;
33    let mut long_fn_count = 0u32;
34    let mut magic_count = 0u32;
35    let mut dup_count = 0u32;
36
37    for issue in issues {
38        let rule = issue.rule_name.to_lowercase();
39        if rule.contains("unwrap") {
40            unwrap_count += 1;
41        } else if rule.contains("name")
42            || rule.contains("single_letter")
43            || rule.contains("meaningless")
44        {
45            naming_count += 1;
46        } else if rule.contains("nest") || rule.contains("complex") {
47            nesting_count += 1;
48        } else if rule.contains("long") || rule.contains("function_length") {
49            long_fn_count += 1;
50        } else if rule.contains("magic") {
51            magic_count += 1;
52        } else if rule.contains("duplicat") {
53            dup_count += 1;
54        }
55    }
56
57    // Determine dominant pattern
58    let counts = [
59        (unwrap_count, "unwrap"),
60        (naming_count, "naming"),
61        (nesting_count, "nesting"),
62        (long_fn_count, "long_fn"),
63        (magic_count, "magic"),
64        (dup_count, "dup"),
65    ];
66
67    let dominant = counts
68        .iter()
69        .max_by_key(|(c, _)| *c)
70        .unwrap_or(&(0, "none"));
71
72    match dominant.1 {
73        "unwrap" => panic_personality(unwrap_count, total),
74        "naming" => naming_personality(naming_count, total),
75        "nesting" => nesting_personality(nesting_count, total),
76        "long_fn" => long_fn_personality(long_fn_count, total),
77        "magic" => magic_personality(magic_count, total),
78        "dup" => dup_personality(dup_count, total),
79        _ => balanced_personality(total),
80    }
81}
82
83fn panic_personality(count: u32, _total: f64) -> Personality {
84    Personality {
85        title: "The Optimist",
86        emoji: "\u{1f60f}",
87        traits: vec![
88            "Believes the world is full of happy paths",
89            "unwrap() is your safety blanket",
90            "Error handling is someone else's problem",
91            "Probably says 'it works on my machine' a lot",
92            "Treats panics as 'unexpected features'",
93        ],
94        advice: vec![
95            "Learn Result<T, E> — your future self will thank you",
96            "Every unwrap() is a potential production incident",
97            "Try `.unwrap_or_default()` at minimum",
98            "Use `?` operator to propagate errors gracefully",
99        ],
100        score: (100.0 - count as f64 * 3.0).max(0.0),
101    }
102}
103
104fn naming_personality(count: u32, _total: f64) -> Personality {
105    Personality {
106        title: "The Minimalist",
107        emoji: "\u{270d}\u{fe0f}",
108        traits: vec![
109            "Why use many word when few letter do trick",
110            "Variables named like chess coordinates",
111            "Your code reads like a math textbook",
112            "Comments explain what x, y, z mean",
113            "Considers 'data' a descriptive name",
114        ],
115        advice: vec![
116            "Descriptive names are not a luxury",
117            "Your IDE has autocomplete — use it",
118            "Future you won't remember what `d` meant",
119            "A good variable name eliminates the need for a comment",
120        ],
121        score: (100.0 - count as f64 * 2.0).max(0.0),
122    }
123}
124
125fn nesting_personality(count: u32, _total: f64) -> Personality {
126    Personality {
127        title: "The Architect",
128        emoji: "\u{1f3d7}\u{fe0f}",
129        traits: vec![
130            "Loves building pyramids of doom",
131            "Indentation is a competitive sport",
132            "Each function is a journey through layers",
133            "Probably dreams in nested brackets",
134            "Thinks 'flat is justice' only applies to anime",
135        ],
136        advice: vec![
137            "Extract inner logic into helper functions",
138            "Use early returns to reduce nesting",
139            "Consider the 'guard clause' pattern",
140            "If you need 4+ levels of nesting, the logic needs refactoring",
141        ],
142        score: (100.0 - count as f64 * 4.0).max(0.0),
143    }
144}
145
146fn long_fn_personality(count: u32, _total: f64) -> Personality {
147    Personality {
148        title: "The Storyteller",
149        emoji: "\u{1f4dd}",
150        traits: vec![
151            "Every function tells a complete story",
152            "Believes in 'single responsibility' — for files, not functions",
153            "Your scroll wheel gets a workout",
154            "Probably writes long commit messages too",
155            "Considers 200 lines a 'concise' function",
156        ],
157        advice: vec![
158            "If a function needs a comment to explain its sections, split it",
159            "Aim for functions that fit on one screen",
160            "The Single Responsibility Principle applies to functions too",
161            "Break complex logic into smaller, testable units",
162        ],
163        score: (100.0 - count as f64 * 3.0).max(0.0),
164    }
165}
166
167fn magic_personality(count: u32, _total: f64) -> Personality {
168    Personality {
169        title: "The Sorcerer",
170        emoji: "\u{1f9d9}",
171        traits: vec![
172            "Numbers have meaning — only to you",
173            "42 appears in your code more than in Hitchhiker's Guide",
174            "Constants are for the weak",
175            "Your code has its own secret numerology",
176            "Believes named constants are 'over-engineering'",
177        ],
178        advice: vec![
179            "Extract magic numbers into named constants",
180            "Your future self won't remember what 86400 means",
181            "Use enums or constants for repeated values",
182            "If a number appears twice, it needs a name",
183        ],
184        score: (100.0 - count as f64 * 2.0).max(0.0),
185    }
186}
187
188fn dup_personality(count: u32, _total: f64) -> Personality {
189    Personality {
190        title: "The Copy-Paste Artist",
191        emoji: "\u{1f4cb}",
192        traits: vec![
193            "Ctrl+C, Ctrl+V is your IDE's most used shortcut",
194            "Why abstract when you can duplicate",
195            "Same bug in 5 places = 5x the debugging fun",
196            "DRY stands for 'Don't Repeat... wait, too late'",
197            "Thinks 'reusable code' means copying it again",
198        ],
199        advice: vec![
200            "Extract common code into shared functions",
201            "One bug fix should fix it everywhere",
202            "Consider a utility module for repeated patterns",
203            "If you're copying code, you're copying bugs too",
204        ],
205        score: (100.0 - count as f64 * 3.0).max(0.0),
206    }
207}
208
209fn balanced_personality(total: f64) -> Personality {
210    Personality {
211        title: "The Pragmatist",
212        emoji: "\u{2696}\u{fe0f}",
213        traits: vec![
214            "A balanced mix of code smells",
215            "Not great at anything, not terrible at anything",
216            "The 'average developer' experience",
217            "Your code has character — like a diverse zoo",
218            "Jack of all trades, master of technical debt",
219        ],
220        advice: vec![
221            "Pick one area to improve at a time",
222            "Focus on the highest-severity issues first",
223            "Consistency is better than perfection",
224            "Tackle your highest-count issue category first",
225        ],
226        score: (100.0 - total * 1.5).max(0.0),
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233    use std::path::PathBuf;
234
235    fn make_issue(rule: &str) -> CodeIssue {
236        CodeIssue {
237            file_path: PathBuf::from("test.rs"),
238            line: 1,
239            column: 0,
240            rule_name: rule.to_string(),
241            message: "test".to_string(),
242            severity: crate::analyzer::Severity::Spicy,
243        }
244    }
245
246    #[test]
247    fn test_empty_issues() {
248        let p = analyze(&[]);
249        assert_eq!(p.title, "The Perfectionist");
250    }
251
252    #[test]
253    fn test_unwrap_dominant() {
254        let issues = vec![
255            make_issue("unwrap_abuse"),
256            make_issue("unwrap_abuse"),
257            make_issue("unwrap_abuse"),
258        ];
259        let p = analyze(&issues);
260        assert_eq!(p.title, "The Optimist");
261    }
262
263    #[test]
264    fn test_naming_dominant() {
265        let issues = vec![
266            make_issue("single_letter_variable"),
267            make_issue("meaningless_name"),
268        ];
269        let p = analyze(&issues);
270        assert_eq!(p.title, "The Minimalist");
271    }
272
273    #[test]
274    fn test_nesting_dominant() {
275        let issues = vec![
276            make_issue("deep_nesting"),
277            make_issue("complex_function"),
278            make_issue("high_complexity"),
279        ];
280        let p = analyze(&issues);
281        assert_eq!(p.title, "The Architect");
282    }
283
284    #[test]
285    fn test_long_fn_dominant() {
286        let issues = vec![make_issue("long_function"), make_issue("function_length")];
287        let p = analyze(&issues);
288        assert_eq!(p.title, "The Storyteller");
289    }
290
291    #[test]
292    fn test_magic_dominant() {
293        let issues = vec![make_issue("magic_number"), make_issue("magic_number")];
294        let p = analyze(&issues);
295        assert_eq!(p.title, "The Sorcerer");
296    }
297
298    #[test]
299    fn test_dup_dominant() {
300        let issues = vec![
301            make_issue("code_duplication"),
302            make_issue("code_duplication"),
303            make_issue("code_duplication"),
304        ];
305        let p = analyze(&issues);
306        assert_eq!(p.title, "The Copy-Paste Artist");
307    }
308
309    #[test]
310    fn test_balanced_mixed() {
311        let issues = vec![
312            make_issue("unwrap_abuse"),
313            make_issue("single_letter_variable"),
314            make_issue("deep_nesting"),
315        ];
316        let p = analyze(&issues);
317        // All tied at 1 each - should pick one (first max)
318        assert!(!p.title.is_empty());
319        assert!(p.score > 0.0);
320    }
321}