sqruff_lib/rules/layout/
lt06.rs1use 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}