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, insert_as_keyword_fix, is_false_alias};
5use crate::violation::LintViolation;
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        let is_in_select = ctx
42            .parent
43            .is_some_and(|p| p.segment_type() == SegmentType::SelectClause);
44        if !is_in_select {
45            return vec![];
46        }
47
48        let children = ctx.segment.children();
49        if is_false_alias(children) || has_as_keyword(children) {
50            return vec![];
51        }
52
53        vec![LintViolation::with_fix(
54            self.code(),
55            "Implicit column aliasing not allowed. Use explicit AS keyword.",
56            ctx.segment.span(),
57            insert_as_keyword_fix(children),
58        )]
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use crate::test_utils::lint_sql;
66
67    #[test]
68    fn test_al02_flags_implicit_column_alias() {
69        let violations = lint_sql("SELECT col alias_name FROM t", RuleAL02);
70        assert_eq!(violations.len(), 1);
71    }
72
73    #[test]
74    fn test_al02_accepts_explicit_as() {
75        let violations = lint_sql("SELECT col AS alias_name FROM t", RuleAL02);
76        assert_eq!(violations.len(), 0);
77    }
78
79    #[test]
80    fn test_al02_skips_non_select() {
81        let violations = lint_sql("SELECT * FROM t1 t2", RuleAL02);
82        assert_eq!(violations.len(), 0);
83    }
84}