use rigsql_core::SegmentType;
use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
use crate::utils::{extract_alias_name, is_in_table_context};
use crate::violation::LintViolation;
#[derive(Debug, Default)]
pub struct RuleAL06 {
pub min_alias_length: usize,
pub max_alias_length: usize,
}
impl Rule for RuleAL06 {
fn code(&self) -> &'static str {
"AL06"
}
fn name(&self) -> &'static str {
"aliasing.length"
}
fn description(&self) -> &'static str {
"Enforce table alias lengths in FROM clauses and JOIN conditions."
}
fn explanation(&self) -> &'static str {
"Table aliases that are too short (like single letters) can be cryptic. \
Aliases that are too long defeat the purpose of aliasing. Configure \
min_alias_length and max_alias_length to enforce your team's standards."
}
fn groups(&self) -> &[RuleGroup] {
&[RuleGroup::Aliasing]
}
fn is_fixable(&self) -> bool {
false
}
fn configure(&mut self, settings: &std::collections::HashMap<String, String>) {
if let Some(val) = settings.get("min_alias_length") {
if let Ok(n) = val.parse() {
self.min_alias_length = n;
}
}
if let Some(val) = settings.get("max_alias_length") {
if let Ok(n) = val.parse() {
self.max_alias_length = n;
}
}
}
fn crawl_type(&self) -> CrawlType {
CrawlType::Segment(vec![SegmentType::AliasExpression])
}
fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
if !is_in_table_context(ctx) {
return vec![];
}
if self.min_alias_length == 0 && self.max_alias_length == 0 {
return vec![];
}
let Some(alias_name) = extract_alias_name(ctx.segment.children()) else {
return vec![];
};
let len = alias_name.len();
if self.min_alias_length > 0 && len < self.min_alias_length {
return vec![LintViolation::with_msg_key(
self.code(),
format!(
"Alias '{}' is too short ({} chars, minimum {}).",
alias_name, len, self.min_alias_length
),
ctx.segment.span(),
"rules.AL06.msg.short",
vec![
("name".to_string(), alias_name.to_string()),
("length".to_string(), len.to_string()),
("min".to_string(), self.min_alias_length.to_string()),
],
)];
}
if self.max_alias_length > 0 && len > self.max_alias_length {
return vec![LintViolation::with_msg_key(
self.code(),
format!(
"Alias '{}' is too long ({} chars, maximum {}).",
alias_name, len, self.max_alias_length
),
ctx.segment.span(),
"rules.AL06.msg.long",
vec![
("name".to_string(), alias_name.to_string()),
("length".to_string(), len.to_string()),
("max".to_string(), self.max_alias_length.to_string()),
],
)];
}
vec![]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::lint_sql;
#[test]
fn test_al06_default_no_violation() {
let violations = lint_sql("SELECT * FROM users AS u", RuleAL06::default());
assert_eq!(violations.len(), 0);
}
#[test]
fn test_al06_min_length_flags_short() {
let rule = RuleAL06 {
min_alias_length: 2,
max_alias_length: 0,
};
let violations = lint_sql("SELECT * FROM users AS u", rule);
assert_eq!(violations.len(), 1);
}
#[test]
fn test_al06_min_length_accepts_long() {
let rule = RuleAL06 {
min_alias_length: 2,
max_alias_length: 0,
};
let violations = lint_sql("SELECT * FROM users AS usr", rule);
assert_eq!(violations.len(), 0);
}
}