sqruff_lib/rules/layout/
lt06.rs

1use ahash::AHashMap;
2use itertools::Itertools;
3use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
4use sqruff_lib_core::lint_fix::LintFix;
5use sqruff_lib_core::parser::segments::base::ErasedSegment;
6
7use crate::core::config::Value;
8use crate::core::rules::base::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
9use crate::core::rules::context::RuleContext;
10use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
11use crate::utils::functional::context::FunctionalContext;
12
13#[derive(Debug, Default, Clone)]
14pub struct RuleLT06;
15
16impl Rule for RuleLT06 {
17    fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
18        Ok(RuleLT06.erased())
19    }
20    fn name(&self) -> &'static str {
21        "layout.functions"
22    }
23
24    fn description(&self) -> &'static str {
25        "Function name not immediately followed by parenthesis."
26    }
27
28    fn long_description(&self) -> &'static str {
29        r#"
30**Anti-pattern**
31
32In this example, there is a space between the function and the parenthesis.
33
34```sql
35SELECT
36    sum (a)
37FROM foo
38```
39
40**Best practice**
41
42Remove the space between the function and the parenthesis.
43
44```sql
45SELECT
46    sum(a)
47FROM foo
48```
49"#
50    }
51
52    fn groups(&self) -> &'static [RuleGroups] {
53        &[RuleGroups::All, RuleGroups::Core, RuleGroups::Layout]
54    }
55    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
56        let segment = FunctionalContext::new(context).segment();
57        let children = segment.children(None);
58
59        let function_name = children
60            .find_first(Some(|segment: &ErasedSegment| {
61                segment.is_type(SyntaxKind::FunctionName)
62            }))
63            .pop();
64        let start_bracket = children
65            .find_first(Some(|segment: &ErasedSegment| {
66                segment.is_type(SyntaxKind::Bracketed)
67            }))
68            .pop();
69
70        let mut intermediate_segments = children.select::<fn(&ErasedSegment) -> bool>(
71            None,
72            None,
73            Some(&function_name),
74            Some(&start_bracket),
75        );
76
77        if !intermediate_segments.is_empty() {
78            return if intermediate_segments.all(Some(|seg| {
79                matches!(seg.get_type(), SyntaxKind::Whitespace | SyntaxKind::Newline)
80            })) {
81                vec![LintResult::new(
82                    intermediate_segments.first().cloned(),
83                    intermediate_segments
84                        .into_iter()
85                        .map(LintFix::delete)
86                        .collect_vec(),
87                    None,
88                    None,
89                )]
90            } else {
91                vec![LintResult::new(
92                    intermediate_segments.pop().into(),
93                    vec![],
94                    None,
95                    None,
96                )]
97            };
98        }
99
100        vec![]
101    }
102
103    fn is_fix_compatible(&self) -> bool {
104        true
105    }
106
107    fn crawl_behaviour(&self) -> Crawler {
108        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::Function]) }).into()
109    }
110}