rigsql_rules/aliasing/
al07.rs1use rigsql_core::{Segment, SegmentType, Span};
2
3use crate::rule::{CrawlType, Rule, RuleContext, RuleGroup};
4use crate::utils::is_in_table_context;
5use crate::violation::{LintViolation, SourceEdit};
6
7#[derive(Debug, Default)]
12pub struct RuleAL07 {
13 pub force_enable: bool,
14}
15
16impl Rule for RuleAL07 {
17 fn code(&self) -> &'static str {
18 "AL07"
19 }
20 fn name(&self) -> &'static str {
21 "aliasing.forbid"
22 }
23 fn description(&self) -> &'static str {
24 "Avoid table aliases in FROM clauses and JOIN conditions."
25 }
26 fn explanation(&self) -> &'static str {
27 "Table aliases can reduce readability, especially initialisms. Using the \
28 full table name makes it clear where each column comes from. This rule \
29 is disabled by default as it is controversial for larger databases."
30 }
31 fn groups(&self) -> &[RuleGroup] {
32 &[RuleGroup::Aliasing]
33 }
34 fn is_fixable(&self) -> bool {
35 true
36 }
37
38 fn configure(&mut self, settings: &std::collections::HashMap<String, String>) {
39 if let Some(val) = settings.get("force_enable") {
40 self.force_enable = val.eq_ignore_ascii_case("true") || val == "1";
41 }
42 }
43
44 fn crawl_type(&self) -> CrawlType {
45 CrawlType::Segment(vec![SegmentType::AliasExpression])
46 }
47
48 fn eval(&self, ctx: &RuleContext) -> Vec<LintViolation> {
49 if !self.force_enable {
50 return vec![];
51 }
52
53 if !is_in_table_context(ctx) {
54 return vec![];
55 }
56
57 let children = ctx.segment.children();
59 let mut alias_start: Option<Span> = None;
60 let mut alias_end: Option<Span> = None;
61 let mut found_table = false;
62
63 for child in children {
64 let st = child.segment_type();
65 if !found_table {
66 if st == SegmentType::Identifier
67 || st == SegmentType::QuotedIdentifier
68 || st == SegmentType::Keyword
69 {
70 found_table = true;
71 }
72 continue;
73 }
74
75 if alias_start.is_none() {
77 if let Segment::Token(_) = child {
79 alias_start = Some(child.span());
80 }
81 }
82 alias_end = Some(child.span());
83 }
84
85 if let (Some(start), Some(end)) = (alias_start, alias_end) {
86 let delete_span = start.merge(end);
87 vec![LintViolation::with_fix_and_msg_key(
88 self.code(),
89 "Avoid using table aliases. Use the full table name instead.",
90 ctx.segment.span(),
91 vec![SourceEdit::delete(delete_span)],
92 "rules.AL07.msg",
93 vec![],
94 )]
95 } else {
96 vec![LintViolation::with_msg_key(
97 self.code(),
98 "Avoid using table aliases. Use the full table name instead.",
99 ctx.segment.span(),
100 "rules.AL07.msg",
101 vec![],
102 )]
103 }
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use crate::test_utils::lint_sql;
111
112 #[test]
113 fn test_al07_disabled_by_default() {
114 let violations = lint_sql("SELECT * FROM users AS u", RuleAL07::default());
115 assert_eq!(violations.len(), 0);
116 }
117
118 #[test]
119 fn test_al07_enabled_flags_table_alias() {
120 let rule = RuleAL07 { force_enable: true };
121 let violations = lint_sql("SELECT * FROM users AS u", rule);
122 assert!(!violations.is_empty());
123 }
124
125 #[test]
126 fn test_al07_skips_column_alias() {
127 let rule = RuleAL07 { force_enable: true };
128 let violations = lint_sql("SELECT col AS c FROM t", rule);
129 assert_eq!(violations.len(), 0);
131 }
132}