sqruff_lib/rules/structure/
st10.rs1use hashbrown::HashMap;
2use smol_str::StrExt;
3use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
4use sqruff_lib_core::parser::segments::ErasedSegment;
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};
10
11#[derive(Default, Debug, Clone)]
12pub struct RuleST10;
13
14impl Rule for RuleST10 {
15 fn load_from_config(&self, _config: &HashMap<String, Value>) -> Result<ErasedRule, String> {
16 Ok(RuleST10.erased())
17 }
18
19 fn name(&self) -> &'static str {
20 "structure.constant_expression"
21 }
22
23 fn description(&self) -> &'static str {
24 "Redundant constant expression."
25 }
26
27 fn long_description(&self) -> &'static str {
28 r#"
29Including an expression that always evaluates to either `TRUE` or `FALSE`
30regardless of the input columns is unnecessary and makes statements harder
31to read and understand.
32
33**Anti-pattern**
34
35```sql
36SELECT *
37FROM my_table
38-- This following WHERE clause is redundant.
39WHERE my_table.col = my_table.col
40```
41
42**Best practice**
43
44```sql
45SELECT *
46FROM my_table
47-- Replace with a condition that includes meaningful logic,
48-- or remove the condition entirely.
49WHERE my_table.col > 3
50```
51"#
52 }
53
54 fn groups(&self) -> &'static [RuleGroups] {
55 &[RuleGroups::All, RuleGroups::Structure]
56 }
57
58 fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
59 let subsegments = context.segment.segments();
60 let count_subsegments = subsegments.len();
61
62 let allowable_literal_expressions = ["1 = 1", "1 = 0"];
63
64 let mut results = Vec::new();
65
66 for (idx, seg) in subsegments.iter().enumerate() {
67 if !seg.is_type(SyntaxKind::ComparisonOperator) {
68 continue;
69 }
70
71 let raw_op = seg.raw();
72 if raw_op.as_str() != "=" && raw_op.as_str() != "!=" && raw_op.as_str() != "<>" {
73 continue;
74 }
75
76 let has_other_operators_before = subsegments[..idx].iter().any(|s| {
82 s.is_type(SyntaxKind::ComparisonOperator) || s.is_type(SyntaxKind::BinaryOperator)
83 });
84
85 if has_other_operators_before {
86 continue;
87 }
88
89 let lhs = subsegments[..idx]
91 .iter()
92 .rev()
93 .find(|s| !is_whitespace_or_newline(s));
94
95 let rhs = subsegments[idx + 1..count_subsegments]
97 .iter()
98 .find(|s| !is_whitespace_or_newline(s));
99
100 let (lhs, rhs) = match (lhs, rhs) {
101 (Some(l), Some(r)) => (l, r),
102 _ => continue,
103 };
104
105 if lhs.is_templated() || rhs.is_templated() {
107 continue;
108 }
109
110 if lhs.is_type(SyntaxKind::NumericLiteral) && rhs.is_type(SyntaxKind::NumericLiteral) {
112 let expr = format!(
113 "{} {} {}",
114 lhs.raw().to_uppercase_smolstr(),
115 raw_op,
116 rhs.raw().to_uppercase_smolstr()
117 );
118 if allowable_literal_expressions.contains(&expr.as_str()) {
119 continue;
120 }
121 } else if is_literal(lhs) && is_literal(rhs) {
122 } else {
124 if lhs.get_type() != rhs.get_type() {
126 continue;
127 }
128 if lhs.raw().to_uppercase_smolstr() != rhs.raw().to_uppercase_smolstr() {
129 continue;
130 }
131 }
132
133 results.push(LintResult::new(seg.clone().into(), Vec::new(), None, None));
135 }
136
137 results
138 }
139
140 fn crawl_behaviour(&self) -> Crawler {
141 SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::Expression]) }).into()
142 }
143}
144
145fn is_whitespace_or_newline(seg: &ErasedSegment) -> bool {
146 matches!(seg.get_type(), SyntaxKind::Whitespace | SyntaxKind::Newline)
147}
148
149fn is_literal(seg: &ErasedSegment) -> bool {
150 matches!(
151 seg.get_type(),
152 SyntaxKind::Literal
153 | SyntaxKind::NumericLiteral
154 | SyntaxKind::QuotedLiteral
155 | SyntaxKind::BooleanLiteral
156 | SyntaxKind::NullLiteral
157 )
158}