Skip to main content

sqrust_rules/layout/
nested_parentheses.rs

1use sqrust_core::{Diagnostic, FileContext, Rule};
2
3pub struct NestedParentheses {
4    pub max_depth: usize,
5}
6
7impl Default for NestedParentheses {
8    fn default() -> Self {
9        NestedParentheses { max_depth: 5 }
10    }
11}
12
13impl Rule for NestedParentheses {
14    fn name(&self) -> &'static str {
15        "Layout/NestedParentheses"
16    }
17
18    fn check(&self, ctx: &FileContext) -> Vec<Diagnostic> {
19        find_violations(&ctx.source, self.name(), self.max_depth)
20    }
21}
22
23fn find_violations(source: &str, rule_name: &'static str, max_depth: usize) -> Vec<Diagnostic> {
24    let bytes = source.as_bytes();
25    let len = bytes.len();
26    let mut diags = Vec::new();
27
28    let mut depth = 0usize;
29    let mut in_string = false;
30    // True while we are still at or above the first excess depth level for the
31    // current "nested group". Reset to false when depth drops back to max_depth.
32    let mut over_max = false;
33
34    let mut i = 0usize;
35    while i < len {
36        let byte = bytes[i];
37
38        if in_string {
39            if byte == b'\'' {
40                // SQL '' escape: two consecutive single-quotes inside a string
41                if i + 1 < len && bytes[i + 1] == b'\'' {
42                    i += 2;
43                    continue;
44                }
45                in_string = false;
46            }
47            i += 1;
48            continue;
49        }
50
51        // Enter single-quoted string
52        if byte == b'\'' {
53            in_string = true;
54            i += 1;
55            continue;
56        }
57
58        if byte == b'(' {
59            depth += 1;
60            if depth > max_depth && !over_max {
61                over_max = true;
62                let (line, col) = byte_offset_to_line_col(source, i);
63                diags.push(Diagnostic {
64                    rule: rule_name,
65                    message: format!(
66                        "Parenthesis nesting depth {} exceeds maximum of {}",
67                        depth, max_depth
68                    ),
69                    line,
70                    col,
71                });
72            }
73        } else if byte == b')' {
74            if depth > 0 {
75                depth -= 1;
76            }
77            // Once we drop back to max_depth or below, reset the flag so the
78            // next time we go over we flag again.
79            if depth <= max_depth {
80                over_max = false;
81            }
82        }
83
84        i += 1;
85    }
86
87    diags
88}
89
90/// Converts a byte offset into a 1-indexed (line, col) pair.
91fn byte_offset_to_line_col(source: &str, offset: usize) -> (usize, usize) {
92    let mut line = 1usize;
93    let mut line_start = 0usize;
94    for (i, ch) in source.char_indices() {
95        if i == offset {
96            break;
97        }
98        if ch == '\n' {
99            line += 1;
100            line_start = i + 1;
101        }
102    }
103    let col = offset - line_start + 1;
104    (line, col)
105}