rigsql_rules/convention/
cv11.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug)]
10pub struct RuleCV11 {
11 pub preferred_style: CastingStyle,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum CastingStyle {
16 Cast,
18 DoubleColon,
20}
21
22impl Default for RuleCV11 {
23 fn default() -> Self {
24 Self {
25 preferred_style: CastingStyle::Cast,
26 }
27 }
28}
29
30impl Rule for RuleCV11 {
31 fn code(&self) -> &'static str {
32 "CV11"
33 }
34 fn name(&self) -> &'static str {
35 "convention.casting_style"
36 }
37 fn description(&self) -> &'static str {
38 "Enforce consistent type casting style."
39 }
40 fn explanation(&self) -> &'static str {
41 "SQL has multiple ways to cast types: CAST(x AS type), x::type, and CONVERT(). \
42 Using a consistent style improves readability. By default, the ANSI CAST() \
43 syntax is preferred."
44 }
45 fn groups(&self) -> &[RuleGroup] {
46 &[RuleGroup::Convention]
47 }
48 fn is_fixable(&self) -> bool {
49 false
50 }
51
52 fn configure(&mut self, settings: &std::collections::HashMap<String, String>) {
53 if let Some(val) = settings.get("preferred_type_casting_style") {
54 self.preferred_style = match val.as_str() {
55 "::" | "shorthand" => CastingStyle::DoubleColon,
56 _ => CastingStyle::Cast,
57 };
58 }
59 }
60
61 fn crawl_type(&self) -> CrawlType {
62 CrawlType::Segment(vec![
63 SegmentType::CastExpression,
64 SegmentType::TypeCastExpression,
65 ])
66 }
67
68 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
69 let st = ctx.segment.segment_type();
70
71 match self.preferred_style {
72 CastingStyle::Cast if st == SegmentType::TypeCastExpression => {
73 vec![LintViolation::new(
74 self.code(),
75 "Use CAST(x AS type) instead of :: syntax.",
76 ctx.segment.span(),
77 )]
78 }
79 CastingStyle::DoubleColon if st == SegmentType::CastExpression => {
80 vec![LintViolation::new(
81 self.code(),
82 "Use :: syntax instead of CAST(x AS type).",
83 ctx.segment.span(),
84 )]
85 }
86 _ => vec![],
87 }
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use crate::test_utils::lint_sql;
95
96 #[test]
97 fn test_cv11_accepts_cast_by_default() {
98 let violations = lint_sql("SELECT CAST(x AS INT) FROM t", RuleCV11::default());
99 assert_eq!(violations.len(), 0);
100 }
101
102 #[test]
103 fn test_cv11_double_colon_policy_flags_cast() {
104 let rule = RuleCV11 {
105 preferred_style: CastingStyle::DoubleColon,
106 };
107 let violations = lint_sql("SELECT CAST(x AS INT) FROM t", rule);
108 assert_eq!(violations.len(), 1);
109 }
110}