rigsql_rules/convention/
cv01.rs1use rigsql_core::{Segment, SegmentType, TokenKind};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::{LintViolation, SourceEdit};
5
6#[derive(Debug)]
10pub struct RuleCV01 {
11 pub preferred: NotEqualStyle,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum NotEqualStyle {
16 CStyle,
18 AnsiStyle,
20}
21
22impl Default for RuleCV01 {
23 fn default() -> Self {
24 Self {
25 preferred: NotEqualStyle::CStyle,
26 }
27 }
28}
29
30impl Rule for RuleCV01 {
31 fn code(&self) -> &'static str {
32 "CV01"
33 }
34 fn name(&self) -> &'static str {
35 "convention.not_equal"
36 }
37 fn description(&self) -> &'static str {
38 "Consistent not-equal operator."
39 }
40 fn explanation(&self) -> &'static str {
41 "SQL has two not-equal operators: '!=' and '<>'. Using one consistently \
42 improves readability. The ANSI standard uses '<>' but '!=' is more common \
43 in modern SQL and programming."
44 }
45 fn groups(&self) -> &[RuleGroup] {
46 &[RuleGroup::Convention]
47 }
48 fn is_fixable(&self) -> bool {
49 true
50 }
51
52 fn configure(&mut self, settings: &std::collections::HashMap<String, String>) {
53 if let Some(val) = settings.get("preferred_not_equal") {
54 self.preferred = match val.as_str() {
55 "ansi" | "<>" => NotEqualStyle::AnsiStyle,
56 _ => NotEqualStyle::CStyle,
57 };
58 }
59 }
60
61 fn crawl_type(&self) -> CrawlType {
62 CrawlType::Segment(vec![SegmentType::ComparisonOperator])
63 }
64
65 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
66 let Segment::Token(t) = ctx.segment else {
67 return vec![];
68 };
69 if t.token.kind != TokenKind::Neq {
70 return vec![];
71 }
72
73 let text = t.token.text.as_str();
74 match self.preferred {
75 NotEqualStyle::CStyle if text == "<>" => {
76 vec![LintViolation::with_fix(
77 self.code(),
78 "Use '!=' instead of '<>'.",
79 t.token.span,
80 vec![SourceEdit::replace(t.token.span, "!=")],
81 )]
82 }
83 NotEqualStyle::AnsiStyle if text == "!=" => {
84 vec![LintViolation::with_fix(
85 self.code(),
86 "Use '<>' instead of '!='.",
87 t.token.span,
88 vec![SourceEdit::replace(t.token.span, "<>")],
89 )]
90 }
91 _ => vec![],
92 }
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::test_utils::lint_sql;
100
101 #[test]
102 fn test_cv01_flags_ansi_neq() {
103 let violations = lint_sql("SELECT * FROM t WHERE a <> b", RuleCV01::default());
104 assert_eq!(violations.len(), 1);
105 }
106
107 #[test]
108 fn test_cv01_accepts_cstyle_neq() {
109 let violations = lint_sql("SELECT * FROM t WHERE a != b", RuleCV01::default());
110 assert_eq!(violations.len(), 0);
111 }
112
113 #[test]
114 fn test_cv01_ansi_policy_flags_cstyle() {
115 let rule = RuleCV01 {
116 preferred: NotEqualStyle::AnsiStyle,
117 };
118 let violations = lint_sql("SELECT * FROM t WHERE a != b", rule);
119 assert_eq!(violations.len(), 1);
120 }
121}