sqruff_lib/rules/aliasing/
al01.rs1use ahash::AHashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3use sqruff_lib_core::parser::segments::base::SegmentBuilder;
4
5use crate::core::config::Value;
6use crate::core::rules::base::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
7use crate::core::rules::context::RuleContext;
8use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
9use crate::utils::reflow::sequence::{Filter, ReflowInsertPosition, ReflowSequence, TargetSide};
10
11#[derive(Debug, PartialEq, Eq, Clone, Copy)]
12pub enum Aliasing {
13 Explicit,
14 Implicit,
15}
16
17#[derive(Debug, Clone)]
18pub struct RuleAL01 {
19 aliasing: Aliasing,
20 target_parent_types: SyntaxSet,
21}
22
23impl RuleAL01 {
24 pub fn aliasing(mut self, aliasing: Aliasing) -> Self {
25 self.aliasing = aliasing;
26 self
27 }
28
29 pub fn target_parent_types(mut self, target_parent_types: SyntaxSet) -> Self {
30 self.target_parent_types = target_parent_types;
31 self
32 }
33}
34
35impl Default for RuleAL01 {
36 fn default() -> Self {
37 Self {
38 aliasing: Aliasing::Explicit,
39 target_parent_types: const {
40 SyntaxSet::new(&[
41 SyntaxKind::FromExpressionElement,
42 SyntaxKind::MergeStatement,
43 ])
44 },
45 }
46 }
47}
48
49impl Rule for RuleAL01 {
50 fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
51 let aliasing = match _config.get("aliasing").unwrap().as_string().unwrap() {
52 "explicit" => Aliasing::Explicit,
53 "implicit" => Aliasing::Implicit,
54 _ => unreachable!(),
55 };
56
57 Ok(RuleAL01 {
58 aliasing,
59 target_parent_types: const {
60 SyntaxSet::new(&[
61 SyntaxKind::FromExpressionElement,
62 SyntaxKind::MergeStatement,
63 ])
64 },
65 }
66 .erased())
67 }
68
69 fn name(&self) -> &'static str {
70 "aliasing.table"
71 }
72
73 fn description(&self) -> &'static str {
74 "Implicit/explicit aliasing of table."
75 }
76
77 fn long_description(&self) -> &'static str {
78 r#"
79**Anti-pattern**
80
81In this example, the alias `voo` is implicit.
82
83```sql
84SELECT
85 voo.a
86FROM foo voo
87```
88
89**Best practice**
90
91Add `AS` to make the alias explicit.
92
93```sql
94SELECT
95 voo.a
96FROM foo AS voo
97```
98"#
99 }
100
101 fn groups(&self) -> &'static [RuleGroups] {
102 &[RuleGroups::All, RuleGroups::Aliasing]
103 }
104
105 fn eval(&self, rule_cx: &RuleContext) -> Vec<LintResult> {
106 let last_seg = rule_cx.parent_stack.last().unwrap();
107 let last_seg_ty = last_seg.get_type();
108
109 if self.target_parent_types.contains(last_seg_ty) {
110 let as_keyword = rule_cx
111 .segment
112 .segments()
113 .iter()
114 .find(|seg| seg.raw().eq_ignore_ascii_case("AS"));
115
116 if let Some(as_keyword) = as_keyword {
117 if self.aliasing == Aliasing::Implicit {
118 return vec![LintResult::new(
119 as_keyword.clone().into(),
120 ReflowSequence::from_around_target(
121 as_keyword,
122 rule_cx.parent_stack[0].clone(),
123 TargetSide::Both,
124 rule_cx.config,
125 )
126 .without(as_keyword)
127 .respace(rule_cx.tables, false, Filter::All)
128 .fixes(),
129 None,
130 None,
131 )];
132 }
133 } else if self.aliasing != Aliasing::Implicit {
134 let identifier = rule_cx
135 .segment
136 .get_raw_segments()
137 .into_iter()
138 .find(|seg| seg.is_code())
139 .expect("Failed to find identifier. Raise this as a bug on GitHub.");
140
141 return vec![LintResult::new(
142 rule_cx.segment.clone().into(),
143 ReflowSequence::from_around_target(
144 &identifier,
145 rule_cx.parent_stack[0].clone(),
146 TargetSide::Before,
147 rule_cx.config,
148 )
149 .insert(
150 SegmentBuilder::keyword(rule_cx.tables.next_id(), "AS"),
151 identifier,
152 ReflowInsertPosition::Before,
153 )
154 .respace(rule_cx.tables, false, Filter::All)
155 .fixes(),
156 None,
157 None,
158 )];
159 }
160 }
161
162 Vec::new()
163 }
164
165 fn is_fix_compatible(&self) -> bool {
166 true
167 }
168
169 fn crawl_behaviour(&self) -> Crawler {
170 SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::AliasExpression]) }).into()
171 }
172}