Skip to main content

rigsql_rules/layout/
lt06.rs

1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::{LintViolation, SourceEdit};
5
6/// LT06: Function name not followed immediately by parenthesis.
7///
8/// There should be no whitespace between the function name and the opening
9/// parenthesis, e.g. `COUNT(*)` not `COUNT (*)`.
10#[derive(Debug, Default)]
11pub struct RuleLT06;
12
13impl Rule for RuleLT06 {
14    fn code(&self) -> &'static str {
15        "LT06"
16    }
17    fn name(&self) -> &'static str {
18        "layout.function_paren"
19    }
20    fn description(&self) -> &'static str {
21        "Function name not followed immediately by parenthesis."
22    }
23    fn explanation(&self) -> &'static str {
24        "In SQL, function calls should have the opening parenthesis immediately after \
25         the function name with no intervening whitespace. For example, write COUNT(*) \
26         rather than COUNT (*)."
27    }
28    fn groups(&self) -> &[RuleGroup] {
29        &[RuleGroup::Layout]
30    }
31    fn is_fixable(&self) -> bool {
32        true
33    }
34
35    fn crawl_type(&self) -> CrawlType {
36        CrawlType::Segment(vec![SegmentType::FunctionCall])
37    }
38
39    fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
40        let children = ctx.segment.children();
41
42        // Find the function name identifier (first non-trivia child)
43        let mut name_idx = None;
44        for (i, child) in children.iter().enumerate() {
45            if !child.segment_type().is_trivia() {
46                name_idx = Some(i);
47                break;
48            }
49        }
50
51        let Some(name_idx) = name_idx else {
52            return vec![];
53        };
54
55        // Collect whitespace segments between the function name and LParen
56        let mut whitespace_spans = Vec::new();
57        let mut found_lparen = false;
58
59        for child in &children[name_idx + 1..] {
60            let st = child.segment_type();
61            if st == SegmentType::Whitespace || st == SegmentType::Newline {
62                whitespace_spans.push(child.span());
63            } else if st == SegmentType::LParen || st == SegmentType::FunctionArgs {
64                found_lparen = true;
65                break;
66            } else {
67                // Non-trivia, non-lparen — not the pattern we're looking for
68                break;
69            }
70        }
71
72        if !found_lparen || whitespace_spans.is_empty() {
73            return vec![];
74        }
75
76        let fixes: Vec<SourceEdit> = whitespace_spans
77            .iter()
78            .map(|span| SourceEdit::delete(*span))
79            .collect();
80
81        let first_ws = whitespace_spans[0];
82        vec![LintViolation::with_fix(
83            self.code(),
84            "No whitespace allowed between function name and parenthesis.",
85            first_ws,
86            fixes,
87        )]
88    }
89}