rigsql_rules/aliasing/
al08.rs1use std::collections::HashMap;
2
3use rigsql_core::SegmentType;
4
5use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
6use crate::utils::extract_alias_name;
7use crate::violation::LintViolation;
8
9#[derive(Debug, Default)]
13pub struct RuleAL08;
14
15impl Rule for RuleAL08 {
16 fn code(&self) -> &'static str {
17 "AL08"
18 }
19 fn name(&self) -> &'static str {
20 "aliasing.unique.column"
21 }
22 fn description(&self) -> &'static str {
23 "Column aliases should be unique within each statement."
24 }
25 fn explanation(&self) -> &'static str {
26 "When the same alias is used for multiple columns in a single SELECT clause, \
27 the result set becomes ambiguous. Each column alias must be unique within \
28 its containing SELECT clause."
29 }
30 fn groups(&self) -> &[RuleGroup] {
31 &[RuleGroup::Aliasing]
32 }
33 fn is_fixable(&self) -> bool {
34 false
35 }
36
37 fn crawl_type(&self) -> CrawlType {
38 CrawlType::Segment(vec![SegmentType::SelectClause])
39 }
40
41 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
42 let mut violations = Vec::new();
43 let mut seen: HashMap<String, rigsql_core::Span> = HashMap::new();
44
45 for child in ctx.segment.children() {
46 if child.segment_type() == SegmentType::AliasExpression {
47 if let Some(name) = extract_alias_name(child.children()) {
48 let span = child.span();
49 let lower = name.to_lowercase();
50 if let Some(first_span) = seen.get(&lower) {
51 violations.push(LintViolation::new(
52 self.code(),
53 format!(
54 "Duplicate column alias '{}'. First used at offset {}.",
55 name, first_span.start,
56 ),
57 span,
58 ));
59 } else {
60 seen.insert(lower, span);
61 }
62 }
63 }
64 }
65
66 violations
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use crate::test_utils::lint_sql;
74
75 #[test]
76 fn test_al08_flags_duplicate_column_alias() {
77 let violations = lint_sql("SELECT a AS x, b AS x FROM t", RuleAL08);
78 assert_eq!(violations.len(), 1);
79 }
80
81 #[test]
82 fn test_al08_accepts_unique_column_aliases() {
83 let violations = lint_sql("SELECT a AS x, b AS y FROM t", RuleAL08);
84 assert_eq!(violations.len(), 0);
85 }
86}