Skip to main content

rigsql_rules/layout/
lt15.rs

1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6/// LT15: DISTINCT should not be followed by a bracket/parenthesis.
7///
8/// `SELECT DISTINCT(col)` should be `SELECT DISTINCT col`.
9#[derive(Debug, Default)]
10pub struct RuleLT15;
11
12impl Rule for RuleLT15 {
13    fn code(&self) -> &'static str {
14        "LT15"
15    }
16    fn name(&self) -> &'static str {
17        "layout.distinct"
18    }
19    fn description(&self) -> &'static str {
20        "DISTINCT used with parentheses."
21    }
22    fn explanation(&self) -> &'static str {
23        "DISTINCT is not a function and should not be used with parentheses. \
24         'SELECT DISTINCT(col)' is misleading because the parentheses don't do anything. \
25         Write 'SELECT DISTINCT col' instead."
26    }
27    fn groups(&self) -> &[RuleGroup] {
28        &[RuleGroup::Layout]
29    }
30    fn is_fixable(&self) -> bool {
31        true
32    }
33
34    fn crawl_type(&self) -> CrawlType {
35        CrawlType::Segment(vec![SegmentType::SelectClause])
36    }
37
38    fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
39        let children = ctx.segment.children();
40
41        // Find DISTINCT keyword
42        for (i, child) in children.iter().enumerate() {
43            if let Segment::Token(t) = child {
44                if t.segment_type == SegmentType::Keyword
45                    && t.token.text.eq_ignore_ascii_case("DISTINCT")
46                {
47                    // Check if next non-trivia is LParen
48                    for next in &children[i + 1..] {
49                        if next.segment_type().is_trivia() {
50                            continue;
51                        }
52                        if next.segment_type() == SegmentType::LParen
53                            || next.segment_type() == SegmentType::ParenExpression
54                        {
55                            return vec![LintViolation::new(
56                                self.code(),
57                                "DISTINCT should not be followed by parentheses.",
58                                t.token.span,
59                            )];
60                        }
61                        break;
62                    }
63                }
64            }
65        }
66
67        vec![]
68    }
69}