sqruff_lib/rules/layout/
lt09.rs

1use ahash::AHashMap;
2use itertools::{Itertools, enumerate};
3use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
4use sqruff_lib_core::lint_fix::LintFix;
5use sqruff_lib_core::parser::segments::{ErasedSegment, SegmentBuilder, Tables};
6use sqruff_lib_core::utils::functional::segments::Segments;
7
8use crate::core::config::Value;
9use crate::core::rules::context::RuleContext;
10use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
11use crate::core::rules::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
12use crate::utils::functional::context::FunctionalContext;
13
14struct SelectTargetsInfo {
15    select_idx: Option<usize>,
16    first_new_line_idx: Option<usize>,
17    first_select_target_idx: Option<usize>,
18
19    #[allow(dead_code)]
20    first_whitespace_idx: Option<usize>,
21    comment_after_select_idx: Option<usize>,
22    select_targets: Segments,
23    from_segment: Option<ErasedSegment>,
24    pre_from_whitespace: Segments,
25}
26
27#[derive(Debug, Clone)]
28pub struct RuleLT09 {
29    wildcard_policy: String,
30}
31
32impl Rule for RuleLT09 {
33    fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
34        Ok(RuleLT09 {
35            wildcard_policy: _config["wildcard_policy"].as_string().unwrap().to_owned(),
36        }
37        .erased())
38    }
39    fn name(&self) -> &'static str {
40        "layout.select_targets"
41    }
42
43    fn description(&self) -> &'static str {
44        "Select targets should be on a new line unless there is only one select target."
45    }
46
47    fn long_description(&self) -> &'static str {
48        r#"
49**Anti-pattern**
50
51Multiple select targets on the same line.
52
53```sql
54select a, b
55from foo;
56
57-- Single select target on its own line.
58
59SELECT
60    a
61FROM foo;
62```
63
64**Best practice**
65
66Multiple select targets each on their own line.
67
68```sql
69select
70    a,
71    b
72from foo;
73
74-- Single select target on the same line as the ``SELECT``
75-- keyword.
76
77SELECT a
78FROM foo;
79
80-- When select targets span multiple lines, however they
81-- can still be on a new line.
82
83SELECT
84    SUM(
85        1 + SUM(
86            2 + 3
87        )
88    ) AS col
89FROM test_table;
90```
91"#
92    }
93
94    fn groups(&self) -> &'static [RuleGroups] {
95        &[RuleGroups::All, RuleGroups::Layout]
96    }
97
98    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
99        let select_targets_info = Self::get_indexes(context);
100        let select_clause = FunctionalContext::new(context).segment();
101
102        let wildcards = select_clause
103            .children_where(|sp| sp.is_type(SyntaxKind::SelectClauseElement))
104            .children_where(|sp| sp.is_type(SyntaxKind::WildcardExpression));
105
106        let has_wildcard = !wildcards.is_empty();
107
108        if select_targets_info.select_targets.len() == 1
109            && (!has_wildcard || self.wildcard_policy == "single")
110        {
111            return self.eval_single_select_target_element(select_targets_info, context);
112        } else if !select_targets_info.select_targets.is_empty() {
113            return self.eval_multiple_select_target_elements(
114                context.tables,
115                select_targets_info,
116                context.segment.clone(),
117            );
118        }
119
120        Vec::new()
121    }
122
123    fn is_fix_compatible(&self) -> bool {
124        true
125    }
126
127    fn crawl_behaviour(&self) -> Crawler {
128        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::SelectClause]) }).into()
129    }
130}
131
132impl RuleLT09 {
133    fn get_indexes(context: &RuleContext) -> SelectTargetsInfo {
134        let children = FunctionalContext::new(context).segment().children_all();
135
136        let select_targets = children
137            .filter(|segment: &ErasedSegment| segment.is_type(SyntaxKind::SelectClauseElement));
138
139        let first_select_target_idx = select_targets.first().and_then(|it| children.find(it));
140
141        let selects = children.filter(|segment: &ErasedSegment| segment.is_keyword("select"));
142
143        let select_idx =
144            (!selects.is_empty()).then(|| children.find(selects.first().unwrap()).unwrap());
145
146        let newlines = children.filter(|it: &ErasedSegment| it.is_type(SyntaxKind::Newline));
147
148        let first_new_line_idx =
149            (!newlines.is_empty()).then(|| children.find(newlines.first().unwrap()).unwrap());
150        let mut comment_after_select_idx = None;
151
152        if !newlines.is_empty() {
153            let select_head = selects.first().unwrap();
154            if let Some(first_comment) = children
155                .iter_after_while(select_head, |seg| {
156                    seg.is_type(SyntaxKind::Comment)
157                        | seg.is_type(SyntaxKind::Whitespace)
158                        | seg.is_meta()
159                })
160                .find(|seg| seg.is_type(SyntaxKind::Comment))
161            {
162                comment_after_select_idx = children.find(first_comment);
163            }
164        }
165
166        let mut first_whitespace_idx = None;
167        if let Some(first_new_line_idx) = first_new_line_idx {
168            let segments_after_first_line = children
169                .after(&children[first_new_line_idx])
170                .filter(|seg: &ErasedSegment| seg.is_type(SyntaxKind::Whitespace));
171
172            if !segments_after_first_line.is_empty() {
173                first_whitespace_idx =
174                    children.find(&segments_after_first_line.get(0, None).unwrap());
175            }
176        }
177
178        let siblings_post = FunctionalContext::new(context).siblings_post();
179        let from_segment = siblings_post
180            .find_first_where(|seg: &ErasedSegment| seg.is_type(SyntaxKind::FromClause))
181            .head()
182            .get(0, None);
183        let pre_from_whitespace = {
184            let range = if let Some(ref stop) = from_segment {
185                siblings_post.before(stop)
186            } else {
187                siblings_post.clone()
188            };
189            range.filter(|seg: &ErasedSegment| seg.is_type(SyntaxKind::Whitespace))
190        };
191
192        SelectTargetsInfo {
193            select_idx,
194            first_new_line_idx,
195            first_select_target_idx,
196            first_whitespace_idx,
197            comment_after_select_idx,
198            select_targets,
199            from_segment,
200            pre_from_whitespace,
201        }
202    }
203
204    fn eval_multiple_select_target_elements(
205        &self,
206        tables: &Tables,
207        select_targets_info: SelectTargetsInfo,
208        segment: ErasedSegment,
209    ) -> Vec<LintResult> {
210        let mut fixes = Vec::new();
211
212        for (i, select_target) in enumerate(select_targets_info.select_targets.iter()) {
213            let base_segment = if i == 0 {
214                segment.clone()
215            } else {
216                select_targets_info.select_targets[i - 1].clone()
217            };
218
219            if let Some((_, _)) = base_segment
220                .get_position_marker()
221                .zip(select_target.get_position_marker())
222                .filter(|(a, b)| a.working_line_no == b.working_line_no)
223            {
224                let mut start_seg = select_targets_info.select_idx.unwrap();
225                let modifier =
226                    segment.child(const { &SyntaxSet::new(&[SyntaxKind::SelectClauseModifier]) });
227
228                if let Some(modifier) = modifier {
229                    start_seg = segment
230                        .segments()
231                        .iter()
232                        .position(|it| it == &modifier)
233                        .unwrap();
234                }
235
236                let segments = segment.segments();
237
238                let start = if i == 0 {
239                    &segments[start_seg]
240                } else {
241                    &select_targets_info.select_targets[i - 1]
242                };
243
244                let start_position = segments.iter().position(|it| it == start).unwrap();
245                let ws_to_delete = segments[start_position + 1..]
246                    .iter()
247                    .take_while(|it| {
248                        it.is_type(SyntaxKind::Whitespace)
249                            | it.is_type(SyntaxKind::Comma)
250                            | it.is_meta()
251                    })
252                    .filter(|it| it.is_type(SyntaxKind::Whitespace));
253
254                fixes.extend(ws_to_delete.cloned().map(LintFix::delete));
255                fixes.push(LintFix::create_before(
256                    select_target.clone(),
257                    vec![SegmentBuilder::newline(tables.next_id(), "\n")],
258                ));
259            }
260
261            if let Some(from_segment) = &select_targets_info.from_segment
262                && i + 1 == select_targets_info.select_targets.len()
263                && select_target.get_position_marker().unwrap().working_line_no
264                    == from_segment.get_position_marker().unwrap().working_line_no
265            {
266                fixes.extend(
267                    select_targets_info
268                        .pre_from_whitespace
269                        .clone()
270                        .into_iter()
271                        .map(LintFix::delete),
272                );
273
274                fixes.push(LintFix::create_before(
275                    from_segment.clone(),
276                    vec![SegmentBuilder::newline(tables.next_id(), "\n")],
277                ));
278            }
279        }
280
281        if !fixes.is_empty() {
282            return vec![LintResult::new(segment.into(), fixes, None, None)];
283        }
284
285        Vec::new()
286    }
287
288    fn eval_single_select_target_element(
289        &self,
290        select_targets_info: SelectTargetsInfo,
291        context: &RuleContext,
292    ) -> Vec<LintResult> {
293        let select_clause = FunctionalContext::new(context).segment();
294        let parent_stack = &context.parent_stack;
295
296        if !(select_targets_info.select_idx < select_targets_info.first_new_line_idx
297            && select_targets_info.first_new_line_idx < select_targets_info.first_select_target_idx)
298        {
299            return Vec::new();
300        }
301
302        let select_children = select_clause.children_all();
303        let mut modifier = select_children
304            .find_first_where(|seg: &ErasedSegment| seg.is_type(SyntaxKind::SelectClauseModifier));
305
306        if select_children[select_targets_info.first_select_target_idx.unwrap()]
307            .descendant_type_set()
308            .contains(SyntaxKind::Newline)
309        {
310            return Vec::new();
311        }
312
313        let mut insert_buff = vec![
314            SegmentBuilder::whitespace(context.tables.next_id(), " "),
315            select_children[select_targets_info.first_select_target_idx.unwrap()].clone(),
316        ];
317
318        if !modifier.is_empty()
319            && select_children.index(&modifier.get(0, None).unwrap())
320                < select_targets_info.first_new_line_idx
321        {
322            modifier = Segments::from_vec(Vec::new(), None);
323        }
324
325        let mut fixes = vec![LintFix::delete(
326            select_children[select_targets_info.first_select_target_idx.unwrap()].clone(),
327        )];
328
329        let start_idx = if !modifier.is_empty() {
330            let buff = std::mem::take(&mut insert_buff);
331
332            insert_buff = vec![
333                SegmentBuilder::whitespace(context.tables.next_id(), " "),
334                modifier[0].clone(),
335            ];
336
337            insert_buff.extend(buff);
338
339            let modifier_idx = select_children
340                .index(&modifier.get(0, None).unwrap())
341                .unwrap();
342
343            if select_children.len() > modifier_idx + 1
344                && select_children[modifier_idx + 2].is_whitespace()
345            {
346                fixes.push(LintFix::delete(select_children[modifier_idx + 2].clone()));
347            }
348
349            fixes.push(LintFix::delete(modifier[0].clone()));
350
351            modifier_idx
352        } else {
353            select_targets_info.first_select_target_idx.unwrap()
354        };
355
356        if !parent_stack.is_empty()
357            && parent_stack
358                .last()
359                .unwrap()
360                .is_type(SyntaxKind::SelectStatement)
361        {
362            let select_stmt = parent_stack.last().unwrap();
363            let select_clause_idx = select_stmt
364                .segments()
365                .iter()
366                .position(|it| it.clone() == select_clause.get(0, None).unwrap())
367                .unwrap();
368            let after_select_clause_idx = select_clause_idx + 1;
369
370            let fixes_for_move_after_select_clause =
371                |fixes: &mut Vec<LintFix>,
372                 stop_seg: ErasedSegment,
373                 delete_segments: Option<Segments>,
374                 add_newline: bool| {
375                    let start_seg = if !modifier.is_empty() {
376                        modifier[0].clone()
377                    } else {
378                        select_children[select_targets_info.first_new_line_idx.unwrap()].clone()
379                    };
380
381                    let move_after_select_clause =
382                        select_children.between_exclusive(&start_seg, &stop_seg);
383                    let mut local_fixes = Vec::new();
384                    let mut all_deletes = fixes
385                        .iter()
386                        .filter(|fix| matches!(fix, LintFix::Delete { .. }))
387                        .map(|fix| fix.anchor().clone())
388                        .collect_vec();
389                    for seg in delete_segments.unwrap_or_default() {
390                        fixes.push(LintFix::delete(seg.clone()));
391                        all_deletes.push(seg);
392                    }
393
394                    let new_fixes = move_after_select_clause
395                        .iter()
396                        .filter(|it| !all_deletes.contains(it))
397                        .cloned()
398                        .map(LintFix::delete);
399                    local_fixes.extend(new_fixes);
400
401                    if !move_after_select_clause.is_empty() || add_newline {
402                        local_fixes.push(LintFix::create_after(
403                            select_clause[0].clone(),
404                            if add_newline {
405                                vec![SegmentBuilder::newline(context.tables.next_id(), "\n")]
406                            } else {
407                                vec![]
408                            }
409                            .into_iter()
410                            .chain(move_after_select_clause)
411                            .collect_vec(),
412                            None,
413                        ));
414                    }
415
416                    local_fixes
417                };
418
419            if select_stmt.segments().len() > after_select_clause_idx {
420                if select_stmt.segments()[after_select_clause_idx].is_type(SyntaxKind::Newline) {
421                    let to_delete = select_children
422                        .reversed()
423                        .after(&select_children[start_idx])
424                        .take_while(|seg| seg.is_type(SyntaxKind::Whitespace));
425
426                    if !to_delete.is_empty() {
427                        let delete_last_newline = select_children[start_idx - to_delete.len() - 1]
428                            .is_type(SyntaxKind::Newline);
429
430                        if delete_last_newline {
431                            fixes.push(LintFix::delete(
432                                select_stmt.segments()[after_select_clause_idx].clone(),
433                            ));
434                        }
435
436                        let new_fixes = fixes_for_move_after_select_clause(
437                            &mut fixes,
438                            to_delete.last().unwrap().clone(),
439                            to_delete.into(),
440                            true,
441                        );
442                        fixes.extend(new_fixes);
443                    }
444                } else if select_stmt.segments()[after_select_clause_idx]
445                    .is_type(SyntaxKind::Whitespace)
446                {
447                    fixes.push(LintFix::delete(
448                        select_stmt.segments()[after_select_clause_idx].clone(),
449                    ));
450
451                    let new_fixes = fixes_for_move_after_select_clause(
452                        &mut fixes,
453                        select_children[select_targets_info.first_select_target_idx.unwrap()]
454                            .clone(),
455                        None,
456                        true,
457                    );
458
459                    fixes.extend(new_fixes);
460                } else if select_stmt.segments()[after_select_clause_idx]
461                    .is_type(SyntaxKind::Dedent)
462                {
463                    let start_seg = if select_clause_idx == 0 {
464                        select_children.last().unwrap()
465                    } else {
466                        &select_children[select_clause_idx - 1]
467                    };
468
469                    let to_delete = select_children
470                        .reversed()
471                        .after(start_seg)
472                        .take_while(|it| it.is_type(SyntaxKind::Whitespace));
473
474                    if !to_delete.is_empty() {
475                        let add_newline =
476                            to_delete.iter().any(|it| it.is_type(SyntaxKind::Newline));
477                        let local_fixes = fixes_for_move_after_select_clause(
478                            &mut fixes,
479                            to_delete.last().unwrap().clone(),
480                            to_delete.into(),
481                            add_newline,
482                        );
483                        fixes.extend(local_fixes);
484                    }
485                } else {
486                    let local_fixes = fixes_for_move_after_select_clause(
487                        &mut fixes,
488                        select_children[select_targets_info.first_select_target_idx.unwrap()]
489                            .clone(),
490                        None,
491                        true,
492                    );
493                    fixes.extend(local_fixes);
494                }
495            }
496        }
497
498        if select_targets_info.comment_after_select_idx.is_none() {
499            fixes.push(LintFix::replace(
500                select_children[select_targets_info.first_new_line_idx.unwrap()].clone(),
501                insert_buff,
502                None,
503            ));
504        }
505
506        vec![LintResult::new(
507            select_clause.get(0, None).unwrap().clone().into(),
508            fixes,
509            None,
510            None,
511        )]
512    }
513}
514
515impl Default for RuleLT09 {
516    fn default() -> Self {
517        Self {
518            wildcard_policy: "single".into(),
519        }
520    }
521}