sqruff_lib/rules/structure/
st01.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use ahash::AHashMap;
use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
use sqruff_lib_core::lint_fix::LintFix;
use sqruff_lib_core::parser::segments::base::ErasedSegment;

use crate::core::config::Value;
use crate::core::rules::base::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
use crate::core::rules::context::RuleContext;
use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
use crate::utils::functional::context::FunctionalContext;

#[derive(Default, Debug, Clone)]
pub struct RuleST01;

impl Rule for RuleST01 {
    fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
        Ok(RuleST01.erased())
    }

    fn name(&self) -> &'static str {
        "structure.else_null"
    }

    fn description(&self) -> &'static str {
        "Do not specify 'else null' in a case when statement (redundant)."
    }

    fn long_description(&self) -> &'static str {
        r#"
**Anti-pattern**

```sql
select
    case
        when name like '%cat%' then 'meow'
        when name like '%dog%' then 'woof'
        else null
    end
from x
```

**Best practice**

Omit `else null`

```sql
select
    case
        when name like '%cat%' then 'meow'
        when name like '%dog%' then 'woof'
    end
from x
```
"#
    }

    fn groups(&self) -> &'static [RuleGroups] {
        &[RuleGroups::All, RuleGroups::Structure]
    }

    fn eval(&self, context: RuleContext) -> Vec<LintResult> {
        let anchor = context.segment.clone();

        let children = FunctionalContext::new(context).segment().children(None);
        let else_clause = children.find_first(Some(|it: &ErasedSegment| {
            it.is_type(SyntaxKind::ElseClause)
        }));

        if !else_clause
            .children(Some(|child| child.raw().eq_ignore_ascii_case("NULL")))
            .is_empty()
        {
            let before_else = children.reversed().select::<fn(&ErasedSegment) -> bool>(
                None,
                Some(|it| {
                    matches!(it.get_type(), SyntaxKind::Whitespace | SyntaxKind::Newline)
                        | it.is_meta()
                }),
                else_clause.first().unwrap().into(),
                None,
            );

            let mut fixes = Vec::with_capacity(before_else.len() + 1);
            fixes.push(LintFix::delete(else_clause.first().unwrap().clone()));
            fixes.extend(before_else.into_iter().map(LintFix::delete));

            vec![LintResult::new(anchor.into(), fixes, None, None, None)]
        } else {
            Vec::new()
        }
    }

    fn crawl_behaviour(&self) -> Crawler {
        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::CaseExpression]) }).into()
    }
}