Skip to main content

rigsql_rules/aliasing/
al02.rs

1use rigsql_core::SegmentType;
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::utils::{has_as_keyword, is_false_alias};
5use crate::violation::{LintViolation, SourceEdit};
6
7/// AL02: Implicit aliasing of columns is not allowed.
8///
9/// Table aliases are checked by AL01; this rule checks column-level aliases.
10/// When a column expression has an alias, AS must be explicit.
11#[derive(Debug, Default)]
12pub struct RuleAL02;
13
14impl Rule for RuleAL02 {
15    fn code(&self) -> &'static str {
16        "AL02"
17    }
18    fn name(&self) -> &'static str {
19        "aliasing.column"
20    }
21    fn description(&self) -> &'static str {
22        "Implicit column aliasing is not allowed."
23    }
24    fn explanation(&self) -> &'static str {
25        "Column aliases should use the explicit AS keyword. \
26         'SELECT col alias' is harder to read than 'SELECT col AS alias'. \
27         This is especially important for complex expressions."
28    }
29    fn groups(&self) -> &[RuleGroup] {
30        &[RuleGroup::Aliasing]
31    }
32    fn is_fixable(&self) -> bool {
33        true
34    }
35
36    fn crawl_type(&self) -> CrawlType {
37        CrawlType::Segment(vec![SegmentType::AliasExpression])
38    }
39
40    fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
41        // Only flag if parent is SelectClause (column aliases, not table aliases)
42        let is_in_select = ctx
43            .parent
44            .is_some_and(|p| p.segment_type() == SegmentType::SelectClause);
45
46        if !is_in_select {
47            return vec![];
48        }
49
50        // Skip if the "alias" is actually a misidentified keyword (e.g. OVER)
51        if is_false_alias(ctx.segment.children()) {
52            return vec![];
53        }
54
55        if !has_as_keyword(ctx.segment.children()) {
56            let children = ctx.segment.children();
57            let fix = children
58                .iter()
59                .rev()
60                .find(|c| !c.segment_type().is_trivia())
61                .map(|alias| SourceEdit::insert(alias.span().start, "AS "));
62
63            return vec![LintViolation::with_fix(
64                self.code(),
65                "Implicit column aliasing not allowed. Use explicit AS keyword.",
66                ctx.segment.span(),
67                fix.into_iter().collect(),
68            )];
69        }
70
71        vec![]
72    }
73}