use rigsql_core::SegmentType;
use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
use crate::violation::{LintViolation, SourceEdit};
#[derive(Debug)]
pub struct RuleLT15 {
pub max_blank_lines: usize,
}
impl Default for RuleLT15 {
fn default() -> Self {
Self { max_blank_lines: 1 }
}
}
impl Rule for RuleLT15 {
fn code(&self) -> &'static str {
"LT15"
}
fn name(&self) -> &'static str {
"layout.newlines"
}
fn description(&self) -> &'static str {
"Too many consecutive blank lines."
}
fn explanation(&self) -> &'static str {
"Files should not contain too many consecutive blank lines. Multiple \
blank lines waste vertical space and reduce readability."
}
fn groups(&self) -> &[RuleGroup] {
&[RuleGroup::Layout]
}
fn is_fixable(&self) -> bool {
true
}
fn configure(&mut self, settings: &std::collections::HashMap<String, String>) {
if let Some(val) = settings.get("max_blank_lines") {
if let Ok(n) = val.parse() {
self.max_blank_lines = n;
}
}
}
fn crawl_type(&self) -> CrawlType {
CrawlType::RootOnly
}
fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
let mut violations = Vec::new();
let mut consecutive_newlines = 0usize;
let mut newline_spans: Vec<rigsql_core::Span> = Vec::new();
let max = self.max_blank_lines;
let code = self.code();
let flush = |consecutive: &mut usize,
spans: &mut Vec<rigsql_core::Span>,
violations: &mut Vec<LintViolation>| {
if *consecutive > max + 1 {
let keep = max + 1; let delete_start = spans[keep - 1].end;
let delete_end = spans.last().unwrap().end;
let delete_span = rigsql_core::Span::new(delete_start, delete_end);
violations.push(LintViolation::with_fix_and_msg_key(
code,
format!("Too many blank lines ({}, max {}).", *consecutive - 1, max),
spans[0],
vec![SourceEdit::delete(delete_span)],
"rules.LT15.msg",
vec![
("count".to_string(), (*consecutive - 1).to_string()),
("max".to_string(), max.to_string()),
],
));
}
*consecutive = 0;
spans.clear();
};
ctx.root.walk(&mut |seg| {
if seg.segment_type() == SegmentType::Newline {
consecutive_newlines += 1;
newline_spans.push(seg.span());
} else if seg.segment_type() == SegmentType::Whitespace {
} else if seg.children().is_empty() {
flush(
&mut consecutive_newlines,
&mut newline_spans,
&mut violations,
);
}
});
flush(
&mut consecutive_newlines,
&mut newline_spans,
&mut violations,
);
violations
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::lint_sql;
#[test]
fn test_lt15_accepts_single_blank_line() {
let violations = lint_sql("SELECT 1;\n\nSELECT 2;\n", RuleLT15::default());
assert_eq!(violations.len(), 0);
}
#[test]
fn test_lt15_accepts_no_blank_lines() {
let violations = lint_sql("SELECT 1;\n", RuleLT15::default());
assert_eq!(violations.len(), 0);
}
}