rigsql_rules/aliasing/
al03.rs1use rigsql_core::{Segment, SegmentType};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::violation::LintViolation;
5
6#[derive(Debug, Default)]
11pub struct RuleAL03;
12
13impl Rule for RuleAL03 {
14 fn code(&self) -> &'static str {
15 "AL03"
16 }
17 fn name(&self) -> &'static str {
18 "aliasing.expression"
19 }
20 fn description(&self) -> &'static str {
21 "Column expression without alias. Use explicit alias."
22 }
23 fn explanation(&self) -> &'static str {
24 "Complex expressions in SELECT should have an explicit alias using AS. \
25 An unlabeled expression like 'SELECT a + b FROM t' is harder to work with \
26 than 'SELECT a + b AS total FROM t'. This makes result sets self-documenting."
27 }
28 fn groups(&self) -> &[RuleGroup] {
29 &[RuleGroup::Aliasing]
30 }
31 fn is_fixable(&self) -> bool {
32 false
33 }
34
35 fn crawl_type(&self) -> CrawlType {
36 CrawlType::Segment(vec![SegmentType::SelectClause])
37 }
38
39 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
40 let children = ctx.segment.children();
41 let mut violations = Vec::new();
42
43 for child in children {
45 let st = child.segment_type();
46
47 if st.is_trivia() || st == SegmentType::Keyword || st == SegmentType::Comma {
49 continue;
50 }
51
52 if is_complex_expression(child) && !is_wrapped_in_alias(child, ctx) {
55 violations.push(LintViolation::with_msg_key(
56 self.code(),
57 "Column expression should have an explicit alias.",
58 child.span(),
59 "rules.AL03.msg",
60 vec![],
61 ));
62 }
63 }
64
65 violations
66 }
67}
68
69fn is_complex_expression(seg: &Segment) -> bool {
70 matches!(
71 seg.segment_type(),
72 SegmentType::BinaryExpression
73 | SegmentType::FunctionCall
74 | SegmentType::CaseExpression
75 | SegmentType::CastExpression
76 | SegmentType::ParenExpression
77 | SegmentType::UnaryExpression
78 )
79}
80
81fn is_wrapped_in_alias(seg: &Segment, _ctx: &RuleContext) -> bool {
82 seg.segment_type() == SegmentType::AliasExpression
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::test_utils::lint_sql;
94
95 #[test]
96 fn test_al03_flags_function_without_alias() {
97 let violations = lint_sql("SELECT COUNT(*) FROM t", RuleAL03);
98 assert_eq!(violations.len(), 1);
99 }
100
101 #[test]
102 fn test_al03_accepts_function_with_alias() {
103 let violations = lint_sql("SELECT COUNT(*) AS cnt FROM t", RuleAL03);
104 assert_eq!(violations.len(), 0);
105 }
106
107 #[test]
108 fn test_al03_accepts_simple_column() {
109 let violations = lint_sql("SELECT a FROM t", RuleAL03);
110 assert_eq!(violations.len(), 0);
111 }
112}