sqruff_lib/rules/aliasing/
al06.rs1use 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}