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::with_msg_key(
55                self.code(),
56                "Nested CASE expression found.",
57                ctx.segment.span(),
58                "rules.ST04.msg",
59                vec![],
60            )];
61        }
62
63        vec![]
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use crate::test_utils::lint_sql;
71
72    #[test]
73    fn test_st04_flags_nested_case() {
74        let violations = lint_sql(
75            "SELECT CASE WHEN x = 1 THEN CASE WHEN y = 2 THEN 'a' ELSE 'b' END ELSE 'c' END;",
76            RuleST04,
77        );
78        // The outer CASE is flagged (it contains a nested CASE)
79        assert!(!violations.is_empty());
80        assert!(violations[0].message.contains("Nested CASE"));
81    }
82
83    #[test]
84    fn test_st04_accepts_simple_case() {
85        let violations = lint_sql("SELECT CASE WHEN x = 1 THEN 'a' ELSE 'b' END;", RuleST04);
86        assert_eq!(violations.len(), 0);
87    }
88}