sqruff_lib/rules/aliasing/
al06.rs

1use ahash::AHashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3use sqruff_lib_core::parser::segments::ErasedSegment;
4
5use crate::core::config::Value;
6use crate::core::rules::context::RuleContext;
7use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
8use crate::core::rules::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
9use crate::utils::functional::context::FunctionalContext;
10
11#[derive(Debug, Clone, Default)]
12pub struct RuleAL06 {
13    min_alias_length: Option<usize>,
14    max_alias_length: Option<usize>,
15}
16
17impl RuleAL06 {
18    fn lint_aliases(&self, from_expression_elements: Vec<ErasedSegment>) -> Vec<LintResult> {
19        let mut violation_buff = Vec::new();
20
21        for from_expression_element in from_expression_elements {
22            let table_ref = from_expression_element
23                .child(const { &SyntaxSet::new(&[SyntaxKind::TableExpression]) })
24                .and_then(|table_expression| {
25                    table_expression.child(
26                        const {
27                            &SyntaxSet::new(&[
28                                SyntaxKind::ObjectReference,
29                                SyntaxKind::TableReference,
30                            ])
31                        },
32                    )
33                });
34
35            let Some(_table_ref) = table_ref else {
36                return Vec::new();
37            };
38
39            let Some(alias_exp_ref) = from_expression_element
40                .child(const { &SyntaxSet::new(&[SyntaxKind::AliasExpression]) })
41            else {
42                return Vec::new();
43            };
44
45            if let Some(min_alias_length) = self.min_alias_length
46                && let Some(alias_identifier_ref) =
47                    alias_exp_ref.child(const { &SyntaxSet::new(&[SyntaxKind::Identifier, SyntaxKind::NakedIdentifier]) })
48                {
49                    let alias_identifier = alias_identifier_ref.raw();
50                    if alias_identifier.len() < min_alias_length {
51                        violation_buff.push(LintResult::new(
52                            Some(alias_identifier_ref),
53                            Vec::new(),
54                            format!(
55                                "Aliases should be at least '{:?}' character(s) long",
56                                self.min_alias_length
57                            )
58                            .into(),
59                            None,
60                        ))
61                    }
62                }
63
64            if let Some(max_alias_length) = self.max_alias_length
65                && let Some(alias_identifier_ref) =
66                    alias_exp_ref.child(const { &SyntaxSet::new(&[SyntaxKind::Identifier, SyntaxKind::NakedIdentifier]) })
67                {
68                    let alias_identifier = alias_identifier_ref.raw();
69
70                    if alias_identifier.len() > max_alias_length {
71                        violation_buff.push(LintResult::new(
72                            Some(alias_identifier_ref),
73                            Vec::new(),
74                            format!(
75                                "Aliases should be no more than '{:?}' character(s) long.",
76                                self.max_alias_length
77                            )
78                            .into(),
79                            None,
80                        ))
81                    }
82                }
83        }
84
85        violation_buff
86    }
87}
88
89impl Rule for RuleAL06 {
90    fn load_from_config(&self, config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
91        Ok(RuleAL06 {
92            min_alias_length: config["min_alias_length"].as_int().map(|it| it as usize),
93            max_alias_length: config["max_alias_length"].as_int().map(|it| it as usize),
94        }
95        .erased())
96    }
97
98    fn name(&self) -> &'static str {
99        "aliasing.length"
100    }
101
102    fn description(&self) -> &'static str {
103        "Identify aliases in from clause and join conditions"
104    }
105
106    fn long_description(&self) -> &'static str {
107        r#"
108**Anti-pattern**
109
110In this example, alias `o` is used for the orders table.
111
112```sql
113SELECT
114    SUM(o.amount) as order_amount,
115FROM orders as o
116```
117
118**Best practice**
119
120Avoid aliases. Avoid short aliases when aliases are necessary.
121
122See also: Rule_AL07.
123
124```sql
125SELECT
126    SUM(orders.amount) as order_amount,
127FROM orders
128
129SELECT
130    replacement_orders.amount,
131    previous_orders.amount
132FROM
133    orders AS replacement_orders
134JOIN
135    orders AS previous_orders
136    ON replacement_orders.id = previous_orders.replacement_id
137```
138"#
139    }
140
141    fn groups(&self) -> &'static [RuleGroups] {
142        &[RuleGroups::All, RuleGroups::Core, RuleGroups::Aliasing]
143    }
144
145    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
146        let children = FunctionalContext::new(context).segment().children(None);
147        let from_expression_elements = children.recursive_crawl(
148            const { &SyntaxSet::new(&[SyntaxKind::FromExpressionElement]) },
149            true,
150        );
151        self.lint_aliases(from_expression_elements.base)
152    }
153
154    fn crawl_behaviour(&self) -> Crawler {
155        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::SelectStatement]) }).into()
156    }
157}