sqruff_lib/rules/layout/
lt05.rs1use hashbrown::{HashMap, HashSet};
2use itertools::enumerate;
3use sqruff_lib_core::dialects::syntax::SyntaxKind;
4use sqruff_lib_core::parser::segments::BlockType;
5
6use crate::core::config::Value;
7use crate::core::rules::context::RuleContext;
8use crate::core::rules::crawlers::{Crawler, RootOnlyCrawler};
9use crate::core::rules::{Erased, ErasedRule, LintResult, Rule, RuleGroups};
10use crate::utils::reflow::sequence::ReflowSequence;
11
12#[derive(Debug, Default, Clone)]
13pub struct RuleLT05 {
14 ignore_comment_lines: bool,
15 ignore_comment_clauses: bool,
16}
17
18impl Rule for RuleLT05 {
19 fn load_from_config(&self, config: &HashMap<String, Value>) -> Result<ErasedRule, String> {
20 Ok(RuleLT05 {
21 ignore_comment_lines: config["ignore_comment_lines"].as_bool().unwrap(),
22 ignore_comment_clauses: config["ignore_comment_clauses"].as_bool().unwrap(),
23 }
24 .erased())
25 }
26 fn name(&self) -> &'static str {
27 "layout.long_lines"
28 }
29
30 fn description(&self) -> &'static str {
31 "Line is too long."
32 }
33
34 fn long_description(&self) -> &'static str {
35 r#"
36**Anti-pattern**
37
38In this example, the line is too long.
39
40```sql
41SELECT
42 my_function(col1 + col2, arg2, arg3) over (partition by col3, col4 order by col5 rows between unbounded preceding and current row) as my_relatively_long_alias,
43 my_other_function(col6, col7 + col8, arg4) as my_other_relatively_long_alias,
44 my_expression_function(col6, col7 + col8, arg4) = col9 + col10 as another_relatively_long_alias
45FROM my_table
46```
47
48**Best practice**
49
50Wraps the line to be within the maximum line length.
51
52```sql
53SELECT
54 my_function(col1 + col2, arg2, arg3)
55 over (
56 partition by col3, col4
57 order by col5 rows between unbounded preceding and current row
58 )
59 as my_relatively_long_alias,
60 my_other_function(col6, col7 + col8, arg4)
61 as my_other_relatively_long_alias,
62 my_expression_function(col6, col7 + col8, arg4)
63 = col9 + col10 as another_relatively_long_alias
64FROM my_table
65```"#
66 }
67
68 fn groups(&self) -> &'static [RuleGroups] {
69 &[RuleGroups::All, RuleGroups::Core, RuleGroups::Layout]
70 }
71 fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
72 let mut results = ReflowSequence::from_root(&context.segment, context.config)
73 .break_long_lines(context.tables)
74 .results();
75
76 let mut to_remove = HashSet::new();
77
78 if self.ignore_comment_lines {
79 let raw_segments = context.segment.get_raw_segments();
80 for (res_idx, res) in enumerate(&results) {
81 if res.anchor.as_ref().unwrap().is_type(SyntaxKind::Comment)
82 || res
83 .anchor
84 .as_ref()
85 .unwrap()
86 .is_type(SyntaxKind::InlineComment)
87 {
88 to_remove.insert(res_idx);
89 continue;
90 }
91
92 let pos_marker = res.anchor.as_ref().unwrap().get_position_marker().unwrap();
93 let raw_idx = raw_segments
94 .iter()
95 .position(|it| it == res.anchor.as_ref().unwrap())
96 .unwrap();
97
98 for seg in &raw_segments[raw_idx..] {
99 if seg.get_position_marker().unwrap().working_line_no
100 != pos_marker.working_line_no
101 {
102 break;
103 }
104
105 if seg.is_type(SyntaxKind::Comment)
106 || seg.is_type(SyntaxKind::InlineComment)
107 || (seg.is_type(SyntaxKind::Placeholder)
108 && seg.block_type() == Some(BlockType::Comment))
109 {
110 to_remove.insert(res_idx);
111 break;
112 }
113 }
114 }
115 }
116
117 if self.ignore_comment_clauses {
118 let raw_segments = context.segment.get_raw_segments();
119 for (res_idx, res) in enumerate(&results) {
120 let raw_idx = raw_segments
121 .iter()
122 .position(|it| it == res.anchor.as_ref().unwrap())
123 .unwrap();
124
125 for seg in &raw_segments[raw_idx..] {
126 if seg.get_position_marker().unwrap().working_line_no
127 != res
128 .anchor
129 .as_ref()
130 .unwrap()
131 .get_position_marker()
132 .unwrap()
133 .working_line_no
134 {
135 break;
136 }
137
138 let mut is_break = false;
139
140 for ps in context.segment.path_to(seg) {
141 if ps.segment.is_type(SyntaxKind::CommentClause)
142 || ps.segment.is_type(SyntaxKind::CommentEqualsClause)
143 {
144 let line_pos =
145 ps.segment.get_position_marker().unwrap().working_line_pos;
146 if (line_pos as i32)
147 < context
148 .config
149 .get("max_line_length", "core")
150 .as_int()
151 .unwrap()
152 {
153 to_remove.insert(res_idx);
154 is_break = true;
155 break;
156 }
157 }
158 }
159
160 if is_break {
161 break;
162 } else {
163 continue;
164 }
165 }
166 }
167 }
168
169 let mut to_remove_vec: Vec<usize> = to_remove.into_iter().collect();
172 to_remove_vec.sort_by(|a, b| b.cmp(a));
173 for idx in to_remove_vec {
174 results.remove(idx);
175 }
176
177 results
178 }
179
180 fn is_fix_compatible(&self) -> bool {
181 true
182 }
183
184 fn crawl_behaviour(&self) -> Crawler {
185 RootOnlyCrawler.into()
186 }
187}