1pub mod config;
2pub use config::Config;
3
4use sqlparser::ast::Statement;
5use sqlparser::dialect::GenericDialect;
6use sqlparser::parser::Parser;
7use std::path::PathBuf;
8
9pub struct Diagnostic {
11 pub rule: &'static str,
12 pub message: String,
13 pub line: usize,
15 pub col: usize,
17}
18
19pub struct FileContext {
21 pub path: PathBuf,
22 pub source: String,
23 pub statements: Vec<Statement>,
25 pub parse_errors: Vec<String>,
27}
28
29impl FileContext {
30 pub fn from_source(source: &str, path: &str) -> Self {
31 let dialect = GenericDialect {};
32 let (statements, parse_errors) = match Parser::parse_sql(&dialect, source) {
33 Ok(stmts) => (stmts, Vec::new()),
34 Err(e) => (Vec::new(), vec![e.to_string()]),
35 };
36 FileContext {
37 path: PathBuf::from(path),
38 source: source.to_string(),
39 statements,
40 parse_errors,
41 }
42 }
43
44 pub fn lines(&self) -> impl Iterator<Item = (usize, &str)> {
46 self.source.lines().enumerate().map(|(i, line)| (i + 1, line))
47 }
48}
49
50pub trait Rule: Send + Sync {
52 fn name(&self) -> &'static str;
53 fn check(&self, ctx: &FileContext) -> Vec<Diagnostic>;
54 fn fix(&self, _ctx: &FileContext) -> Option<String> {
56 None
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn valid_sql_populates_statements() {
66 let ctx = FileContext::from_source("SELECT 1; SELECT 2;", "t.sql");
67 assert_eq!(ctx.statements.len(), 2);
68 assert!(ctx.parse_errors.is_empty());
69 }
70
71 #[test]
72 fn invalid_sql_stores_parse_error() {
73 let ctx = FileContext::from_source("SELECT FROM FROM", "t.sql");
74 assert!(ctx.statements.is_empty());
75 assert!(!ctx.parse_errors.is_empty());
76 }
77
78 #[test]
79 fn empty_sql_produces_no_statements_and_no_errors() {
80 let ctx = FileContext::from_source("", "t.sql");
81 assert!(ctx.statements.is_empty());
82 assert!(ctx.parse_errors.is_empty());
83 }
84
85 #[test]
86 fn lines_still_works_after_ast_addition() {
87 let ctx = FileContext::from_source("SELECT 1\nFROM t\n", "t.sql");
88 let lines: Vec<_> = ctx.lines().collect();
89 assert_eq!(lines.len(), 2);
90 assert_eq!(lines[0], (1, "SELECT 1"));
91 assert_eq!(lines[1], (2, "FROM t"));
92 }
93}