sqruff_lib/rules/structure/
st08.rs1use 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}