use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
use crate::violation::{LintViolation, SourceEdit};
#[derive(Debug, Default)]
pub struct RuleLT12;
impl Rule for RuleLT12 {
fn code(&self) -> &'static str {
"LT12"
}
fn name(&self) -> &'static str {
"layout.end_of_file"
}
fn description(&self) -> &'static str {
"Files must end with a single trailing newline."
}
fn explanation(&self) -> &'static str {
"Files should end with exactly one newline character. Missing trailing newlines \
can cause issues with some tools, and multiple trailing newlines are untidy."
}
fn groups(&self) -> &[RuleGroup] {
&[RuleGroup::Layout]
}
fn is_fixable(&self) -> bool {
true
}
fn crawl_type(&self) -> CrawlType {
CrawlType::RootOnly
}
fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
let source = ctx.source;
if source.is_empty() {
return vec![];
}
let end = source.len() as u32;
if !source.ends_with('\n') {
return vec![LintViolation::with_fix_and_msg_key(
self.code(),
"File does not end with a trailing newline.",
rigsql_core::Span::new(end, end),
vec![SourceEdit::insert(end, "\n")],
"rules.LT12.msg.missing",
vec![],
)];
}
let trimmed = source.trim_end_matches(&['\n', '\r'][..]);
let trailing_newlines = source[trimmed.len()..]
.bytes()
.filter(|&b| b == b'\n')
.count();
if trailing_newlines > 1 {
let span_start = trimmed.len() as u32;
return vec![LintViolation::with_fix_and_msg_key(
self.code(),
format!(
"File ends with {} trailing newlines instead of 1.",
trailing_newlines
),
rigsql_core::Span::new(span_start, end),
vec![SourceEdit::replace(
rigsql_core::Span::new(span_start, end),
"\n",
)],
"rules.LT12.msg.multiple",
vec![("count".to_string(), trailing_newlines.to_string())],
)];
}
vec![]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::lint_sql;
#[test]
fn test_lt12_flags_no_trailing_newline() {
let violations = lint_sql("SELECT 1", RuleLT12);
assert_eq!(violations.len(), 1);
}
#[test]
fn test_lt12_accepts_single_trailing_newline() {
let violations = lint_sql("SELECT 1\n", RuleLT12);
assert_eq!(violations.len(), 0);
}
#[test]
fn test_lt12_flags_multiple_trailing_newlines() {
let violations = lint_sql("SELECT 1\n\n", RuleLT12);
assert_eq!(violations.len(), 1);
}
}