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