cha_core/plugins/
naming.rs1use crate::{AnalysisContext, Finding, Location, Plugin, Severity, SmellCategory};
2
3pub struct NamingAnalyzer {
5 pub min_name_length: usize,
6 pub max_name_length: usize,
7}
8
9impl Default for NamingAnalyzer {
10 fn default() -> Self {
11 Self {
12 min_name_length: 2,
13 max_name_length: 50,
14 }
15 }
16}
17
18impl Plugin for NamingAnalyzer {
19 fn name(&self) -> &str {
20 "naming"
21 }
22
23 fn description(&self) -> &str {
24 "Naming convention violations"
25 }
26
27 fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding> {
28 let mut findings = Vec::new();
29 self.check_functions(ctx, &mut findings);
30 self.check_classes(ctx, &mut findings);
31 findings
32 }
33}
34
35impl NamingAnalyzer {
36 fn check_functions(&self, ctx: &AnalysisContext, findings: &mut Vec<Finding>) {
37 for f in &ctx.model.functions {
38 let check = NameCheck {
39 name: &f.name,
40 kind: "Function",
41 path: &ctx.file.path,
42 start_line: f.start_line,
43 end_line: f.end_line,
44 };
45 if let Some(finding) = check_name(&check, self.min_name_length, self.max_name_length) {
46 findings.push(finding);
47 }
48 }
49 }
50
51 fn check_classes(&self, ctx: &AnalysisContext, findings: &mut Vec<Finding>) {
52 for c in &ctx.model.classes {
53 if let Some(f) = check_pascal_case(c, &ctx.file.path) {
54 findings.push(f);
55 }
56 let check = NameCheck {
57 name: &c.name,
58 kind: "Class",
59 path: &ctx.file.path,
60 start_line: c.start_line,
61 end_line: c.end_line,
62 };
63 if let Some(f) = check_name(&check, self.min_name_length, self.max_name_length) {
64 findings.push(f);
65 }
66 }
67 }
68}
69
70fn check_pascal_case(c: &crate::ClassInfo, path: &std::path::Path) -> Option<Finding> {
72 if c.name.is_empty() || c.name.chars().next().is_some_and(|ch| ch.is_uppercase()) {
73 return None;
74 }
75 Some(Finding {
76 smell_name: "naming_convention".into(),
77 category: SmellCategory::Bloaters,
78 severity: Severity::Hint,
79 location: Location {
80 path: path.to_path_buf(),
81 start_line: c.start_line,
82 end_line: c.end_line,
83 name: Some(c.name.clone()),
84 },
85 message: format!("Class `{}` should use PascalCase", c.name),
86 suggested_refactorings: vec!["Rename Method".into()],
87 ..Default::default()
88 })
89}
90
91struct NameCheck<'a> {
92 name: &'a str,
93 kind: &'a str,
94 path: &'a std::path::Path,
95 start_line: usize,
96 end_line: usize,
97}
98
99fn check_name(check: &NameCheck, min_len: usize, max_len: usize) -> Option<Finding> {
100 let (smell, severity, qualifier, limit) = if check.name.len() < min_len {
101 ("naming_too_short", Severity::Warning, "short", min_len)
102 } else if check.name.len() > max_len {
103 ("naming_too_long", Severity::Hint, "long", max_len)
104 } else {
105 return None;
106 };
107 let bound_label = if qualifier == "short" { "min" } else { "max" };
108 Some(Finding {
109 smell_name: smell.into(),
110 category: SmellCategory::Bloaters,
111 severity,
112 location: Location {
113 path: check.path.to_path_buf(),
114 start_line: check.start_line,
115 end_line: check.end_line,
116 name: Some(check.name.to_string()),
117 },
118 message: format!(
119 "{} `{}` name is too {} ({} chars, {}: {})",
120 check.kind,
121 check.name,
122 qualifier,
123 check.name.len(),
124 bound_label,
125 limit
126 ),
127 suggested_refactorings: vec!["Rename Method".into()],
128 ..Default::default()
129 })
130}