sqruff_lib/rules/structure/
st01.rs1use ahash::AHashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3use sqruff_lib_core::lint_fix::LintFix;
4use sqruff_lib_core::parser::segments::ErasedSegment;
5
6use crate::core::config::Value;
7use crate::core::rules::context::RuleContext;
8use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
9use crate::core::rules::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
10use crate::utils::functional::context::FunctionalContext;
11
12#[derive(Default, Debug, Clone)]
13pub struct RuleST01;
14
15impl Rule for RuleST01 {
16 fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
17 Ok(RuleST01.erased())
18 }
19
20 fn name(&self) -> &'static str {
21 "structure.else_null"
22 }
23
24 fn description(&self) -> &'static str {
25 "Do not specify 'else null' in a case when statement (redundant)."
26 }
27
28 fn long_description(&self) -> &'static str {
29 r#"
30**Anti-pattern**
31
32```sql
33select
34 case
35 when name like '%cat%' then 'meow'
36 when name like '%dog%' then 'woof'
37 else null
38 end
39from x
40```
41
42**Best practice**
43
44Omit `else null`
45
46```sql
47select
48 case
49 when name like '%cat%' then 'meow'
50 when name like '%dog%' then 'woof'
51 end
52from x
53```
54"#
55 }
56
57 fn groups(&self) -> &'static [RuleGroups] {
58 &[RuleGroups::All, RuleGroups::Structure]
59 }
60
61 fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
62 let anchor = context.segment.clone();
63
64 let children = FunctionalContext::new(context).segment().children_all();
65 let else_clause =
66 children.find_first_where(|it: &ErasedSegment| it.is_type(SyntaxKind::ElseClause));
67
68 if !else_clause
69 .children_where(|child| child.raw().eq_ignore_ascii_case("NULL"))
70 .is_empty()
71 {
72 let before_else = children
73 .reversed()
74 .after(else_clause.first().unwrap())
75 .take_while(|it| {
76 matches!(it.get_type(), SyntaxKind::Whitespace | SyntaxKind::Newline)
77 | it.is_meta()
78 });
79
80 let mut fixes = Vec::with_capacity(before_else.len() + 1);
81 fixes.push(LintFix::delete(else_clause.first().unwrap().clone()));
82 fixes.extend(before_else.into_iter().map(LintFix::delete));
83
84 vec![LintResult::new(anchor.into(), fixes, None, None)]
85 } else {
86 Vec::new()
87 }
88 }
89
90 fn crawl_behaviour(&self) -> Crawler {
91 SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::CaseExpression]) }).into()
92 }
93}