Skip to main content

sqruff_lib/rules/layout/
lt14.rs

1use hashbrown::HashMap;
2use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
3
4use crate::core::config::Value;
5use crate::core::rules::context::RuleContext;
6use crate::core::rules::crawlers::{Crawler, RootOnlyCrawler};
7use crate::core::rules::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
8use crate::utils::reflow::sequence::ReflowSequence;
9
10const CLAUSE_TYPES: SyntaxSet = SyntaxSet::new(&[
11    SyntaxKind::SelectClause,
12    SyntaxKind::FromClause,
13    SyntaxKind::WhereClause,
14    SyntaxKind::JoinClause,
15    SyntaxKind::GroupbyClause,
16    SyntaxKind::OrderbyClause,
17    SyntaxKind::HavingClause,
18    SyntaxKind::LimitClause,
19]);
20
21#[derive(Debug, Default, Clone)]
22pub struct RuleLT14;
23
24impl Rule for RuleLT14 {
25    fn load_from_config(&self, _config: &HashMap<String, Value>) -> Result<ErasedRule, String> {
26        Ok(RuleLT14.erased())
27    }
28
29    fn name(&self) -> &'static str {
30        "layout.keyword_newline"
31    }
32
33    fn description(&self) -> &'static str {
34        "Keyword clause newline enforcement."
35    }
36
37    fn long_description(&self) -> &'static str {
38        r#"
39This rule checks the following clause types:
40
41- `SELECT`
42- `FROM`
43- `WHERE`
44- `JOIN`
45- `GROUP BY`
46- `ORDER BY`
47- `HAVING`
48- `LIMIT`
49
50**Anti-pattern**
51
52In this example, some clauses share a line while others don't,
53creating inconsistent formatting.
54
55```sql
56SELECT a
57FROM foo WHERE a = 1
58```
59
60**Best practice**
61
62Each clause should start on a new line.
63
64```sql
65SELECT a
66FROM foo
67WHERE a = 1
68```
69"#
70    }
71
72    fn groups(&self) -> &'static [RuleGroups] {
73        &[RuleGroups::All, RuleGroups::Layout]
74    }
75
76    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
77        ReflowSequence::from_root(&context.segment, context.config)
78            .rebreak(context.tables)
79            .results()
80            .into_iter()
81            .filter(|r| {
82                r.anchor
83                    .as_ref()
84                    .is_some_and(|seg| CLAUSE_TYPES.contains(seg.get_type()))
85            })
86            .collect()
87    }
88
89    fn is_fix_compatible(&self) -> bool {
90        true
91    }
92
93    fn crawl_behaviour(&self) -> Crawler {
94        RootOnlyCrawler.into()
95    }
96}