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