sqruff_lib/rules/layout/
lt07.rs

1use ahash::{AHashMap, AHashSet};
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3use sqruff_lib_core::lint_fix::LintFix;
4use sqruff_lib_core::parser::segments::{ErasedSegment, SegmentBuilder};
5
6use crate::core::config::Value;
7use crate::core::rules::context::RuleContext;
8use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
9use crate::core::rules::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
10use crate::utils::functional::context::FunctionalContext;
11
12#[derive(Debug, Default, Clone)]
13pub struct RuleLT07;
14
15impl Rule for RuleLT07 {
16    fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
17        Ok(RuleLT07.erased())
18    }
19    fn name(&self) -> &'static str {
20        "layout.cte_bracket"
21    }
22
23    fn description(&self) -> &'static str {
24        "'WITH' clause closing bracket should be on a new line."
25    }
26
27    fn long_description(&self) -> &'static str {
28        r#"
29**Anti-pattern**
30
31In this example, the closing bracket is on the same line as CTE.
32
33```sql
34 WITH zoo AS (
35     SELECT a FROM foo)
36
37 SELECT * FROM zoo
38```
39
40**Best practice**
41
42Move the closing bracket on a new line.
43
44```sql
45WITH zoo AS (
46    SELECT a FROM foo
47)
48
49SELECT * FROM zoo
50```
51"#
52    }
53
54    fn groups(&self) -> &'static [RuleGroups] {
55        &[RuleGroups::All, RuleGroups::Core, RuleGroups::Layout]
56    }
57    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
58        let segments = FunctionalContext::new(context)
59            .segment()
60            .children_where(|seg| seg.is_type(SyntaxKind::CommonTableExpression));
61
62        let mut cte_end_brackets = AHashSet::new();
63        for cte in segments.iterate_segments() {
64            let cte_start_bracket = cte
65                .children_all()
66                .find_last_where(|seg| seg.is_type(SyntaxKind::Bracketed))
67                .children_all()
68                .find_first_where(|seg: &ErasedSegment| seg.is_type(SyntaxKind::StartBracket));
69
70            let cte_end_bracket = cte
71                .children_all()
72                .find_last_where(|seg| seg.is_type(SyntaxKind::Bracketed))
73                .children_all()
74                .find_first_where(|seg: &ErasedSegment| seg.is_type(SyntaxKind::EndBracket));
75
76            if !cte_start_bracket.is_empty() && !cte_end_bracket.is_empty() {
77                if cte_start_bracket[0]
78                    .get_position_marker()
79                    .unwrap()
80                    .line_no()
81                    == cte_end_bracket[0].get_position_marker().unwrap().line_no()
82                {
83                    continue;
84                }
85                cte_end_brackets.insert(cte_end_bracket[0].clone());
86            }
87        }
88
89        for seg in cte_end_brackets {
90            let mut contains_non_whitespace = false;
91            let idx = context
92                .segment
93                .get_raw_segments()
94                .iter()
95                .position(|it| it == &seg)
96                .unwrap();
97            if idx > 0 {
98                for elem in context.segment.get_raw_segments()[..idx].iter().rev() {
99                    if elem.is_type(SyntaxKind::Newline) {
100                        break;
101                    } else if !(matches!(
102                        elem.get_type(),
103                        SyntaxKind::Indent | SyntaxKind::Implicit
104                    ) || elem.is_type(SyntaxKind::Dedent)
105                        || elem.is_type(SyntaxKind::Whitespace))
106                    {
107                        contains_non_whitespace = true;
108                        break;
109                    }
110                }
111            }
112
113            if contains_non_whitespace {
114                return vec![LintResult::new(
115                    seg.clone().into(),
116                    vec![LintFix::create_before(
117                        seg,
118                        vec![SegmentBuilder::newline(context.tables.next_id(), "\n")],
119                    )],
120                    None,
121                    None,
122                )];
123            }
124        }
125
126        Vec::new()
127    }
128
129    fn is_fix_compatible(&self) -> bool {
130        false
131    }
132
133    fn crawl_behaviour(&self) -> Crawler {
134        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::WithCompoundStatement]) })
135            .into()
136    }
137}