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::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}