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