Skip to main content

sqrust_rules/layout/
leading_comma.rs

1use sqrust_core::{Diagnostic, FileContext, Rule};
2
3pub struct LeadingComma;
4
5impl Rule for LeadingComma {
6    fn name(&self) -> &'static str {
7        "Layout/LeadingComma"
8    }
9
10    fn check(&self, ctx: &FileContext) -> Vec<Diagnostic> {
11        let mut diags = Vec::new();
12
13        // Text-based scan: works even when ctx.parse_errors is non-empty.
14        // Split on '\n' (not .lines()) so we preserve accurate line indices.
15        let lines: Vec<&str> = ctx.source.split('\n').collect();
16        // Track whether we are inside a single-quoted string literal across lines.
17        // Uses a simple odd/even quote-count heuristic: each unescaped `'` toggles
18        // the in_string state. Good enough for the rare "comma-at-line-start inside
19        // a multi-line string" false-positive prevention.
20        let mut in_string = false;
21
22        for (idx, line) in lines.iter().enumerate() {
23            let trimmed = line.trim_start();
24
25            if !in_string && trimmed.starts_with(',') {
26                // col is 1-indexed position of ',' in the original line
27                let leading_spaces = line.len() - trimmed.len();
28                let col = leading_spaces + 1;
29                diags.push(Diagnostic {
30                    rule: self.name(),
31                    message: "Comma at start of line; place commas at the end of the previous line"
32                        .to_string(),
33                    line: idx + 1,
34                    col,
35                });
36            }
37
38            // Update in_string: each single-quote character toggles the state.
39            // Odd number of quotes on the line means we cross a string boundary.
40            let quote_count = line.chars().filter(|&c| c == '\'').count();
41            if quote_count % 2 != 0 {
42                in_string = !in_string;
43            }
44        }
45
46        diags
47    }
48}