sqruff_lib/rules/layout/
lt15.rs1use 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 let mut consecutive_newlines = 1;
111
112 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 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}