sqruff_lib/rules/aliasing/
al01.rs

1use 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}