Skip to main content

garbage_code_hunter/
educational.rs

1/// Educational advice system that provides detailed explanations and improvement suggestions
2use std::collections::HashMap;
3
4#[derive(Debug, Clone)]
5pub struct EducationalAdvice {
6    pub why_bad: String,
7    pub how_to_fix: String,
8    pub best_practice_tip: Option<String>,
9}
10
11pub struct EducationalAdvisor {
12    advice_db: HashMap<String, EducationalAdvice>,
13    lang: String,
14}
15
16impl EducationalAdvisor {
17    pub fn new(lang: &str) -> Self {
18        let mut advisor = Self {
19            advice_db: HashMap::new(),
20            lang: lang.to_string(),
21        };
22        advisor.initialize_advice_database();
23        advisor
24    }
25
26    pub fn get_advice(&self, rule_name: &str) -> Option<&EducationalAdvice> {
27        self.advice_db.get(rule_name)
28    }
29
30    fn initialize_advice_database(&mut self) {
31        // Naming convention advice
32        self.add_advice("terrible-naming", self.create_terrible_naming_advice());
33        self.add_advice(
34            "meaningless-naming",
35            self.create_meaningless_naming_advice(),
36        );
37        self.add_advice(
38            "hungarian-notation",
39            self.create_hungarian_notation_advice(),
40        );
41        self.add_advice(
42            "abbreviation-abuse",
43            self.create_abbreviation_abuse_advice(),
44        );
45
46        // Complexity advice
47        self.add_advice("deep-nesting", self.create_deep_nesting_advice());
48        self.add_advice("god-function", self.create_god_function_advice());
49        self.add_advice("long-function", self.create_long_function_advice());
50
51        // Code smells advice
52        self.add_advice("magic-number", self.create_magic_number_advice());
53        self.add_advice("commented-code", self.create_commented_code_advice());
54        self.add_advice("dead-code", self.create_dead_code_advice());
55
56        // Rust-specific advice
57        self.add_advice("unwrap-abuse", self.create_unwrap_abuse_advice());
58        self.add_advice("string-abuse", self.create_string_abuse_advice());
59        self.add_advice("unnecessary-clone", self.create_unnecessary_clone_advice());
60        self.add_advice("iterator-abuse", self.create_iterator_abuse_advice());
61
62        // Student code advice
63        self.add_advice("println-debugging", self.create_println_debugging_advice());
64        self.add_advice("panic-abuse", self.create_panic_abuse_advice());
65        self.add_advice("todo-comment", self.create_todo_comment_advice());
66
67        // File structure advice
68        self.add_advice("file-too-long", self.create_file_too_long_advice());
69        self.add_advice("unordered-imports", self.create_unordered_imports_advice());
70        self.add_advice(
71            "deep-module-nesting",
72            self.create_deep_module_nesting_advice(),
73        );
74    }
75
76    fn add_advice(&mut self, rule_name: &str, advice: EducationalAdvice) {
77        self.advice_db.insert(rule_name.to_string(), advice);
78    }
79
80    fn create_terrible_naming_advice(&self) -> EducationalAdvice {
81        if self.lang == "zh-CN" {
82            EducationalAdvice {
83                why_bad: "糟糕的变量命名会严重影响代码可读性,让其他开发者(包括未来的你)难以理解代码意图。".to_string(),
84                how_to_fix: "使用描述性的、有意义的变量名,清楚地表达变量的用途和含义。".to_string(),
85                best_practice_tip: Some("变量名应该是自文档化的,读代码的人应该能从名字就理解变量的用途。".to_string()),
86            }
87        } else {
88            EducationalAdvice {
89                why_bad: "Poor variable naming severely impacts code readability, making it difficult for other developers (including future you) to understand the code's intent.".to_string(),
90                how_to_fix: "Use descriptive, meaningful variable names that clearly express the variable's purpose and meaning.".to_string(),
91                best_practice_tip: Some("Variable names should be self-documenting - readers should understand the purpose from the name alone.".to_string()),
92            }
93        }
94    }
95
96    fn create_meaningless_naming_advice(&self) -> EducationalAdvice {
97        if self.lang == "zh-CN" {
98            EducationalAdvice {
99                why_bad: "使用 foo、bar、data、temp 等占位符命名会让代码失去表达力,增加维护成本。"
100                    .to_string(),
101                how_to_fix: "根据变量的实际用途选择具体的、有意义的名称。".to_string(),
102                best_practice_tip: Some(
103                    "避免使用通用词汇,选择能准确描述数据性质的词汇。".to_string(),
104                ),
105            }
106        } else {
107            EducationalAdvice {
108                why_bad: "Using placeholder names like foo, bar, data, temp makes code lose expressiveness and increases maintenance cost.".to_string(),
109                how_to_fix: "Choose specific, meaningful names based on the variable's actual purpose.".to_string(),
110                best_practice_tip: Some("Avoid generic words, choose words that accurately describe the nature of the data.".to_string()),
111            }
112        }
113    }
114
115    fn create_hungarian_notation_advice(&self) -> EducationalAdvice {
116        if self.lang == "zh-CN" {
117            EducationalAdvice {
118                why_bad:
119                    "匈牙利命名法在现代编程语言中已经过时,Rust 的类型系统已经提供了类型安全保障。"
120                        .to_string(),
121                how_to_fix: "使用描述性名称而不是类型前缀,让 Rust 的类型系统处理类型检查。"
122                    .to_string(),
123                best_practice_tip: Some(
124                    "Rust 的强类型系统使得类型前缀变得多余,专注于语义而非类型。".to_string(),
125                ),
126            }
127        } else {
128            EducationalAdvice {
129                why_bad: "Hungarian notation is outdated in modern programming languages, Rust's type system already provides type safety guarantees.".to_string(),
130                how_to_fix: "Use descriptive names instead of type prefixes, let Rust's type system handle type checking.".to_string(),
131                best_practice_tip: Some("Rust's strong type system makes type prefixes redundant, focus on semantics rather than types.".to_string()),
132            }
133        }
134    }
135
136    fn create_abbreviation_abuse_advice(&self) -> EducationalAdvice {
137        if self.lang == "zh-CN" {
138            EducationalAdvice {
139                why_bad: "过度缩写会让代码变得难以理解,特别是对新团队成员或几个月后的自己。"
140                    .to_string(),
141                how_to_fix: "使用完整的、清晰的单词,现代编辑器的自动补全让长名称不再是问题。"
142                    .to_string(),
143                best_practice_tip: Some(
144                    "清晰胜过简洁,代码被阅读的次数远超过被编写的次数。".to_string(),
145                ),
146            }
147        } else {
148            EducationalAdvice {
149                why_bad: "Excessive abbreviations make code hard to understand, especially for new team members or yourself months later.".to_string(),
150                how_to_fix: "Use complete, clear words. Modern editors' auto-completion makes long names no longer a problem.".to_string(),
151                best_practice_tip: Some("Clarity over brevity - code is read far more often than it's written.".to_string()),
152            }
153        }
154    }
155
156    fn create_deep_nesting_advice(&self) -> EducationalAdvice {
157        if self.lang == "zh-CN" {
158            EducationalAdvice {
159                why_bad: "深层嵌套增加了代码的认知复杂度,使得逻辑难以跟踪和调试。".to_string(),
160                how_to_fix: "使用早期返回、提取函数、或者 Rust 的 ? 操作符来减少嵌套层级。"
161                    .to_string(),
162                best_practice_tip: Some(
163                    "保持嵌套层级在 3 层以内,使用卫语句和早期返回。".to_string(),
164                ),
165            }
166        } else {
167            EducationalAdvice {
168                why_bad: "Deep nesting increases cognitive complexity, making logic hard to follow and debug.".to_string(),
169                how_to_fix: "Use early returns, extract functions, or Rust's ? operator to reduce nesting levels.".to_string(),
170                best_practice_tip: Some("Keep nesting levels within 3, use guard clauses and early returns.".to_string()),
171            }
172        }
173    }
174
175    fn create_unwrap_abuse_advice(&self) -> EducationalAdvice {
176        if self.lang == "zh-CN" {
177            EducationalAdvice {
178                why_bad:
179                    "过度使用 unwrap() 会导致程序在遇到错误时直接崩溃,无法优雅地处理异常情况。"
180                        .to_string(),
181                how_to_fix: "使用 match、if let、或者 ? 操作符来正确处理 Option 和 Result 类型。"
182                    .to_string(),
183                best_practice_tip: Some(
184                    "只在你确定不会失败的情况下使用 unwrap(),并添加注释说明原因。".to_string(),
185                ),
186            }
187        } else {
188            EducationalAdvice {
189                why_bad: "Excessive use of unwrap() causes programs to crash directly when encountering errors, unable to handle exceptions gracefully.".to_string(),
190                how_to_fix: "Use match, if let, or the ? operator to properly handle Option and Result types.".to_string(),
191                best_practice_tip: Some("Only use unwrap() when you're certain it won't fail, and add comments explaining why.".to_string()),
192            }
193        }
194    }
195
196    fn create_string_abuse_advice(&self) -> EducationalAdvice {
197        if self.lang == "zh-CN" {
198            EducationalAdvice {
199                why_bad:
200                    "不必要的 String 分配会增加内存使用和性能开销,特别是在只需要读取的场景中。"
201                        .to_string(),
202                how_to_fix: "在只需要读取字符串的地方使用 &str,只在需要拥有所有权时使用 String。"
203                    .to_string(),
204                best_practice_tip: Some(
205                    "优先使用 &str 作为函数参数,这样可以接受 String 和 &str 两种类型。"
206                        .to_string(),
207                ),
208            }
209        } else {
210            EducationalAdvice {
211                why_bad: "Unnecessary String allocations increase memory usage and performance overhead, especially in read-only scenarios.".to_string(),
212                how_to_fix: "Use &str where you only need to read strings, use String only when you need ownership.".to_string(),
213                best_practice_tip: Some("Prefer &str as function parameters, this way you can accept both String and &str types.".to_string()),
214            }
215        }
216    }
217
218    fn create_println_debugging_advice(&self) -> EducationalAdvice {
219        if self.lang == "zh-CN" {
220            EducationalAdvice {
221                why_bad: "遗留的 println! 调试语句会污染输出,在生产环境中可能泄露敏感信息。"
222                    .to_string(),
223                how_to_fix: "使用 log 库进行日志记录,或者使用 dbg! 宏进行临时调试(记得删除)。"
224                    .to_string(),
225                best_practice_tip: Some(
226                    "使用条件编译 #[cfg(debug_assertions)] 来确保调试代码不会进入生产环境。"
227                        .to_string(),
228                ),
229            }
230        } else {
231            EducationalAdvice {
232                why_bad: "Leftover println! debug statements pollute output and may leak sensitive information in production.".to_string(),
233                how_to_fix: "Use the log crate for logging, or use the dbg! macro for temporary debugging (remember to remove).".to_string(),
234                best_practice_tip: Some("Use conditional compilation #[cfg(debug_assertions)] to ensure debug code doesn't reach production.".to_string()),
235            }
236        }
237    }
238
239    fn create_file_too_long_advice(&self) -> EducationalAdvice {
240        if self.lang == "zh-CN" {
241            EducationalAdvice {
242                why_bad: "过长的文件难以导航和维护,违反了单一职责原则,增加了代码复杂度。"
243                    .to_string(),
244                how_to_fix: "将大文件拆分成多个小模块,每个模块负责特定的功能领域。".to_string(),
245                best_practice_tip: Some(
246                    "保持文件在 500-1000 行以内,超出时考虑按功能拆分。".to_string(),
247                ),
248            }
249        } else {
250            EducationalAdvice {
251                why_bad: "Overly long files are hard to navigate and maintain, violate the single responsibility principle, and increase code complexity.".to_string(),
252                how_to_fix: "Split large files into multiple small modules, each responsible for specific functional areas.".to_string(),
253                best_practice_tip: Some("Keep files within 500-1000 lines, consider splitting by functionality when exceeded.".to_string()),
254            }
255        }
256    }
257
258    // Add more advice creation methods for other rules...
259    fn create_god_function_advice(&self) -> EducationalAdvice {
260        if self.lang == "zh-CN" {
261            EducationalAdvice {
262                why_bad: "做太多事情的函数违反了单一职责原则,难以测试和维护。".to_string(),
263                how_to_fix: "将大函数拆分成更小的、专注的函数,每个函数只做一件事。".to_string(),
264                best_practice_tip: Some("保持函数在 20-30 行以内,专注于单一任务。".to_string()),
265            }
266        } else {
267            EducationalAdvice {
268                why_bad: "Functions that do too much violate the single responsibility principle and are hard to test and maintain.".to_string(),
269                how_to_fix: "Break down large functions into smaller, focused functions that each do one thing well.".to_string(),
270                best_practice_tip: Some("Keep functions under 20-30 lines and focused on a single task.".to_string()),
271            }
272        }
273    }
274
275    fn create_long_function_advice(&self) -> EducationalAdvice {
276        if self.lang == "zh-CN" {
277            EducationalAdvice {
278                why_bad: "长函数更难理解、测试和维护。".to_string(),
279                how_to_fix: "将逻辑块提取到具有描述性名称的独立函数中。".to_string(),
280                best_practice_tip: Some(
281                    "如果你无法在屏幕上看到整个函数,那它可能太长了。".to_string(),
282                ),
283            }
284        } else {
285            EducationalAdvice {
286                why_bad: "Long functions are harder to understand, test, and maintain.".to_string(),
287                how_to_fix:
288                    "Extract logical blocks into separate functions with descriptive names."
289                        .to_string(),
290                best_practice_tip: Some(
291                    "If you can't see the entire function on your screen, it's probably too long."
292                        .to_string(),
293                ),
294            }
295        }
296    }
297
298    fn create_magic_number_advice(&self) -> EducationalAdvice {
299        if self.lang == "zh-CN" {
300            EducationalAdvice {
301                why_bad: "魔法数字让代码难以理解和维护。".to_string(),
302                how_to_fix: "用能解释其用途的命名常量替换魔法数字。".to_string(),
303                best_practice_tip: Some("对具有语义含义的值使用 const 声明。".to_string()),
304            }
305        } else {
306            EducationalAdvice {
307                why_bad: "Magic numbers make code hard to understand and maintain.".to_string(),
308                how_to_fix:
309                    "Replace magic numbers with named constants that explain their purpose."
310                        .to_string(),
311                best_practice_tip: Some(
312                    "Use const declarations for values that have semantic meaning.".to_string(),
313                ),
314            }
315        }
316    }
317
318    fn create_commented_code_advice(&self) -> EducationalAdvice {
319        if self.lang == "zh-CN" {
320            EducationalAdvice {
321                why_bad: "注释掉的代码会污染代码库,让人困惑哪些是真正在使用的代码。".to_string(),
322                how_to_fix: "删除注释掉的代码 - 版本控制系统会保留历史记录。".to_string(),
323                best_practice_tip: Some(
324                    "相信你的版本控制系统 - 删除死代码而不是注释掉它。".to_string(),
325                ),
326            }
327        } else {
328            EducationalAdvice {
329                why_bad: "Commented-out code clutters the codebase and creates confusion about what's actually used.".to_string(),
330                how_to_fix: "Remove commented code - version control systems preserve history.".to_string(),
331                best_practice_tip: Some("Trust your version control system - delete dead code instead of commenting it out.".to_string()),
332            }
333        }
334    }
335
336    fn create_dead_code_advice(&self) -> EducationalAdvice {
337        if self.lang == "zh-CN" {
338            EducationalAdvice {
339                why_bad: "死代码增加了维护负担,会让开发者困惑。".to_string(),
340                how_to_fix: "定期删除未使用的函数、变量和导入。".to_string(),
341                best_practice_tip: Some("使用 cargo clippy 自动检测死代码。".to_string()),
342            }
343        } else {
344            EducationalAdvice {
345                why_bad: "Dead code increases maintenance burden and can confuse developers."
346                    .to_string(),
347                how_to_fix: "Remove unused functions, variables, and imports regularly."
348                    .to_string(),
349                best_practice_tip: Some(
350                    "Use cargo clippy to detect dead code automatically.".to_string(),
351                ),
352            }
353        }
354    }
355
356    fn create_unnecessary_clone_advice(&self) -> EducationalAdvice {
357        if self.lang == "zh-CN" {
358            EducationalAdvice {
359                why_bad: "不必要的 clone 会浪费内存和 CPU 周期。".to_string(),
360                how_to_fix: "尽可能使用引用,只在需要所有权时才 clone。".to_string(),
361                best_practice_tip: Some("理解 Rust 的借用规则以减少不必要的分配。".to_string()),
362            }
363        } else {
364            EducationalAdvice {
365                why_bad: "Unnecessary clones waste memory and CPU cycles.".to_string(),
366                how_to_fix: "Use references when possible, clone only when you need ownership."
367                    .to_string(),
368                best_practice_tip: Some(
369                    "Understand Rust's borrowing rules to minimize unnecessary allocations."
370                        .to_string(),
371                ),
372            }
373        }
374    }
375
376    fn create_iterator_abuse_advice(&self) -> EducationalAdvice {
377        if self.lang == "zh-CN" {
378            EducationalAdvice {
379                why_bad: "手动循环通常比迭代器链更低效且表达力差。".to_string(),
380                how_to_fix: "在适当的情况下使用 map、filter、fold 等迭代器方法代替手动循环。"
381                    .to_string(),
382                best_practice_tip: Some(
383                    "迭代器链由于惰性求值和编译器优化,通常更高效。".to_string(),
384                ),
385            }
386        } else {
387            EducationalAdvice {
388                why_bad: "Manual loops are often less efficient and expressive than iterator chains.".to_string(),
389                how_to_fix: "Use iterator methods like map, filter, fold instead of manual loops when appropriate.".to_string(),
390                best_practice_tip: Some("Iterator chains are often more efficient due to lazy evaluation and compiler optimizations.".to_string()),
391            }
392        }
393    }
394
395    fn create_panic_abuse_advice(&self) -> EducationalAdvice {
396        if self.lang == "zh-CN" {
397            EducationalAdvice {
398                why_bad: "过度使用 panic 会让程序在生产环境中不可靠且难以调试。".to_string(),
399                how_to_fix: "对可恢复的错误使用 Result 类型,仅在真正无法恢复的情况下使用 panic。"
400                    .to_string(),
401                best_practice_tip: Some(
402                    "Panic 应该用于编程错误,而不是预期的错误条件。".to_string(),
403                ),
404            }
405        } else {
406            EducationalAdvice {
407                why_bad: "Excessive panics make programs unreliable and hard to debug in production.".to_string(),
408                how_to_fix: "Use Result types for recoverable errors, reserve panics for truly unrecoverable situations.".to_string(),
409                best_practice_tip: Some("Panics should be used for programming errors, not for expected error conditions.".to_string()),
410            }
411        }
412    }
413
414    fn create_todo_comment_advice(&self) -> EducationalAdvice {
415        if self.lang == "zh-CN" {
416            EducationalAdvice {
417                why_bad: "过多的 TODO 注释表示代码不完整或规划不当。".to_string(),
418                how_to_fix: "要么实现这些 TODO,要么为未来的工作创建适当的问题跟踪。".to_string(),
419                best_practice_tip: Some(
420                    "谨慎使用 TODO,并始终附带具体的解决方案计划。".to_string(),
421                ),
422            }
423        } else {
424            EducationalAdvice {
425                why_bad: "Too many TODO comments indicate incomplete or poorly planned code."
426                    .to_string(),
427                how_to_fix:
428                    "Either implement the TODOs or create proper issue tracking for future work."
429                        .to_string(),
430                best_practice_tip: Some(
431                    "Use TODO sparingly and always with a specific plan for resolution."
432                        .to_string(),
433                ),
434            }
435        }
436    }
437
438    fn create_unordered_imports_advice(&self) -> EducationalAdvice {
439        EducationalAdvice {
440            why_bad: "Unordered imports make it hard to find and manage dependencies.".to_string(),
441            how_to_fix: "Use rustfmt to automatically sort imports, or sort them manually by: std, external crates, local modules.".to_string(),
442            best_practice_tip: Some("Configure your editor to run rustfmt on save to maintain consistent formatting.".to_string()),
443        }
444    }
445
446    fn create_deep_module_nesting_advice(&self) -> EducationalAdvice {
447        EducationalAdvice {
448            why_bad: "Deep module nesting makes code navigation difficult and indicates poor architecture.".to_string(),
449            how_to_fix: "Flatten module structure, use re-exports to maintain clean public APIs.".to_string(),
450            best_practice_tip: Some("Keep module nesting to 2-3 levels maximum, use re-exports for convenience.".to_string()),
451        }
452    }
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458
459    const ALL_RULES: &[&str] = &[
460        "terrible-naming",
461        "meaningless-naming",
462        "hungarian-notation",
463        "abbreviation-abuse",
464        "deep-nesting",
465        "god-function",
466        "long-function",
467        "magic-number",
468        "commented-code",
469        "dead-code",
470        "unwrap-abuse",
471        "string-abuse",
472        "unnecessary-clone",
473        "iterator-abuse",
474        "println-debugging",
475        "panic-abuse",
476        "todo-comment",
477        "file-too-long",
478        "unordered-imports",
479        "deep-module-nesting",
480    ];
481
482    /// Objective: Verify that every known rule has English advice registered.
483    /// Invariants: If a rule is added to the engine but not the advice DB,
484    ///             calling get_advice must return Some(...).
485    #[test]
486    fn test_all_rules_have_english_advice() {
487        let advisor = EducationalAdvisor::new("en");
488        for rule in ALL_RULES {
489            let advice = advisor.get_advice(rule);
490            assert!(
491                advice.is_some(),
492                "rule '{rule}' has no advice registered in English database"
493            );
494            let a = advice.unwrap();
495            assert!(!a.why_bad.is_empty(), "rule '{rule}' has empty why_bad");
496            assert!(
497                !a.how_to_fix.is_empty(),
498                "rule '{rule}' has empty how_to_fix"
499            );
500        }
501    }
502
503    /// Objective: Verify that every known rule has Chinese advice registered.
504    /// Invariants: Even English-only rules (unordered-imports, deep-module-nesting)
505    ///             should still return Some (they store English text even when lang=zh-CN).
506    #[test]
507    fn test_all_rules_have_chinese_advice() {
508        let advisor = EducationalAdvisor::new("zh-CN");
509        for rule in ALL_RULES {
510            let advice = advisor.get_advice(rule);
511            assert!(
512                advice.is_some(),
513                "rule '{rule}' has no advice when lang=zh-CN"
514            );
515        }
516    }
517
518    /// Objective: Verify unknown rule returns None, not a crash.
519    #[test]
520    fn test_unknown_rule_returns_none() {
521        let advisor = EducationalAdvisor::new("en");
522        assert!(
523            advisor.get_advice("nonexistent-rule").is_none(),
524            "unknown rule should return None"
525        );
526    }
527
528    /// Objective: Verify that English `terrible-naming` advice contains
529    ///            expected English tokens, confirming the i18n path works.
530    #[test]
531    fn test_english_terrible_naming_has_expected_content() {
532        let advisor = EducationalAdvisor::new("en");
533        let advice = advisor
534            .get_advice("terrible-naming")
535            .expect("terrible-naming should exist");
536        assert!(
537            advice.why_bad.contains("readability"),
538            "English why_bad should mention readability, got: {}",
539            advice.why_bad
540        );
541        assert!(
542            advice.how_to_fix.contains("descriptive"),
543            "English how_to_fix should suggest descriptive names, got: {}",
544            advice.how_to_fix
545        );
546        assert!(
547            advice
548                .best_practice_tip
549                .as_ref()
550                .unwrap()
551                .contains("self-documenting"),
552            "English tip should mention self-documenting"
553        );
554    }
555
556    /// Objective: Verify that Chinese `terrible-naming` advice contains
557    ///            expected Chinese characters, confirming the zh-CN path works.
558    #[test]
559    fn test_chinese_terrible_naming_has_expected_content() {
560        let advisor = EducationalAdvisor::new("zh-CN");
561        let advice = advisor
562            .get_advice("terrible-naming")
563            .expect("terrible-naming should exist");
564        assert!(
565            advice.why_bad.contains("糟糕"),
566            "Chinese why_bad should contain '糟糕', got: {}",
567            advice.why_bad
568        );
569        assert!(
570            advice.how_to_fix.contains("描述性"),
571            "Chinese how_to_fix should contain '描述性', got: {}",
572            advice.how_to_fix
573        );
574        assert!(
575            advice
576                .best_practice_tip
577                .as_ref()
578                .unwrap()
579                .contains("自文档化"),
580            "Chinese tip should contain '自文档化'"
581        );
582    }
583
584    /// Objective: Verify ALL English advice entries have a non-None best_practice_tip.
585    /// Invariants: best_practice_tip is always Some for every rule in English.
586    #[test]
587    fn test_all_english_advice_have_best_practice_tip() {
588        let advisor = EducationalAdvisor::new("en");
589        for (rule, advice) in &advisor.advice_db {
590            assert!(
591                advice.best_practice_tip.is_some(),
592                "rule '{rule}' is missing best_practice_tip in English"
593            );
594        }
595    }
596
597    /// Objective: Verify ALL Chinese advice entries have a non-None best_practice_tip.
598    #[test]
599    fn test_all_chinese_advice_have_best_practice_tip() {
600        let advisor = EducationalAdvisor::new("zh-CN");
601        for (rule, advice) in &advisor.advice_db {
602            assert!(
603                advice.best_practice_tip.is_some(),
604                "rule '{rule}' is missing best_practice_tip in Chinese"
605            );
606        }
607    }
608
609    /// Objective: Verify that English-only rules (unordered-imports, deep-module-nesting)
610    ///            still contain English text even when lang=zh-CN.
611    /// Invariants: These rules have no Chinese translation; they fall back to English.
612    #[test]
613    fn test_english_only_rules_remain_english_in_chinese_mode() {
614        let advisor = EducationalAdvisor::new("zh-CN");
615        for rule in &["unordered-imports", "deep-module-nesting"] {
616            let advice = advisor
617                .get_advice(rule)
618                .expect("rule should exist in zh-CN");
619            assert!(
620                advice.why_bad.contains(' '),
621                "rule '{rule}' in zh-CN should still contain English (has spaces): {}",
622                advice.why_bad
623            );
624        }
625    }
626
627    /// Objective: Verify the advice DB is initialized with exactly the expected
628    ///            number of entries, catching accidental deletions or duplicates.
629    /// Invariants: The count must match the number of unique rules that have advice.
630    #[test]
631    fn test_advice_db_has_expected_rule_count() {
632        let advisor = EducationalAdvisor::new("en");
633        // 4 naming + 3 complexity + 3 code-smells + 4 Rust-specific + 3 student + 3 file-structure = 20
634        assert_eq!(
635            advisor.advice_db.len(),
636            ALL_RULES.len(),
637            "expected {} rules in advice DB, got {}",
638            ALL_RULES.len(),
639            advisor.advice_db.len()
640        );
641    }
642
643    /// Objective: Verify that how_to_fix for every English rule is at least 10 chars long,
644    ///            meaning it provides meaningful guidance rather than a one-liner.
645    /// Invariants: Short how_to_fix would indicate content was accidentally left incomplete.
646    #[test]
647    fn test_how_to_fix_minimum_length() {
648        let advisor = EducationalAdvisor::new("en");
649        let min_len = 10;
650        for (rule, advice) in &advisor.advice_db {
651            assert!(
652                advice.how_to_fix.len() >= min_len,
653                "rule '{rule}' how_to_fix is too short ({}, min {min_len}): '{}'",
654                advice.how_to_fix.len(),
655                advice.how_to_fix
656            );
657        }
658    }
659
660    /// Objective: Verify that advice is deterministic — same rule, same lang, same content.
661    /// Invariants: Two advisor instances with the same language produce identical advice.
662    #[test]
663    fn test_advice_is_deterministic() {
664        let a1 = EducationalAdvisor::new("en");
665        let a2 = EducationalAdvisor::new("en");
666        for rule in ALL_RULES {
667            let adv1 = a1.get_advice(rule).expect("rule should exist");
668            let adv2 = a2.get_advice(rule).expect("rule should exist");
669            assert_eq!(
670                adv1.why_bad, adv2.why_bad,
671                "rule '{rule}' why_bad differs between instances"
672            );
673            assert_eq!(
674                adv1.how_to_fix, adv2.how_to_fix,
675                "rule '{rule}' how_to_fix differs between instances"
676            );
677        }
678    }
679}