sqruff_lib/rules/layout/
lt14.rs1use 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}