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::{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}