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::{ErasedSegment, SegmentBuilder};
5use sqruff_lib_core::utils::functional::segments::Segments;
6
7use crate::core::config::Value;
8use crate::core::rules::context::RuleContext;
9use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
10use crate::core::rules::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
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, 'b>(
19        &self,
20        context: &'b RuleContext<'a>,
21        bracketed: Segments,
22    ) -> (ErasedSegment, ReflowSequence<'a, 'b>) {
23        let anchor = &bracketed.get(0, None).unwrap();
24        let seq = ReflowSequence::from_around_target(
25            anchor,
26            &context.parent_stack[0],
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_all();
88
89        if context.segment.is_type(SyntaxKind::SelectClause) {
90            let modifier =
91                children.filter(|it: &ErasedSegment| it.is_type(SyntaxKind::SelectClauseModifier));
92            let selected_elements =
93                children.filter(|it: &ErasedSegment| it.is_type(SyntaxKind::SelectClauseElement));
94            let first_element = selected_elements.head();
95            let expression = first_element
96                .children_where(|it| it.is_type(SyntaxKind::Expression))
97                .head();
98            let expression = if expression.is_empty() {
99                first_element
100            } else {
101                expression
102            };
103            let bracketed = expression
104                .children_where(|it| it.get_type() == SyntaxKind::Bracketed)
105                .head();
106
107            if !modifier.is_empty() && !bracketed.is_empty() {
108                if expression[0].segments().len() == 1 {
109                    let ret = self.remove_unneeded_brackets(context, bracketed);
110                    anchor = ret.0.into();
111                    seq = ret.1.into();
112                } else {
113                    anchor = Some(modifier[0].clone());
114                    seq = Some(ReflowSequence::from_around_target(
115                        &modifier[0],
116                        &context.parent_stack[0],
117                        TargetSide::After,
118                        context.config,
119                    ));
120                }
121            }
122        } else if context.segment.is_type(SyntaxKind::Function) {
123            let anchor = context.parent_stack.last().unwrap();
124
125            if !anchor.is_type(SyntaxKind::Expression) || anchor.segments().len() != 1 {
126                return Vec::new();
127            }
128
129            let selected_functions =
130                children.filter(|it: &ErasedSegment| it.is_type(SyntaxKind::FunctionName));
131            let function_name = selected_functions.first();
132            let bracketed = children
133                .find_first_where(|it: &ErasedSegment| it.is_type(SyntaxKind::FunctionContents));
134
135            if function_name.is_none()
136                || !function_name
137                    .unwrap()
138                    .raw()
139                    .eq_ignore_ascii_case("DISTINCT")
140                || bracketed.is_empty()
141            {
142                return Vec::new();
143            }
144
145            let bracketed = &bracketed.children_all()[0];
146            let mut edits = vec![
147                SegmentBuilder::token(
148                    context.tables.next_id(),
149                    "DISTINCT",
150                    SyntaxKind::FunctionNameIdentifier,
151                )
152                .finish(),
153                SegmentBuilder::whitespace(context.tables.next_id(), " "),
154            ];
155            edits.extend(Self::filter_meta(
156                &bracketed.segments()[1..bracketed.segments().len() - 1],
157                false,
158            ));
159
160            return vec![LintResult::new(
161                anchor.clone().into(),
162                vec![LintFix::replace(anchor.clone(), edits, None)],
163                None,
164                None,
165            )];
166        }
167
168        if let Some(seq) = seq
169            && let Some(anchor) = anchor
170        {
171            let fixes = seq.respace(context.tables, false, Filter::All).fixes();
172
173            if !fixes.is_empty() {
174                return vec![LintResult::new(Some(anchor), fixes, None, None)];
175            }
176        }
177
178        Vec::new()
179    }
180
181    fn crawl_behaviour(&self) -> Crawler {
182        SegmentSeekerCrawler::new(
183            const { SyntaxSet::new(&[SyntaxKind::SelectClause, SyntaxKind::Function]) },
184        )
185        .into()
186    }
187}