sqruff_lib/rules/aliasing/
al03.rs1use ahash::AHashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3use sqruff_lib_core::parser::segments::base::ErasedSegment;
4use sqruff_lib_core::utils::functional::segments::Segments;
5
6use crate::core::config::Value;
7use crate::core::rules::base::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
8use crate::core::rules::context::RuleContext;
9use crate::core::rules::crawlers::{Crawler, SegmentSeekerCrawler};
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(None);
62
63 if children.any(Some(|it| it.get_type() == SyntaxKind::AliasExpression)) {
64 return Vec::new();
65 }
66
67 if !children
69 .select(
70 Some(|sp: &ErasedSegment| sp.is_type(SyntaxKind::Function)),
71 None,
72 None,
73 None,
74 )
75 .children(None)
76 .select(
77 Some(|sp: &ErasedSegment| sp.is_type(SyntaxKind::EmitsSegment)),
78 None,
79 None,
80 None,
81 )
82 .is_empty()
83 {
84 return Vec::new();
85 }
86
87 if !children
88 .children(None)
89 .select(
90 Some(|it: &ErasedSegment| it.is_type(SyntaxKind::CastExpression)),
91 None,
92 None,
93 None,
94 )
95 .is_empty()
96 && !children
97 .children(None)
98 .select(
99 Some(|it: &ErasedSegment| it.is_type(SyntaxKind::CastExpression)),
100 None,
101 None,
102 None,
103 )
104 .children(None)
105 .any(Some(|it| it.is_type(SyntaxKind::Function)))
106 {
107 return Vec::new();
108 }
109
110 let parent_stack = functional_context.parent_stack();
111
112 if parent_stack
113 .find_last(Some(|it| it.is_type(SyntaxKind::CommonTableExpression)))
114 .children(None)
115 .any(Some(|it| it.is_type(SyntaxKind::CTEColumnList)))
116 {
117 return Vec::new();
118 }
119
120 let select_clause_children = children.select(
121 Some(|it: &ErasedSegment| !it.is_type(SyntaxKind::Star)),
122 None,
123 None,
124 None,
125 );
126 let is_complex_clause = recursively_check_is_complex(select_clause_children);
127
128 if !is_complex_clause {
129 return Vec::new();
130 }
131
132 if context
133 .config
134 .get("allow_scalar", "rules")
135 .as_bool()
136 .unwrap()
137 {
138 let immediate_parent = parent_stack.find_last(None);
139 let elements =
140 immediate_parent.children(Some(|it| it.is_type(SyntaxKind::SelectClauseElement)));
141
142 if elements.len() > 1 {
143 return vec![LintResult::new(
144 context.segment.clone().into(),
145 Vec::new(),
146 None,
147 None,
148 )];
149 }
150
151 return Vec::new();
152 }
153
154 vec![LintResult::new(
155 context.segment.clone().into(),
156 Vec::new(),
157 None,
158 None,
159 )]
160 }
161
162 fn crawl_behaviour(&self) -> Crawler {
163 SegmentSeekerCrawler::new(const { SyntaxSet::new(&[SyntaxKind::SelectClauseElement]) })
164 .into()
165 }
166}
167
168fn recursively_check_is_complex(select_clause_or_exp_children: Segments) -> bool {
169 let selector: Option<fn(&ErasedSegment) -> bool> = Some(|it: &ErasedSegment| {
170 !matches!(
171 it.get_type(),
172 SyntaxKind::Whitespace
173 | SyntaxKind::Newline
174 | SyntaxKind::ColumnReference
175 | SyntaxKind::WildcardExpression
176 | SyntaxKind::Bracketed
177 )
178 });
179
180 let filtered = select_clause_or_exp_children.select(selector, None, None, None);
181 let remaining_count = filtered.len();
182
183 if remaining_count == 0 {
184 return false;
185 }
186
187 let first_el = filtered.find_first::<fn(&ErasedSegment) -> _>(None);
188
189 if remaining_count > 1 || !first_el.all(Some(|it| it.is_type(SyntaxKind::Expression))) {
190 return true;
191 }
192
193 recursively_check_is_complex(first_el.children(None))
194}