sqruff_lib/rules/layout/
lt15.rs

1use ahash::AHashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3use sqruff_lib_core::lint_fix::LintFix;
4
5use crate::core::config::Value;
6use crate::core::rules::context::RuleContext;
7use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
8use crate::core::rules::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
9
10#[derive(Debug, Clone)]
11pub struct RuleLT15 {
12    maximum_empty_lines_between_statements: usize,
13    maximum_empty_lines_inside_statements: usize,
14}
15
16impl Default for RuleLT15 {
17    fn default() -> Self {
18        Self {
19            maximum_empty_lines_between_statements: 2,
20            maximum_empty_lines_inside_statements: 1,
21        }
22    }
23}
24
25impl Rule for RuleLT15 {
26    fn load_from_config(&self, config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
27        Ok(RuleLT15 {
28            maximum_empty_lines_between_statements: config
29                .get("maximum_empty_lines_between_statements")
30                .and_then(Value::as_int)
31                .map(|v| v as usize)
32                .unwrap_or(self.maximum_empty_lines_between_statements),
33            maximum_empty_lines_inside_statements: config
34                .get("maximum_empty_lines_inside_statements")
35                .and_then(Value::as_int)
36                .map(|v| v as usize)
37                .unwrap_or(self.maximum_empty_lines_inside_statements),
38        }
39        .erased())
40    }
41
42    fn name(&self) -> &'static str {
43        "layout.newlines"
44    }
45
46    fn description(&self) -> &'static str {
47        "Too many consecutive blank lines."
48    }
49
50    fn long_description(&self) -> &'static str {
51        r#"**Anti-pattern**
52
53In this example, the maximum number of empty lines inside a statement is set to 0.
54
55```sql
56SELECT 'a' AS col
57FROM tab
58
59
60WHERE x = 4
61ORDER BY y
62
63
64LIMIT 5
65;
66```
67
68**Best practice**
69
70```sql
71SELECT 'a' AS col
72FROM tab
73WHERE x = 4
74ORDER BY y
75LIMIT 5
76;
77```"#
78    }
79
80    fn groups(&self) -> &'static [RuleGroups] {
81        &[RuleGroups::All, RuleGroups::Layout]
82    }
83
84    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
85        if !context.segment.is_type(SyntaxKind::Newline) {
86            return Vec::new();
87        }
88
89        let inside_statement = context
90            .parent_stack
91            .iter()
92            .any(|seg| seg.is_type(SyntaxKind::Statement));
93
94        let maximum_empty_lines = if inside_statement {
95            self.maximum_empty_lines_inside_statements
96        } else {
97            self.maximum_empty_lines_between_statements
98        };
99
100        let Some(parent) = context.parent_stack.last() else {
101            return Vec::new();
102        };
103
104        let siblings = parent.segments();
105        let Some(current_idx) = siblings.iter().position(|s| s == &context.segment) else {
106            return Vec::new();
107        };
108
109        // Count consecutive newlines including this one
110        let mut consecutive_newlines = 1;
111
112        // Count backwards from current position
113        for i in (0..current_idx).rev() {
114            if siblings[i].is_type(SyntaxKind::Newline) {
115                consecutive_newlines += 1;
116            } else {
117                break;
118            }
119        }
120
121        // Too many consecutive newlines means too many empty lines
122        if consecutive_newlines > maximum_empty_lines + 1 {
123            return vec![LintResult::new(
124                context.segment.clone().into(),
125                vec![LintFix::delete(context.segment.clone())],
126                None,
127                None,
128            )];
129        }
130
131        Vec::new()
132    }
133
134    fn is_fix_compatible(&self) -> bool {
135        true
136    }
137
138    fn crawl_behaviour(&self) -> Crawler {
139        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::Newline]) }).into()
140    }
141}