rigsql_rules/structure/
st08.rs1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
11pub struct RuleST08;
12
13impl Rule for RuleST08 {
14 fn code(&self) -> &'static str {
15 "ST08"
16 }
17 fn name(&self) -> &'static str {
18 "structure.distinct"
19 }
20 fn description(&self) -> &'static str {
21 "DISTINCT used with parentheses is misleading."
22 }
23 fn explanation(&self) -> &'static str {
24 "DISTINCT is a keyword that applies to the entire SELECT result set, not a function. \
25 Writing SELECT DISTINCT(col) suggests DISTINCT operates on a single column like a \
26 function, which is misleading. Use SELECT DISTINCT col instead."
27 }
28 fn groups(&self) -> &[RuleGroup] {
29 &[RuleGroup::Structure]
30 }
31 fn is_fixable(&self) -> bool {
32 false
33 }
34
35 fn crawl_type(&self) -> CrawlType {
36 CrawlType::Segment(vec![SegmentType::SelectClause])
37 }
38
39 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
40 let children = ctx.segment.children();
41
42 let mut found_distinct = false;
44 for child in children {
45 if child.segment_type().is_trivia() {
46 continue;
47 }
48
49 if found_distinct {
50 if child.segment_type() == SegmentType::ParenExpression {
52 return vec![LintViolation::with_msg_key(
53 self.code(),
54 "DISTINCT used with parentheses is misleading. DISTINCT is not a function.",
55 child.span(),
56 "rules.ST08.msg",
57 vec![],
58 )];
59 }
60 found_distinct = false;
62 }
63
64 if let Segment::Token(t) = child {
65 if t.segment_type == SegmentType::Keyword
66 && t.token.text.eq_ignore_ascii_case("DISTINCT")
67 {
68 found_distinct = true;
69 }
70 }
71 }
72
73 vec![]
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use crate::test_utils::lint_sql;
81
82 #[test]
83 fn test_st08_accepts_distinct_without_parens() {
84 let violations = lint_sql("SELECT DISTINCT a, b FROM t;", RuleST08);
85 assert_eq!(violations.len(), 0);
86 }
87
88 #[test]
89 fn test_st08_accepts_no_distinct() {
90 let violations = lint_sql("SELECT a FROM t;", RuleST08);
91 assert_eq!(violations.len(), 0);
92 }
93}