sqruff_lib/rules/structure/
st08.rs

1use ahash::AHashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3use sqruff_lib_core::lint_fix::LintFix;
4use sqruff_lib_core::parser::segments::base::{ErasedSegment, SegmentBuilder};
5use sqruff_lib_core::utils::functional::segments::Segments;
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;
12use crate::utils::reflow::sequence::{Filter, ReflowSequence, TargetSide};
13
14#[derive(Debug, Default, Clone)]
15pub struct RuleST08;
16
17impl RuleST08 {
18    pub fn remove_unneeded_brackets<'a>(
19        &self,
20        context: &RuleContext<'a>,
21        bracketed: Segments,
22    ) -> (ErasedSegment, ReflowSequence<'a>) {
23        let anchor = &bracketed.get(0, None).unwrap();
24        let seq = ReflowSequence::from_around_target(
25            anchor,
26            context.parent_stack[0].clone(),
27            TargetSide::Before,
28            context.config,
29        )
30        .replace(
31            anchor.clone(),
32            &Self::filter_meta(&anchor.segments()[1..anchor.segments().len() - 1], false),
33        );
34
35        (anchor.clone(), seq)
36    }
37
38    pub fn filter_meta(segments: &[ErasedSegment], keep_meta: bool) -> Vec<ErasedSegment> {
39        segments
40            .iter()
41            .filter(|&elem| elem.is_meta() == keep_meta)
42            .cloned()
43            .collect()
44    }
45}
46
47impl Rule for RuleST08 {
48    fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
49        Ok(RuleST08.erased())
50    }
51
52    fn name(&self) -> &'static str {
53        "structure.distinct"
54    }
55
56    fn description(&self) -> &'static str {
57        "Looking for DISTINCT before a bracket"
58    }
59
60    fn long_description(&self) -> &'static str {
61        r#"
62**Anti-pattern**
63
64In this example, parentheses are not needed and confuse DISTINCT with a function. The parentheses can also be misleading about which columns are affected by the DISTINCT (all the columns!).
65
66```sql
67SELECT DISTINCT(a), b FROM foo
68```
69
70**Best practice**
71
72Remove parentheses to be clear that the DISTINCT applies to both columns.
73
74```sql
75SELECT DISTINCT a, b FROM foo
76```
77"#
78    }
79
80    fn groups(&self) -> &'static [RuleGroups] {
81        &[RuleGroups::All, RuleGroups::Core, RuleGroups::Structure]
82    }
83
84    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
85        let mut seq: Option<ReflowSequence> = None;
86        let mut anchor: Option<ErasedSegment> = None;
87        let children = FunctionalContext::new(context).segment().children(None);
88
89        if context.segment.is_type(SyntaxKind::SelectClause) {
90            let modifier = children.select(
91                Some(|it: &ErasedSegment| it.is_type(SyntaxKind::SelectClauseModifier)),
92                None,
93                None,
94                None,
95            );
96            let selected_elements = children.select(
97                Some(|it: &ErasedSegment| it.is_type(SyntaxKind::SelectClauseElement)),
98                None,
99                None,
100                None,
101            );
102            let first_element = selected_elements.find_first::<fn(&_) -> _>(None);
103            let expression = first_element
104                .children(Some(|it| it.is_type(SyntaxKind::Expression)))
105                .find_first::<fn(&ErasedSegment) -> bool>(None);
106            let expression = if expression.is_empty() {
107                first_element
108            } else {
109                expression
110            };
111            let bracketed = expression
112                .children(Some(|it| it.get_type() == SyntaxKind::Bracketed))
113                .find_first::<fn(&_) -> _>(None);
114
115            if !modifier.is_empty() && !bracketed.is_empty() {
116                if expression[0].segments().len() == 1 {
117                    let ret = self.remove_unneeded_brackets(context, bracketed);
118                    anchor = ret.0.into();
119                    seq = ret.1.into();
120                } else {
121                    anchor = Some(modifier[0].clone());
122                    seq = Some(ReflowSequence::from_around_target(
123                        &modifier[0],
124                        context.parent_stack[0].clone(),
125                        TargetSide::After,
126                        context.config,
127                    ));
128                }
129            }
130        } else if context.segment.is_type(SyntaxKind::Function) {
131            let anchor = context.parent_stack.last().unwrap();
132
133            if !anchor.is_type(SyntaxKind::Expression) || anchor.segments().len() != 1 {
134                return Vec::new();
135            }
136
137            let selected_functions = children.select(
138                Some(|it: &ErasedSegment| it.is_type(SyntaxKind::FunctionName)),
139                None,
140                None,
141                None,
142            );
143            let function_name = selected_functions.first();
144            let bracketed =
145                children.find_first(Some(|it: &ErasedSegment| it.is_type(SyntaxKind::Bracketed)));
146
147            if function_name.is_none()
148                || !function_name
149                    .unwrap()
150                    .raw()
151                    .eq_ignore_ascii_case("DISTINCT")
152                || bracketed.is_empty()
153            {
154                return Vec::new();
155            }
156
157            let bracketed = &bracketed[0];
158            let mut edits = vec![
159                SegmentBuilder::token(
160                    context.tables.next_id(),
161                    "DISTINCT",
162                    SyntaxKind::FunctionNameIdentifier,
163                )
164                .finish(),
165                SegmentBuilder::whitespace(context.tables.next_id(), " "),
166            ];
167            edits.extend(Self::filter_meta(
168                &bracketed.segments()[1..bracketed.segments().len() - 1],
169                false,
170            ));
171
172            return vec![LintResult::new(
173                anchor.clone().into(),
174                vec![LintFix::replace(anchor.clone(), edits, None)],
175                None,
176                None,
177            )];
178        }
179
180        if let Some(seq) = seq {
181            if let Some(anchor) = anchor {
182                let fixes = seq.respace(context.tables, false, Filter::All).fixes();
183
184                if !fixes.is_empty() {
185                    return vec![LintResult::new(Some(anchor), fixes, None, None)];
186                }
187            }
188        }
189
190        Vec::new()
191    }
192
193    fn crawl_behaviour(&self) -> Crawler {
194        SegmentSeekerCrawler::new(
195            const { SyntaxSet::new(&[SyntaxKind::SelectClause, SyntaxKind::Function]) },
196        )
197        .into()
198    }
199}