sqruff_lib/rules/convention/
cv01.rs1use ahash::AHashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3use sqruff_lib_core::lint_fix::LintFix;
4use sqruff_lib_core::parser::segments::SegmentBuilder;
5
6use crate::core::config::Value;
7use crate::core::rules::context::RuleContext;
8use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
9use crate::core::rules::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
10use crate::utils::functional::context::FunctionalContext;
11
12#[derive(Debug, Default, Clone)]
13pub struct RuleCV01 {
14 preferred_not_equal_style: PreferredNotEqualStyle,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Default)]
18enum PreferredNotEqualStyle {
19 #[default]
20 Consistent,
21 CStyle,
22 Ansi,
23}
24
25impl Rule for RuleCV01 {
26 fn load_from_config(&self, config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
27 if let Some(value) = config["preferred_not_equal_style"].as_string() {
28 let preferred_not_equal_style = match value {
29 "consistent" => PreferredNotEqualStyle::Consistent,
30 "c_style" => PreferredNotEqualStyle::CStyle,
31 "ansi" => PreferredNotEqualStyle::Ansi,
32 _ => {
33 return Err(format!(
34 "Invalid value for preferred_not_equal_style: {value}"
35 ));
36 }
37 };
38 Ok(RuleCV01 {
39 preferred_not_equal_style,
40 }
41 .erased())
42 } else {
43 Err("Missing value for preferred_not_equal_style".to_string())
44 }
45 }
46
47 fn name(&self) -> &'static str {
48 "convention.not_equal"
49 }
50
51 fn description(&self) -> &'static str {
52 "Consistent usage of ``!=`` or ``<>`` for \"not equal to\" operator."
53 }
54
55 fn long_description(&self) -> &'static str {
56 r#"
57**Anti-pattern**
58
59Consistent usage of `!=` or `<>` for "not equal to" operator.
60
61```sql
62SELECT * FROM X WHERE 1 <> 2 AND 3 != 4;
63```
64
65**Best practice**
66
67Ensure all "not equal to" comparisons are consistent, not mixing `!=` and `<>`.
68
69```sql
70SELECT * FROM X WHERE 1 != 2 AND 3 != 4;
71```
72"#
73 }
74
75 fn groups(&self) -> &'static [RuleGroups] {
76 &[RuleGroups::All, RuleGroups::Convention]
77 }
78
79 fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
80 let segment = FunctionalContext::new(context).segment();
82 let raw_comparison_operators = segment.children(None);
83
84 let raw_operator_list = raw_comparison_operators
86 .iter()
87 .map(|r| r.raw())
88 .collect::<Vec<_>>();
89 if raw_operator_list != ["<", ">"] && raw_operator_list != ["!", "="] {
90 return Vec::new();
91 }
92
93 let preferred_style =
95 if self.preferred_not_equal_style == PreferredNotEqualStyle::Consistent {
96 if let Some(preferred_style) = context.try_get::<PreferredNotEqualStyle>() {
97 preferred_style
98 } else {
99 let style = if raw_operator_list == ["<", ">"] {
100 PreferredNotEqualStyle::Ansi
101 } else {
102 PreferredNotEqualStyle::CStyle
103 };
104 context.set(style);
105 style
106 }
107 } else {
108 self.preferred_not_equal_style
109 };
110
111 let replacement = match preferred_style {
113 PreferredNotEqualStyle::CStyle => {
114 vec!["!", "="]
115 }
116 PreferredNotEqualStyle::Ansi => {
117 vec!["<", ">"]
118 }
119 PreferredNotEqualStyle::Consistent => {
120 unreachable!("Consistent style should have been handled earlier")
121 }
122 };
123
124 if raw_operator_list == replacement {
126 return Vec::new();
127 }
128
129 let fixes = vec![
135 LintFix::replace(
136 raw_comparison_operators[0].clone(),
137 vec![
138 SegmentBuilder::token(
139 context.tables.next_id(),
140 replacement[0],
141 SyntaxKind::ComparisonOperator,
142 )
143 .finish(),
144 ],
145 None,
146 ),
147 LintFix::replace(
148 raw_comparison_operators[1].clone(),
149 vec![
150 SegmentBuilder::token(
151 context.tables.next_id(),
152 replacement[1],
153 SyntaxKind::ComparisonOperator,
154 )
155 .finish(),
156 ],
157 None,
158 ),
159 ];
160
161 vec![LintResult::new(
162 context.segment.clone().into(),
163 fixes,
164 None,
165 None,
166 )]
167 }
168
169 fn is_fix_compatible(&self) -> bool {
170 true
171 }
172
173 fn crawl_behaviour(&self) -> Crawler {
174 SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::ComparisonOperator]) })
175 .into()
176 }
177}