sqruff_lib/rules/layout/
lt13.rs

1use ahash::AHashMap;
2use itertools::Itertools;
3use sqruff_lib_core::dialects::syntax::SyntaxKind;
4use sqruff_lib_core::lint_fix::LintFix;
5use sqruff_lib_core::utils::functional::segments::Segments;
6
7use crate::core::config::Value;
8use crate::core::rules::base::{Erased, ErasedRule, LintPhase, LintResult, Rule, RuleGroups};
9use crate::core::rules::context::RuleContext;
10use crate::core::rules::crawlers::{Crawler, RootOnlyCrawler};
11
12#[derive(Debug, Default, Clone)]
13pub struct RuleLT13;
14
15impl Rule for RuleLT13 {
16    fn load_from_config(&self, _config: &AHashMap<String, Value>) -> Result<ErasedRule, String> {
17        Ok(RuleLT13.erased())
18    }
19
20    fn lint_phase(&self) -> LintPhase {
21        LintPhase::Post
22    }
23
24    fn name(&self) -> &'static str {
25        "layout.start_of_file"
26    }
27
28    fn description(&self) -> &'static str {
29        "Files must not begin with newlines or whitespace."
30    }
31
32    fn long_description(&self) -> &'static str {
33        r#"
34**Anti-pattern**
35
36The file begins with newlines or whitespace. The ^ represents the beginning of the file.
37
38```sql
39 ^
40
41 SELECT
42     a
43 FROM foo
44
45 -- Beginning on an indented line is also forbidden,
46 -- (the • represents space).
47
48 ••••SELECT
49 ••••a
50 FROM
51 ••••foo
52```
53
54**Best practice**
55
56Start file on either code or comment. (The ^ represents the beginning of the file.)
57
58```sql
59 ^SELECT
60     a
61 FROM foo
62
63 -- Including an initial block comment.
64
65 ^/*
66 This is a description of my SQL code.
67 */
68 SELECT
69     a
70 FROM
71     foo
72
73 -- Including an initial inline comment.
74
75 ^--This is a description of my SQL code.
76 SELECT
77     a
78 FROM
79     foo
80```
81"#
82    }
83
84    fn groups(&self) -> &'static [RuleGroups] {
85        &[RuleGroups::All, RuleGroups::Layout]
86    }
87
88    fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
89        let mut raw_segments = Vec::new();
90
91        for seg in context.segment.recursive_crawl_all(false) {
92            if !seg.segments().is_empty() {
93                continue;
94            }
95
96            if matches!(
97                seg.get_type(),
98                SyntaxKind::Newline
99                    | SyntaxKind::Whitespace
100                    | SyntaxKind::Indent
101                    | SyntaxKind::Dedent
102            ) {
103                raw_segments.push(seg);
104                continue;
105            }
106
107            let raw_stack =
108                Segments::from_vec(raw_segments.clone(), context.templated_file.clone());
109            // Non-whitespace segment.
110            if !raw_stack.all(Some(|seg| seg.is_meta())) {
111                return vec![LintResult::new(
112                    context.segment.clone().into(),
113                    raw_stack.into_iter().map(LintFix::delete).collect_vec(),
114                    None,
115                    None,
116                )];
117            } else {
118                break;
119            }
120        }
121
122        Vec::new()
123    }
124
125    fn is_fix_compatible(&self) -> bool {
126        true
127    }
128
129    fn crawl_behaviour(&self) -> Crawler {
130        RootOnlyCrawler.into()
131    }
132}