Skip to main content

rigsql_rules/aliasing/
al06.rs

1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::utils::{extract_alias_name, is_in_table_context};
5use crate::violation::LintViolation;
6
7/// AL06: Enforce table alias lengths in FROM clauses and JOIN conditions.
8///
9/// Configurable minimum and maximum alias length. Disabled by default
10/// (both min and max are 0).
11#[derive(Debug, Default)]
12pub struct RuleAL06 {
13    pub min_alias_length: usize,
14    pub max_alias_length: usize,
15}
16
17impl Rule for RuleAL06 {
18    fn code(&self) -> &'static str {
19        "AL06"
20    }
21    fn name(&self) -> &'static str {
22        "aliasing.length"
23    }
24    fn description(&self) -> &'static str {
25        "Enforce table alias lengths in FROM clauses and JOIN conditions."
26    }
27    fn explanation(&self) -> &'static str {
28        "Table aliases that are too short (like single letters) can be cryptic. \
29         Aliases that are too long defeat the purpose of aliasing. Configure \
30         min_alias_length and max_alias_length to enforce your team's standards."
31    }
32    fn groups(&self) -> &[RuleGroup] {
33        &[RuleGroup::Aliasing]
34    }
35    fn is_fixable(&self) -> bool {
36        false
37    }
38
39    fn configure(&mut self, settings: &std::collections::HashMap<String, String>) {
40        if let Some(val) = settings.get("min_alias_length") {
41            if let Ok(n) = val.parse() {
42                self.min_alias_length = n;
43            }
44        }
45        if let Some(val) = settings.get("max_alias_length") {
46            if let Ok(n) = val.parse() {
47                self.max_alias_length = n;
48            }
49        }
50    }
51
52    fn crawl_type(&self) -> CrawlType {
53        CrawlType::Segment(vec![SegmentType::AliasExpression])
54    }
55
56    fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
57        if !is_in_table_context(ctx) {
58            return vec![];
59        }
60
61        // Both 0 means disabled
62        if self.min_alias_length == 0 && self.max_alias_length == 0 {
63            return vec![];
64        }
65
66        let Some(alias_name) = extract_alias_name(ctx.segment.children()) else {
67            return vec![];
68        };
69
70        let len = alias_name.len();
71
72        if self.min_alias_length > 0 && len < self.min_alias_length {
73            return vec![LintViolation::with_msg_key(
74                self.code(),
75                format!(
76                    "Alias '{}' is too short ({} chars, minimum {}).",
77                    alias_name, len, self.min_alias_length
78                ),
79                ctx.segment.span(),
80                "rules.AL06.msg.short",
81                vec![
82                    ("name".to_string(), alias_name.to_string()),
83                    ("length".to_string(), len.to_string()),
84                    ("min".to_string(), self.min_alias_length.to_string()),
85                ],
86            )];
87        }
88
89        if self.max_alias_length > 0 && len > self.max_alias_length {
90            return vec![LintViolation::with_msg_key(
91                self.code(),
92                format!(
93                    "Alias '{}' is too long ({} chars, maximum {}).",
94                    alias_name, len, self.max_alias_length
95                ),
96                ctx.segment.span(),
97                "rules.AL06.msg.long",
98                vec![
99                    ("name".to_string(), alias_name.to_string()),
100                    ("length".to_string(), len.to_string()),
101                    ("max".to_string(), self.max_alias_length.to_string()),
102                ],
103            )];
104        }
105
106        vec![]
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use crate::test_utils::lint_sql;
114
115    #[test]
116    fn test_al06_default_no_violation() {
117        let violations = lint_sql("SELECT * FROM users AS u", RuleAL06::default());
118        assert_eq!(violations.len(), 0);
119    }
120
121    #[test]
122    fn test_al06_min_length_flags_short() {
123        let rule = RuleAL06 {
124            min_alias_length: 2,
125            max_alias_length: 0,
126        };
127        let violations = lint_sql("SELECT * FROM users AS u", rule);
128        assert_eq!(violations.len(), 1);
129    }
130
131    #[test]
132    fn test_al06_min_length_accepts_long() {
133        let rule = RuleAL06 {
134            min_alias_length: 2,
135            max_alias_length: 0,
136        };
137        let violations = lint_sql("SELECT * FROM users AS usr", rule);
138        assert_eq!(violations.len(), 0);
139    }
140}