sqrust_rules/convention/
select_star.rs1use sqrust_core::{Diagnostic, FileContext, Rule};
2
3use crate::capitalisation::SkipMap;
4
5pub struct SelectStar;
6
7impl Rule for SelectStar {
8 fn name(&self) -> &'static str {
9 "Convention/SelectStar"
10 }
11
12 fn check(&self, ctx: &FileContext) -> Vec<Diagnostic> {
13 let source = &ctx.source;
14 let bytes = source.as_bytes();
15 let len = bytes.len();
16 let skip_map = SkipMap::build(source);
17
18 let mut diags = Vec::new();
19
20 for i in 0..len {
21 if bytes[i] != b'*' {
22 continue;
23 }
24 if !skip_map.is_code(i) {
25 continue;
26 }
27
28 if i > 0 && bytes[i - 1] == b'.' {
31 let (line, col) = line_col(source, i);
32 diags.push(Diagnostic {
33 rule: self.name(),
34 message: "Avoid SELECT *; list columns explicitly".to_string(),
35 line,
36 col,
37 });
38 continue;
39 }
40
41 if i > 0 {
45 let prev = bytes[i - 1];
46 if prev == b'(' {
47 continue;
49 }
50 if prev == b' ' || prev == b'\t' || prev == b'\n' || prev == b'\r' {
51 let next = if i + 1 < len { bytes[i + 1] } else { 0 };
53 let followed_by_separator = next == b' '
54 || next == b'\t'
55 || next == b'\n'
56 || next == b'\r'
57 || next == b','
58 || next == b';'
59 || next == 0;
60 if followed_by_separator {
61 let (line, col) = line_col(source, i);
62 diags.push(Diagnostic {
63 rule: self.name(),
64 message: "Avoid SELECT *; list columns explicitly".to_string(),
65 line,
66 col,
67 });
68 }
69 }
70 }
71 }
72
73 diags
74 }
75}
76
77fn line_col(source: &str, offset: usize) -> (usize, usize) {
79 let before = &source[..offset];
80 let line = before.chars().filter(|&c| c == '\n').count() + 1;
81 let col = before.rfind('\n').map(|p| offset - p - 1).unwrap_or(offset) + 1;
82 (line, col)
83}