use super::config::sqlfluff_name_for_code;
use crate::types::{Dialect, Issue, Span};
use sqlparser::ast::Statement;
use sqlparser::tokenizer::TokenWithSpan;
use std::cell::{Cell, RefCell};
use std::ops::Range;
thread_local! {
static ACTIVE_DIALECT: Cell<Dialect> = const { Cell::new(Dialect::Generic) };
static ACTIVE_DOCUMENT_TOKENS: RefCell<Vec<TokenWithSpan>> = const { RefCell::new(Vec::new()) };
static DOCUMENT_IS_TEMPLATED: Cell<bool> = const { Cell::new(false) };
}
pub struct LintContext<'a> {
pub sql: &'a str,
pub statement_range: Range<usize>,
pub statement_index: usize,
}
impl<'a> LintContext<'a> {
pub fn statement_sql(&self) -> &str {
&self.sql[self.statement_range.clone()]
}
pub fn span_from_statement_offset(&self, start: usize, end: usize) -> Span {
Span::new(
self.statement_range.start + start,
self.statement_range.start + end,
)
}
pub fn dialect(&self) -> Dialect {
ACTIVE_DIALECT.with(Cell::get)
}
pub fn with_document_tokens<T>(&self, f: impl FnOnce(&[TokenWithSpan]) -> T) -> T {
ACTIVE_DOCUMENT_TOKENS.with(|tokens| {
let borrowed = tokens.borrow();
f(&borrowed)
})
}
pub fn is_templated(&self) -> bool {
DOCUMENT_IS_TEMPLATED.with(Cell::get)
}
}
pub(crate) fn with_active_dialect<T>(dialect: Dialect, f: impl FnOnce() -> T) -> T {
ACTIVE_DIALECT.with(|active| {
struct DialectReset<'a> {
cell: &'a Cell<Dialect>,
previous: Dialect,
}
impl Drop for DialectReset<'_> {
fn drop(&mut self) {
self.cell.set(self.previous);
}
}
let reset = DialectReset {
cell: active,
previous: active.replace(dialect),
};
let result = f();
drop(reset);
result
})
}
pub(crate) fn with_active_is_templated<T>(is_templated: bool, f: impl FnOnce() -> T) -> T {
DOCUMENT_IS_TEMPLATED.with(|active| {
let previous = active.replace(is_templated);
let result = f();
active.set(previous);
result
})
}
pub(crate) fn with_active_document_tokens<T>(tokens: &[TokenWithSpan], f: impl FnOnce() -> T) -> T {
ACTIVE_DOCUMENT_TOKENS.with(|active| {
struct TokensReset<'a> {
cell: &'a RefCell<Vec<TokenWithSpan>>,
previous: Vec<TokenWithSpan>,
}
impl Drop for TokensReset<'_> {
fn drop(&mut self) {
let _ = self.cell.replace(std::mem::take(&mut self.previous));
}
}
let reset = TokensReset {
cell: active,
previous: active.replace(tokens.to_vec()),
};
let result = f();
drop(reset);
result
})
}
pub trait LintRule: Send + Sync {
fn code(&self) -> &'static str;
fn name(&self) -> &'static str;
fn description(&self) -> &'static str;
fn sqlfluff_name(&self) -> &'static str {
sqlfluff_name_for_code(self.code()).unwrap_or("")
}
fn check(&self, stmt: &Statement, ctx: &LintContext) -> Vec<Issue>;
}