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::base::SegmentBuilder;
5
6use crate::core::config::Value;
7use crate::core::rules::base::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
8use crate::core::rules::context::RuleContext;
9use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
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: {}",
35 value
36 ));
37 }
38 };
39 Ok(RuleCV01 {
40 preferred_not_equal_style,
41 }
42 .erased())
43 } else {
44 Err("Missing value for preferred_not_equal_style".to_string())
45 }
46 }
47
48 fn name(&self) -> &'static str {
49 "convention.not_equal"
50 }
51
52 fn description(&self) -> &'static str {
53 "Consistent usage of ``!=`` or ``<>`` for \"not equal to\" operator."
54 }
55
56 fn long_description(&self) -> &'static str {
57 r#"
58**Anti-pattern**
59
60Consistent usage of `!=` or `<>` for "not equal to" operator.
61
62```sql
63SELECT * FROM X WHERE 1 <> 2 AND 3 != 4;
64```
65
66**Best practice**
67
68Ensure all "not equal to" comparisons are consistent, not mixing `!=` and `<>`.
69
70```sql
71SELECT * FROM X WHERE 1 != 2 AND 3 != 4;
72```
73"#
74 }
75
76 fn groups(&self) -> &'static [RuleGroups] {
77 &[RuleGroups::All, RuleGroups::Convention]
78 }
79
80 fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
81 let segment = FunctionalContext::new(context).segment();
83 let raw_comparison_operators = segment.children(None);
84
85 let raw_operator_list = raw_comparison_operators
87 .iter()
88 .map(|r| r.raw())
89 .collect::<Vec<_>>();
90 if raw_operator_list != ["<", ">"] && raw_operator_list != ["!", "="] {
91 return Vec::new();
92 }
93
94 let preferred_style =
96 if self.preferred_not_equal_style == PreferredNotEqualStyle::Consistent {
97 if let Some(preferred_style) = context.try_get::<PreferredNotEqualStyle>() {
98 preferred_style
99 } else {
100 let style = if raw_operator_list == ["<", ">"] {
101 PreferredNotEqualStyle::Ansi
102 } else {
103 PreferredNotEqualStyle::CStyle
104 };
105 context.set(style);
106 style
107 }
108 } else {
109 self.preferred_not_equal_style
110 };
111
112 let replacement = match preferred_style {
114 PreferredNotEqualStyle::CStyle => {
115 vec!["!", "="]
116 }
117 PreferredNotEqualStyle::Ansi => {
118 vec!["<", ">"]
119 }
120 PreferredNotEqualStyle::Consistent => {
121 unreachable!("Consistent style should have been handled earlier")
122 }
123 };
124
125 if raw_operator_list == replacement {
127 return Vec::new();
128 }
129
130 let fixes = vec![
136 LintFix::replace(
137 raw_comparison_operators[0].clone(),
138 vec![
139 SegmentBuilder::token(
140 context.tables.next_id(),
141 replacement[0],
142 SyntaxKind::ComparisonOperator,
143 )
144 .finish(),
145 ],
146 None,
147 ),
148 LintFix::replace(
149 raw_comparison_operators[1].clone(),
150 vec![
151 SegmentBuilder::token(
152 context.tables.next_id(),
153 replacement[1],
154 SyntaxKind::ComparisonOperator,
155 )
156 .finish(),
157 ],
158 None,
159 ),
160 ];
161
162 vec![LintResult::new(
163 context.segment.clone().into(),
164 fixes,
165 None,
166 None,
167 )]
168 }
169
170 fn is_fix_compatible(&self) -> bool {
171 true
172 }
173
174 fn crawl_behaviour(&self) -> Crawler {
175 SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::ComparisonOperator]) })
176 .into()
177 }
178}