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::base::ErasedSegment;
5
6use crate::core::config::Value;
7use crate::core::rules::base::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
8use crate::core::rules::context::RuleContext;
9use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
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(None);
65 let else_clause = children.find_first(Some(|it: &ErasedSegment| {
66 it.is_type(SyntaxKind::ElseClause)
67 }));
68
69 if !else_clause
70 .children(Some(|child| child.raw().eq_ignore_ascii_case("NULL")))
71 .is_empty()
72 {
73 let before_else = children.reversed().select::<fn(&ErasedSegment) -> bool>(
74 None,
75 Some(|it| {
76 matches!(it.get_type(), SyntaxKind::Whitespace | SyntaxKind::Newline)
77 | it.is_meta()
78 }),
79 else_clause.first().unwrap().into(),
80 None,
81 );
82
83 let mut fixes = Vec::with_capacity(before_else.len() + 1);
84 fixes.push(LintFix::delete(else_clause.first().unwrap().clone()));
85 fixes.extend(before_else.into_iter().map(LintFix::delete));
86
87 vec![LintResult::new(anchor.into(), fixes, None, None)]
88 } else {
89 Vec::new()
90 }
91 }
92
93 fn crawl_behaviour(&self) -> Crawler {
94 SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::CaseExpression]) }).into()
95 }
96}