sqruff_lib/rules/layout/
lt07.rs1use 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}