Skip to main content

rigsql_rules/convention/
cv01.rs

1use rigsql_core::{Segment, SegmentType, TokenKind};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6/// CV01: Use consistent not-equal operator.
7///
8/// By default, prefer `!=` over `<>`.
9#[derive(Debug)]
10pub struct RuleCV01 {
11    pub preferred: NotEqualStyle,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum NotEqualStyle {
16    /// Prefer `!=`
17    CStyle,
18    /// Prefer `<>`
19    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::new(
77                    self.code(),
78                    "Use '!=' instead of '<>'.",
79                    t.token.span,
80                )]
81            }
82            NotEqualStyle::AnsiStyle if text == "!=" => {
83                vec![LintViolation::new(
84                    self.code(),
85                    "Use '<>' instead of '!='.",
86                    t.token.span,
87                )]
88            }
89            _ => vec![],
90        }
91    }
92}