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::new(
56 self.code(),
57 "Column expression should have an explicit alias.",
58 child.span(),
59 ));
60 }
61 }
62
63 violations
64 }
65}
66
67fn is_complex_expression(seg: &Segment) -> bool {
68 matches!(
69 seg.segment_type(),
70 SegmentType::BinaryExpression
71 | SegmentType::FunctionCall
72 | SegmentType::CaseExpression
73 | SegmentType::CastExpression
74 | SegmentType::ParenExpression
75 | SegmentType::UnaryExpression
76 )
77}
78
79fn is_wrapped_in_alias(seg: &Segment, _ctx: &RuleContext) -> bool {
80 seg.segment_type() == SegmentType::AliasExpression
86}