rigsql_rules/layout/
lt13.rs1use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
2use crate::violation::{LintViolation, SourceEdit};
3
4#[derive(Debug, Default)]
6pub struct RuleLT13;
7
8impl Rule for RuleLT13 {
9 fn code(&self) -> &'static str {
10 "LT13"
11 }
12 fn name(&self) -> &'static str {
13 "layout.start_of_file"
14 }
15 fn description(&self) -> &'static str {
16 "Files must not begin with newlines or whitespace."
17 }
18 fn explanation(&self) -> &'static str {
19 "Files should start with actual content, not blank lines or whitespace. \
20 Leading whitespace is likely unintentional and should be removed."
21 }
22 fn groups(&self) -> &[RuleGroup] {
23 &[RuleGroup::Layout]
24 }
25 fn is_fixable(&self) -> bool {
26 true
27 }
28
29 fn crawl_type(&self) -> CrawlType {
30 CrawlType::RootOnly
31 }
32
33 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
34 let source = ctx.source;
35 if source.is_empty() {
36 return vec![];
37 }
38
39 let first_non_ws = source.find(|c: char| !c.is_whitespace());
40
41 match first_non_ws {
42 Some(0) => vec![], Some(pos) => {
44 vec![LintViolation::with_fix_and_msg_key(
45 self.code(),
46 "File starts with whitespace or blank lines.",
47 rigsql_core::Span::new(0, pos as u32),
48 vec![SourceEdit::delete(rigsql_core::Span::new(0, pos as u32))],
49 "rules.LT13.msg",
50 vec![],
51 )]
52 }
53 None => vec![], }
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use crate::test_utils::lint_sql;
62
63 #[test]
64 fn test_lt13_flags_leading_whitespace() {
65 let violations = lint_sql(" SELECT 1", RuleLT13);
66 assert_eq!(violations.len(), 1);
67 }
68
69 #[test]
70 fn test_lt13_accepts_no_leading_whitespace() {
71 let violations = lint_sql("SELECT 1", RuleLT13);
72 assert_eq!(violations.len(), 0);
73 }
74}