rigsql_rules/layout/
lt07.rs1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::utils::has_trailing_newline;
5use crate::violation::LintViolation;
6
7#[derive(Debug, Default)]
11pub struct RuleLT07;
12
13impl Rule for RuleLT07 {
14 fn code(&self) -> &'static str {
15 "LT07"
16 }
17 fn name(&self) -> &'static str {
18 "layout.cte_bracket"
19 }
20 fn description(&self) -> &'static str {
21 "WITH clause closing bracket should be on a new line."
22 }
23 fn explanation(&self) -> &'static str {
24 "The closing parenthesis of a CTE definition should be placed on its \
25 own line, not on the same line as the last expression in the CTE body."
26 }
27 fn groups(&self) -> &[RuleGroup] {
28 &[RuleGroup::Layout]
29 }
30 fn is_fixable(&self) -> bool {
31 false
32 }
33
34 fn crawl_type(&self) -> CrawlType {
35 CrawlType::Segment(vec![SegmentType::CteDefinition])
36 }
37
38 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
39 let children = ctx.segment.children();
40
41 let (search_children, rparen_idx) = if let Some(idx) = children
43 .iter()
44 .rposition(|c| c.segment_type() == SegmentType::RParen)
45 {
46 (children, idx)
47 } else {
48 let subquery = children
50 .iter()
51 .find(|c| c.segment_type() == SegmentType::Subquery);
52 let Some(sq) = subquery else {
53 return vec![];
54 };
55 let sq_children = sq.children();
56 let Some(idx) = sq_children
57 .iter()
58 .rposition(|c| c.segment_type() == SegmentType::RParen)
59 else {
60 return vec![];
61 };
62 (sq_children, idx)
63 };
64
65 let mut found_newline = false;
68 for child in search_children[..rparen_idx].iter().rev() {
69 let st = child.segment_type();
70 if st == SegmentType::Newline {
71 found_newline = true;
72 break;
73 }
74 if st == SegmentType::Whitespace {
75 continue;
76 }
77 found_newline = has_trailing_newline(child);
79 break;
80 }
81
82 if !found_newline {
83 return vec![LintViolation::new(
84 self.code(),
85 "Closing bracket of CTE should be on a new line.",
86 search_children[rparen_idx].span(),
87 )];
88 }
89
90 vec![]
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::test_utils::lint_sql;
98
99 #[test]
100 fn test_lt07_accepts_newline_before_bracket() {
101 let violations = lint_sql("WITH cte AS (\n SELECT 1\n) SELECT * FROM cte", RuleLT07);
102 assert_eq!(violations.len(), 0);
103 }
104
105 #[test]
106 fn test_lt07_flags_inline_bracket() {
107 let violations = lint_sql("WITH cte AS (SELECT 1) SELECT * FROM cte", RuleLT07);
108 assert_eq!(violations.len(), 1);
109 }
110}