rigsql-rules 0.7.0

Lint rules (sqlfluff-compatible) for the rigsql SQL linter
Documentation
use rigsql_core::SegmentType;

use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
use crate::utils::{has_as_keyword, insert_as_keyword_fix, is_false_alias};
use crate::violation::LintViolation;

/// AL01: Implicit aliasing of table/column is not allowed.
///
/// Use explicit `AS` keyword for all aliases.
#[derive(Debug, Default)]
pub struct RuleAL01;

impl Rule for RuleAL01 {
    fn code(&self) -> &'static str {
        "AL01"
    }
    fn name(&self) -> &'static str {
        "aliasing.table"
    }
    fn description(&self) -> &'static str {
        "Implicit aliasing of table/column is not allowed."
    }
    fn explanation(&self) -> &'static str {
        "Aliases should use the explicit AS keyword for clarity. \
         'SELECT a alias_name' is harder to read than 'SELECT a AS alias_name'. \
         Explicit aliasing makes the intent clear and prevents ambiguity."
    }
    fn groups(&self) -> &[RuleGroup] {
        &[RuleGroup::Aliasing]
    }
    fn is_fixable(&self) -> bool {
        true
    }

    fn crawl_type(&self) -> CrawlType {
        CrawlType::Segment(vec![SegmentType::AliasExpression])
    }

    fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
        let children = ctx.segment.children();
        if is_false_alias(children) || has_as_keyword(children) {
            return vec![];
        }

        vec![LintViolation::with_fix_and_msg_key(
            self.code(),
            "Implicit aliasing not allowed. Use explicit AS keyword.",
            ctx.segment.span(),
            insert_as_keyword_fix(children),
            "rules.AL01.msg",
            vec![],
        )]
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_utils::lint_sql;

    #[test]
    fn test_al01_flags_implicit_alias() {
        let violations = lint_sql("SELECT a b FROM t", RuleAL01);
        assert_eq!(violations.len(), 1);
    }

    #[test]
    fn test_al01_accepts_explicit_alias() {
        let violations = lint_sql("SELECT a AS b FROM t", RuleAL01);
        assert_eq!(violations.len(), 0);
    }

    #[test]
    fn test_al01_fix_inserts_as() {
        let violations = lint_sql("SELECT a b FROM t", RuleAL01);
        assert_eq!(violations.len(), 1);
        assert!(!violations[0].fixes.is_empty());
        assert!(violations[0]
            .fixes
            .iter()
            .any(|f| f.new_text.contains("AS")));
    }
}