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_and_msg_key(
57 self.code(),
58 "Redundant ELSE NULL in CASE expression.",
59 child.span(),
60 vec![SourceEdit::delete(child.span())],
61 "rules.ST01.msg",
62 vec![],
63 )];
64 }
65 }
66 }
67
68 vec![]
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::test_utils::lint_sql;
76
77 #[test]
78 fn test_st01_flags_else_null() {
79 let violations = lint_sql("SELECT CASE WHEN x = 1 THEN 'a' ELSE NULL END;", RuleST01);
80 assert_eq!(violations.len(), 1);
81 assert!(violations[0].message.contains("Redundant ELSE NULL"));
82 }
83
84 #[test]
85 fn test_st01_accepts_else_value() {
86 let violations = lint_sql("SELECT CASE WHEN x = 1 THEN 'a' ELSE 'b' END;", RuleST01);
87 assert_eq!(violations.len(), 0);
88 }
89
90 #[test]
91 fn test_st01_accepts_no_else() {
92 let violations = lint_sql("SELECT CASE WHEN x = 1 THEN 'a' END;", RuleST01);
93 assert_eq!(violations.len(), 0);
94 }
95}