rigsql_rules/structure/
st01.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::{LintViolation, SourceEdit};
5
6#[derive(Debug, Default)]
11pub struct RuleST01;
12
13impl Rule for RuleST01 {
14 fn code(&self) -> &'static str {
15 "ST01"
16 }
17 fn name(&self) -> &'static str {
18 "structure.else_null"
19 }
20 fn description(&self) -> &'static str {
21 "Do not specify redundant ELSE NULL in a CASE expression."
22 }
23 fn explanation(&self) -> &'static str {
24 "A CASE expression without an ELSE clause implicitly returns NULL. \
25 Writing ELSE NULL is therefore redundant and adds unnecessary noise \
26 to the query. Remove the ELSE NULL clause for clarity."
27 }
28 fn groups(&self) -> &[RuleGroup] {
29 &[RuleGroup::Structure]
30 }
31 fn is_fixable(&self) -> bool {
32 true
33 }
34
35 fn crawl_type(&self) -> CrawlType {
36 CrawlType::Segment(vec![SegmentType::CaseExpression])
37 }
38
39 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
40 let children = ctx.segment.children();
41
42 for child in children {
43 if child.segment_type() == SegmentType::ElseClause {
44 let else_children = child.children();
46 let non_trivia: Vec<_> = else_children
47 .iter()
48 .filter(|s| !s.segment_type().is_trivia())
49 .collect();
50
51 if non_trivia.len() == 2
53 && non_trivia[0].segment_type() == SegmentType::Keyword
54 && non_trivia[1].segment_type() == SegmentType::NullLiteral
55 {
56 return vec![LintViolation::with_fix(
57 self.code(),
58 "Redundant ELSE NULL in CASE expression.",
59 child.span(),
60 vec![SourceEdit::delete(child.span())],
61 )];
62 }
63 }
64 }
65
66 vec![]
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use crate::test_utils::lint_sql;
74
75 #[test]
76 fn test_st01_flags_else_null() {
77 let violations = lint_sql("SELECT CASE WHEN x = 1 THEN 'a' ELSE NULL END;", RuleST01);
78 assert_eq!(violations.len(), 1);
79 assert!(violations[0].message.contains("Redundant ELSE NULL"));
80 }
81
82 #[test]
83 fn test_st01_accepts_else_value() {
84 let violations = lint_sql("SELECT CASE WHEN x = 1 THEN 'a' ELSE 'b' END;", RuleST01);
85 assert_eq!(violations.len(), 0);
86 }
87
88 #[test]
89 fn test_st01_accepts_no_else() {
90 let violations = lint_sql("SELECT CASE WHEN x = 1 THEN 'a' END;", RuleST01);
91 assert_eq!(violations.len(), 0);
92 }
93}