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::with_msg_key(
74 self.code(),
75 "Use CAST(x AS type) instead of :: syntax.",
76 ctx.segment.span(),
77 "rules.CV11.msg.cast",
78 vec![],
79 )]
80 }
81 CastingStyle::DoubleColon if st == SegmentType::CastExpression => {
82 vec![LintViolation::with_msg_key(
83 self.code(),
84 "Use :: syntax instead of CAST(x AS type).",
85 ctx.segment.span(),
86 "rules.CV11.msg.shorthand",
87 vec![],
88 )]
89 }
90 _ => vec![],
91 }
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::test_utils::lint_sql;
99
100 #[test]
101 fn test_cv11_accepts_cast_by_default() {
102 let violations = lint_sql("SELECT CAST(x AS INT) FROM t", RuleCV11::default());
103 assert_eq!(violations.len(), 0);
104 }
105
106 #[test]
107 fn test_cv11_double_colon_policy_flags_cast() {
108 let rule = RuleCV11 {
109 preferred_style: CastingStyle::DoubleColon,
110 };
111 let violations = lint_sql("SELECT CAST(x AS INT) FROM t", rule);
112 assert_eq!(violations.len(), 1);
113 }
114}