sqruff_lib/rules/aliasing/
al03.rs

1use 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        // Ignore if it's a function with EMITS clause as EMITS is equivalent to AS
68        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}