sqruff_lib/rules/capitalisation/
cp04.rs

1use ahash::AHashMap;
2use regex::Regex;
3use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
4
5use super::cp01::RuleCP01;
6use crate::core::config::Value;
7use crate::core::rules::base::{CloneRule, ErasedRule, LintResult, Rule, RuleGroups};
8use crate::core::rules::context::RuleContext;
9use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
10
11#[derive(Clone, Debug)]
12pub struct RuleCP04 {
13    base: RuleCP01,
14}
15
16impl Default for RuleCP04 {
17    fn default() -> Self {
18        Self {
19            base: RuleCP01 {
20                skip_literals: false,
21                exclude_parent_types: &[],
22                description_elem: "Boolean/null literals",
23                ..Default::default()
24            },
25        }
26    }
27}
28
29impl Rule for RuleCP04 {
30    fn load_from_config(&self, config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
31        Ok(RuleCP04 {
32            base: RuleCP01 {
33                capitalisation_policy: config["capitalisation_policy"].as_string().unwrap().into(),
34                ignore_words: config["ignore_words"]
35                    .map(|it| {
36                        it.as_array()
37                            .unwrap_or_default()
38                            .iter()
39                            .map(|it| it.as_string().unwrap().to_lowercase())
40                            .collect()
41                    })
42                    .unwrap_or_default(),
43                ignore_words_regex: config["ignore_words_regex"]
44                    .map(|it| {
45                        it.as_array()
46                            .unwrap_or_default()
47                            .iter()
48                            .map(|it| Regex::new(it.as_string().unwrap()).unwrap())
49                            .collect()
50                    })
51                    .unwrap_or_default(),
52                ..Default::default()
53            },
54        }
55        .erased())
56    }
57
58    fn name(&self) -> &'static str {
59        "capitalisation.literals"
60    }
61
62    fn description(&self) -> &'static str {
63        "Inconsistent capitalisation of boolean/null literal."
64    }
65
66    fn long_description(&self) -> &'static str {
67        r#"
68**Anti-pattern**
69
70In this example, `null` and `false` are in lower-case whereas `TRUE` is in upper-case.
71
72```sql
73select
74    a,
75    null,
76    TRUE,
77    false
78from foo
79```
80
81**Best practice**
82
83Ensure all literal `null`/`true`/`false` literals are consistently upper or lower case
84
85```sql
86select
87    a,
88    NULL,
89    TRUE,
90    FALSE
91from foo
92
93-- Also good
94
95select
96    a,
97    null,
98    true,
99    false
100from foo
101```
102"#
103    }
104
105    fn groups(&self) -> &'static [RuleGroups] {
106        &[
107            RuleGroups::All,
108            RuleGroups::Core,
109            RuleGroups::Capitalisation,
110        ]
111    }
112    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
113        self.base.eval(context)
114    }
115
116    fn is_fix_compatible(&self) -> bool {
117        true
118    }
119
120    fn crawl_behaviour(&self) -> Crawler {
121        SegmentSeekerCrawler::new(
122            const { SyntaxSet::new(&[SyntaxKind::NullLiteral, SyntaxKind::BooleanLiteral]) },
123        )
124        .into()
125    }
126}