sqruff_lib/rules/aliasing/
al06.rs

1use ahash::AHashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3use sqruff_lib_core::parser::segments::base::ErasedSegment;
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::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                if 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
65            if let Some(max_alias_length) = self.max_alias_length {
66                if let Some(alias_identifier_ref) =
67                    alias_exp_ref.child(const { &SyntaxSet::new(&[SyntaxKind::Identifier, SyntaxKind::NakedIdentifier]) })
68                {
69                    let alias_identifier = alias_identifier_ref.raw();
70
71                    if alias_identifier.len() > max_alias_length {
72                        violation_buff.push(LintResult::new(
73                            Some(alias_identifier_ref),
74                            Vec::new(),
75                            format!(
76                                "Aliases should be no more than '{:?}' character(s) long.",
77                                self.max_alias_length
78                            )
79                            .into(),
80                            None,
81                        ))
82                    }
83                }
84            }
85        }
86
87        violation_buff
88    }
89}
90
91impl Rule for RuleAL06 {
92    fn load_from_config(&self, config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
93        Ok(RuleAL06 {
94            min_alias_length: config["min_alias_length"].as_int().map(|it| it as usize),
95            max_alias_length: config["max_alias_length"].as_int().map(|it| it as usize),
96        }
97        .erased())
98    }
99
100    fn name(&self) -> &'static str {
101        "aliasing.length"
102    }
103
104    fn description(&self) -> &'static str {
105        "Identify aliases in from clause and join conditions"
106    }
107
108    fn long_description(&self) -> &'static str {
109        r#"
110**Anti-pattern**
111
112In this example, alias `o` is used for the orders table.
113
114```sql
115SELECT
116    SUM(o.amount) as order_amount,
117FROM orders as o
118```
119
120**Best practice**
121
122Avoid aliases. Avoid short aliases when aliases are necessary.
123
124See also: Rule_AL07.
125
126```sql
127SELECT
128    SUM(orders.amount) as order_amount,
129FROM orders
130
131SELECT
132    replacement_orders.amount,
133    previous_orders.amount
134FROM
135    orders AS replacement_orders
136JOIN
137    orders AS previous_orders
138    ON replacement_orders.id = previous_orders.replacement_id
139```
140"#
141    }
142
143    fn groups(&self) -> &'static [RuleGroups] {
144        &[RuleGroups::All, RuleGroups::Core, RuleGroups::Aliasing]
145    }
146
147    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
148        let children = FunctionalContext::new(context).segment().children(None);
149        let from_expression_elements = children.recursive_crawl(
150            const { &SyntaxSet::new(&[SyntaxKind::FromExpressionElement]) },
151            true,
152        );
153        self.lint_aliases(from_expression_elements.base)
154    }
155
156    fn crawl_behaviour(&self) -> Crawler {
157        SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::SelectStatement]) }).into()
158    }
159}