Skip to main content

rigsql_rules/structure/
st04.rs

1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6/// ST04: Nested CASE expressions.
7///
8/// Nested CASE statements are hard to read and should be refactored.
9#[derive(Debug, Default)]
10pub struct RuleST04;
11
12impl Rule for RuleST04 {
13    fn code(&self) -> &'static str {
14        "ST04"
15    }
16    fn name(&self) -> &'static str {
17        "structure.nested_case"
18    }
19    fn description(&self) -> &'static str {
20        "Nested CASE expressions should be avoided."
21    }
22    fn explanation(&self) -> &'static str {
23        "Nested CASE expressions make SQL queries difficult to read and maintain. \
24         Consider refactoring the logic using CTEs, subqueries, or separate columns \
25         to improve readability."
26    }
27    fn groups(&self) -> &[RuleGroup] {
28        &[RuleGroup::Structure]
29    }
30    fn is_fixable(&self) -> bool {
31        false
32    }
33
34    fn crawl_type(&self) -> CrawlType {
35        CrawlType::Segment(vec![SegmentType::CaseExpression])
36    }
37
38    fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
39        // Check if any descendant (not self) is also a CaseExpression
40        let mut found_nested = false;
41        let mut is_first = true;
42
43        ctx.segment.walk(&mut |seg| {
44            if is_first {
45                is_first = false;
46                return;
47            }
48            if seg.segment_type() == SegmentType::CaseExpression {
49                found_nested = true;
50            }
51        });
52
53        if found_nested {
54            return vec![LintViolation::new(
55                self.code(),
56                "Nested CASE expression found.",
57                ctx.segment.span(),
58            )];
59        }
60
61        vec![]
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use crate::test_utils::lint_sql;
69
70    #[test]
71    fn test_st04_flags_nested_case() {
72        let violations = lint_sql(
73            "SELECT CASE WHEN x = 1 THEN CASE WHEN y = 2 THEN 'a' ELSE 'b' END ELSE 'c' END;",
74            RuleST04,
75        );
76        // The outer CASE is flagged (it contains a nested CASE)
77        assert!(!violations.is_empty());
78        assert!(violations[0].message.contains("Nested CASE"));
79    }
80
81    #[test]
82    fn test_st04_accepts_simple_case() {
83        let violations = lint_sql("SELECT CASE WHEN x = 1 THEN 'a' ELSE 'b' END;", RuleST04);
84        assert_eq!(violations.len(), 0);
85    }
86}