sqruff_lib/rules/aliasing/
al03.rs1use ahash::AHashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3use sqruff_lib_core::parser::segments::ErasedSegment;
4use sqruff_lib_core::utils::functional::segments::Segments;
5
6use crate::core::config::Value;
7use crate::core::rules::context::RuleContext;
8use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
9use crate::core::rules::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
10use crate::utils::functional::context::FunctionalContext;
11
12#[derive(Debug, Clone)]
13pub struct RuleAL03;
14
15impl Rule for RuleAL03 {
16 fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
17 Ok(RuleAL03.erased())
18 }
19
20 fn name(&self) -> &'static str {
21 "aliasing.expression"
22 }
23
24 fn description(&self) -> &'static str {
25 "Column expression without alias. Use explicit `AS` clause."
26 }
27
28 fn long_description(&self) -> &'static str {
29 r#"
30**Anti-pattern**
31
32In this example, there is no alias for both sums.
33
34```sql
35SELECT
36 sum(a),
37 sum(b)
38FROM foo
39```
40
41**Best practice**
42
43Add aliases.
44
45```sql
46SELECT
47 sum(a) AS a_sum,
48 sum(b) AS b_sum
49FROM foo
50```
51"#
52 }
53
54 fn groups(&self) -> &'static [RuleGroups] {
55 &[RuleGroups::All, RuleGroups::Core, RuleGroups::Aliasing]
56 }
57
58 fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
59 let functional_context = FunctionalContext::new(context);
60 let segment = functional_context.segment();
61 let children = segment.children_all();
62
63 if children.any_match(|it| it.get_type() == SyntaxKind::AliasExpression) {
64 return Vec::new();
65 }
66
67 let functions = children.filter(|sp: &ErasedSegment| sp.is_type(SyntaxKind::Function));
69 if !functions
70 .children_all()
71 .filter(|sp: &ErasedSegment| sp.is_type(SyntaxKind::EmitsSegment))
72 .is_empty()
73 {
74 return Vec::new();
75 }
76
77 let casts = children
78 .children_all()
79 .filter(|it: &ErasedSegment| it.is_type(SyntaxKind::CastExpression));
80 if !casts.is_empty()
81 && !casts
82 .children_all()
83 .any_match(|it| it.is_type(SyntaxKind::Function))
84 {
85 return Vec::new();
86 }
87
88 let parent_stack = functional_context.parent_stack();
89
90 if parent_stack
91 .find_last_where(|it| it.is_type(SyntaxKind::CommonTableExpression))
92 .children_all()
93 .any_match(|it| it.is_type(SyntaxKind::CTEColumnList))
94 {
95 return Vec::new();
96 }
97
98 let select_clause_children =
99 children.filter(|it: &ErasedSegment| !it.is_type(SyntaxKind::Star));
100 let is_complex_clause = recursively_check_is_complex(select_clause_children);
101
102 if !is_complex_clause {
103 return Vec::new();
104 }
105
106 if context
107 .config
108 .get("allow_scalar", "rules")
109 .as_bool()
110 .unwrap()
111 {
112 let immediate_parent = parent_stack.last().unwrap().clone();
113 let elements = Segments::new(immediate_parent, None)
114 .children_where(|it| it.is_type(SyntaxKind::SelectClauseElement));
115
116 if elements.len() > 1 {
117 return vec![LintResult::new(
118 context.segment.clone().into(),
119 Vec::new(),
120 None,
121 None,
122 )];
123 }
124
125 return Vec::new();
126 }
127
128 vec![LintResult::new(
129 context.segment.clone().into(),
130 Vec::new(),
131 None,
132 None,
133 )]
134 }
135
136 fn crawl_behaviour(&self) -> Crawler {
137 SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::SelectClauseElement]) })
138 .into()
139 }
140}
141
142fn recursively_check_is_complex(select_clause_or_exp_children: Segments) -> bool {
143 let filtered = select_clause_or_exp_children.filter(|it: &ErasedSegment| {
144 !matches!(
145 it.get_type(),
146 SyntaxKind::Whitespace
147 | SyntaxKind::Newline
148 | SyntaxKind::ColumnReference
149 | SyntaxKind::WildcardExpression
150 | SyntaxKind::Bracketed
151 )
152 });
153 let remaining_count = filtered.len();
154
155 if remaining_count == 0 {
156 return false;
157 }
158
159 let first_el = filtered.head();
160
161 if remaining_count > 1 || !first_el.all_match(|it| it.is_type(SyntaxKind::Expression)) {
162 return true;
163 }
164
165 recursively_check_is_complex(first_el.children_all())
166}