rigsql_rules/aliasing/
al06.rs1use 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#[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 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::new(
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 )];
81 }
82
83 if self.max_alias_length > 0 && len > self.max_alias_length {
84 return vec![LintViolation::new(
85 self.code(),
86 format!(
87 "Alias '{}' is too long ({} chars, maximum {}).",
88 alias_name, len, self.max_alias_length
89 ),
90 ctx.segment.span(),
91 )];
92 }
93
94 vec![]
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use crate::test_utils::lint_sql;
102
103 #[test]
104 fn test_al06_default_no_violation() {
105 let violations = lint_sql("SELECT * FROM users AS u", RuleAL06::default());
106 assert_eq!(violations.len(), 0);
107 }
108
109 #[test]
110 fn test_al06_min_length_flags_short() {
111 let rule = RuleAL06 {
112 min_alias_length: 2,
113 max_alias_length: 0,
114 };
115 let violations = lint_sql("SELECT * FROM users AS u", rule);
116 assert_eq!(violations.len(), 1);
117 }
118
119 #[test]
120 fn test_al06_min_length_accepts_long() {
121 let rule = RuleAL06 {
122 min_alias_length: 2,
123 max_alias_length: 0,
124 };
125 let violations = lint_sql("SELECT * FROM users AS usr", rule);
126 assert_eq!(violations.len(), 0);
127 }
128}