rigsql_rules/capitalisation/
cp05.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::utils::check_capitalisation;
5use crate::violation::LintViolation;
6
7#[derive(Debug)]
11pub struct RuleCP05 {
12 pub policy: DataTypeCapPolicy,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum DataTypeCapPolicy {
17 Upper,
18 Lower,
19}
20
21impl Default for RuleCP05 {
22 fn default() -> Self {
23 Self {
24 policy: DataTypeCapPolicy::Upper,
25 }
26 }
27}
28
29impl Rule for RuleCP05 {
30 fn code(&self) -> &'static str {
31 "CP05"
32 }
33 fn name(&self) -> &'static str {
34 "capitalisation.types"
35 }
36 fn description(&self) -> &'static str {
37 "Data type names must be consistently capitalised."
38 }
39 fn explanation(&self) -> &'static str {
40 "Data type names (INT, VARCHAR, TEXT, etc.) should use consistent capitalisation. \
41 Most style guides recommend upper case for data types to distinguish them from column names."
42 }
43 fn groups(&self) -> &[RuleGroup] {
44 &[RuleGroup::Capitalisation]
45 }
46 fn is_fixable(&self) -> bool {
47 true
48 }
49
50 fn configure(&mut self, settings: &std::collections::HashMap<String, String>) {
51 if let Some(val) = settings.get("capitalisation_policy") {
52 self.policy = match val.as_str() {
53 "lower" => DataTypeCapPolicy::Lower,
54 _ => DataTypeCapPolicy::Upper,
55 };
56 }
57 }
58
59 fn crawl_type(&self) -> CrawlType {
60 CrawlType::Segment(vec![SegmentType::DataType])
61 }
62
63 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
64 let tokens = ctx.segment.tokens();
66 let mut violations = Vec::new();
67
68 for token in tokens {
69 let text = token.text.as_str();
70 if text
72 .chars()
73 .all(|c| c.is_ascii_digit() || c == '(' || c == ')' || c == ',')
74 {
75 continue;
76 }
77
78 let (expected, policy_name) = match self.policy {
79 DataTypeCapPolicy::Upper => (text.to_ascii_uppercase(), "upper"),
80 DataTypeCapPolicy::Lower => (text.to_ascii_lowercase(), "lower"),
81 };
82
83 if let Some(v) = check_capitalisation(
84 self.code(),
85 "Data type",
86 text,
87 &expected,
88 policy_name,
89 token.span,
90 ) {
91 violations.push(v);
92 }
93 }
94
95 violations
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use crate::test_utils::lint_sql;
103
104 #[test]
105 fn test_cp05_flags_lowercase_type() {
106 let violations = lint_sql("SELECT CAST(1 AS int)", RuleCP05::default());
107 assert_eq!(violations.len(), 1);
108 }
109
110 #[test]
111 fn test_cp05_accepts_uppercase_type() {
112 let violations = lint_sql("SELECT CAST(1 AS INT)", RuleCP05::default());
113 assert_eq!(violations.len(), 0);
114 }
115
116 #[test]
117 fn test_cp05_lower_policy() {
118 let rule = RuleCP05 {
119 policy: DataTypeCapPolicy::Lower,
120 };
121 let violations = lint_sql("SELECT CAST(1 AS INT)", rule);
122 assert_eq!(violations.len(), 1);
123 }
124}