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::ErasedSegment;
6
7use crate::core::config::Value;
8use crate::core::rules::context::RuleContext;
9use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
10use crate::core::rules::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
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_all();
58
59        let function_name = children
60            .find_first_where(|segment: &ErasedSegment| segment.is_type(SyntaxKind::FunctionName))
61            .pop();
62        let function_contents = children
63            .find_first_where(|segment: &ErasedSegment| {
64                segment.is_type(SyntaxKind::FunctionContents)
65            })
66            .pop();
67
68        let mut intermediate_segments =
69            children.between_exclusive(&function_name, &function_contents);
70
71        if !intermediate_segments.is_empty() {
72            return if intermediate_segments.all_match(|seg| {
73                matches!(seg.get_type(), SyntaxKind::Whitespace | SyntaxKind::Newline)
74            }) {
75                vec![LintResult::new(
76                    intermediate_segments.first().cloned(),
77                    intermediate_segments
78                        .into_iter()
79                        .map(LintFix::delete)
80                        .collect_vec(),
81                    None,
82                    None,
83                )]
84            } else {
85                vec![LintResult::new(
86                    intermediate_segments.pop().into(),
87                    vec![],
88                    None,
89                    None,
90                )]
91            };
92        }
93
94        vec![]
95    }
96
97    fn is_fix_compatible(&self) -> bool {
98        true
99    }
100
101    fn crawl_behaviour(&self) -> Crawler {
102        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::Function]) }).into()
103    }
104}