flowscope_core/linter/
rule.rs1use super::config::sqlfluff_name_for_code;
4use crate::types::{Dialect, Issue, Span};
5use sqlparser::ast::Statement;
6use sqlparser::tokenizer::TokenWithSpan;
7use std::cell::{Cell, RefCell};
8use std::ops::Range;
9
10thread_local! {
11 static ACTIVE_DIALECT: Cell<Dialect> = const { Cell::new(Dialect::Generic) };
12 static ACTIVE_DOCUMENT_TOKENS: RefCell<Vec<TokenWithSpan>> = const { RefCell::new(Vec::new()) };
13 static DOCUMENT_IS_TEMPLATED: Cell<bool> = const { Cell::new(false) };
14}
15
16pub struct LintContext<'a> {
18 pub sql: &'a str,
20 pub statement_range: Range<usize>,
22 pub statement_index: usize,
24}
25
26impl<'a> LintContext<'a> {
27 pub fn statement_sql(&self) -> &str {
29 &self.sql[self.statement_range.clone()]
30 }
31
32 pub fn span_from_statement_offset(&self, start: usize, end: usize) -> Span {
34 Span::new(
35 self.statement_range.start + start,
36 self.statement_range.start + end,
37 )
38 }
39
40 pub fn dialect(&self) -> Dialect {
42 ACTIVE_DIALECT.with(Cell::get)
43 }
44
45 pub fn with_document_tokens<T>(&self, f: impl FnOnce(&[TokenWithSpan]) -> T) -> T {
50 ACTIVE_DOCUMENT_TOKENS.with(|tokens| {
51 let borrowed = tokens.borrow();
52 f(&borrowed)
53 })
54 }
55
56 pub fn is_templated(&self) -> bool {
59 DOCUMENT_IS_TEMPLATED.with(Cell::get)
60 }
61}
62
63pub(crate) fn with_active_dialect<T>(dialect: Dialect, f: impl FnOnce() -> T) -> T {
64 ACTIVE_DIALECT.with(|active| {
65 struct DialectReset<'a> {
66 cell: &'a Cell<Dialect>,
67 previous: Dialect,
68 }
69
70 impl Drop for DialectReset<'_> {
71 fn drop(&mut self) {
72 self.cell.set(self.previous);
73 }
74 }
75
76 let reset = DialectReset {
77 cell: active,
78 previous: active.replace(dialect),
79 };
80 let result = f();
81 drop(reset);
82 result
83 })
84}
85
86pub(crate) fn with_active_is_templated<T>(is_templated: bool, f: impl FnOnce() -> T) -> T {
87 DOCUMENT_IS_TEMPLATED.with(|active| {
88 let previous = active.replace(is_templated);
89 let result = f();
90 active.set(previous);
91 result
92 })
93}
94
95pub(crate) fn with_active_document_tokens<T>(tokens: &[TokenWithSpan], f: impl FnOnce() -> T) -> T {
96 ACTIVE_DOCUMENT_TOKENS.with(|active| {
97 struct TokensReset<'a> {
98 cell: &'a RefCell<Vec<TokenWithSpan>>,
99 previous: Vec<TokenWithSpan>,
100 }
101
102 impl Drop for TokensReset<'_> {
103 fn drop(&mut self) {
104 let _ = self.cell.replace(std::mem::take(&mut self.previous));
105 }
106 }
107
108 let reset = TokensReset {
109 cell: active,
110 previous: active.replace(tokens.to_vec()),
111 };
112 let result = f();
113 drop(reset);
114 result
115 })
116}
117
118pub trait LintRule: Send + Sync {
120 fn code(&self) -> &'static str;
122
123 fn name(&self) -> &'static str;
125
126 fn description(&self) -> &'static str;
128
129 fn sqlfluff_name(&self) -> &'static str {
131 sqlfluff_name_for_code(self.code()).unwrap_or("")
132 }
133
134 fn check(&self, stmt: &Statement, ctx: &LintContext) -> Vec<Issue>;
136}