1use std::cmp::PartialEq;
2use std::mem::take;
3
4use itertools::Itertools;
5use sqruff_lib_core::dialects::syntax::SyntaxKind;
6use sqruff_lib_core::lint_fix::LintFix;
7use sqruff_lib_core::parser::segments::{ErasedSegment, Tables};
8
9use super::config::ReflowConfig;
10use super::depth_map::DepthMap;
11use super::elements::{ReflowBlock, ReflowElement, ReflowPoint, ReflowSequenceType};
12use super::rebreak::{rebreak_keywords_sequence, rebreak_sequence};
13use super::reindent::{construct_single_indent, lint_indent_points, lint_line_length};
14use crate::core::config::FluffConfig;
15use crate::core::rules::LintResult;
16
17pub struct ReflowSequence<'a, 'b> {
18 root_segment: &'b ErasedSegment,
19 elements: ReflowSequenceType,
20 lint_results: Vec<LintResult>,
21 reflow_config: &'a ReflowConfig,
22 depth_map: DepthMap,
23}
24
25#[derive(Clone, Copy, PartialEq, Eq)]
26pub enum TargetSide {
27 Both,
28 Before,
29 After,
30}
31
32#[derive(Clone, Copy, PartialEq, Eq)]
33pub enum ReflowInsertPosition {
34 Before,
35}
36
37#[derive(Clone, Copy, PartialEq, Eq)]
38pub enum RebreakType {
39 Lines,
40 Keywords,
41}
42
43impl<'a, 'b> ReflowSequence<'a, 'b> {
44 pub fn raw(&self) -> String {
45 self.elements.iter().map(|it| it.raw()).join("")
46 }
47
48 pub fn results(self) -> Vec<LintResult> {
49 self.lint_results
50 }
51
52 pub fn fixes(self) -> Vec<LintFix> {
53 self.results()
54 .into_iter()
55 .flat_map(|result| result.fixes)
56 .collect()
57 }
58
59 pub fn from_root(root_segment: &'b ErasedSegment, config: &'a FluffConfig) -> Self {
60 let raws_with_stack = root_segment.raw_segments_with_ancestors();
61 let depth_map = Some(DepthMap::from_raws_with_stack(raws_with_stack));
62
63 Self::from_raw_segments(
64 root_segment.get_raw_segments(),
65 root_segment,
66 config,
67 depth_map,
68 )
69 }
70
71 pub fn from_raw_segments(
72 segments: Vec<ErasedSegment>,
73 root_segment: &'b ErasedSegment,
74 config: &'a FluffConfig,
75 depth_map: Option<DepthMap>,
76 ) -> ReflowSequence<'a, 'b> {
77 let reflow_config = config.reflow();
78 let depth_map = depth_map.unwrap_or_else(|| {
79 DepthMap::from_raws_and_root(segments.clone().into_iter(), root_segment)
80 });
81 let elements = Self::elements_from_raw_segments(segments, &depth_map, reflow_config);
82
83 Self {
84 root_segment,
85 elements,
86 lint_results: Vec::new(),
87 reflow_config,
88 depth_map,
89 }
90 }
91
92 fn elements_from_raw_segments(
93 segments: Vec<ErasedSegment>,
94 depth_map: &DepthMap,
95 reflow_config: &ReflowConfig,
96 ) -> Vec<ReflowElement> {
97 let mut elem_buff = Vec::new();
98 let mut seg_buff = Vec::new();
99
100 for seg in segments {
101 if matches!(
106 seg.get_type(),
107 SyntaxKind::Whitespace
108 | SyntaxKind::Newline
109 | SyntaxKind::Indent
110 | SyntaxKind::Implicit
111 | SyntaxKind::Dedent
112 ) {
113 seg_buff.push(seg);
115 continue;
116 } else if !elem_buff.is_empty() || !seg_buff.is_empty() {
117 let seg_buff = take(&mut seg_buff);
120 elem_buff.push(ReflowElement::Point(ReflowPoint::new(seg_buff)));
121 }
122
123 let depth_info = depth_map.get_depth_info(&seg);
125 elem_buff.push(ReflowElement::Block(ReflowBlock::from_config(
126 seg,
127 reflow_config,
128 depth_info,
129 )));
130 }
131
132 if !seg_buff.is_empty() {
133 elem_buff.push(ReflowPoint::new(seg_buff).into());
134 }
135
136 elem_buff
137 }
138
139 pub fn from_around_target(
140 target_segment: &ErasedSegment,
141 root_segment: &'b ErasedSegment,
142 sides: TargetSide,
143 config: &'a FluffConfig,
144 ) -> ReflowSequence<'a, 'b> {
145 let all_raws = root_segment.get_raw_segments();
146 let target_raws = target_segment.get_raw_segments();
147
148 assert!(!target_raws.is_empty());
149
150 let pre_idx = all_raws.iter().position(|x| x == &target_raws[0]).unwrap();
151 let post_idx = all_raws
152 .iter()
153 .position(|x| x == &target_raws[target_raws.len() - 1])
154 .unwrap()
155 + 1;
156
157 let mut pre_idx = pre_idx;
158 let mut post_idx = post_idx;
159
160 if sides == TargetSide::Both || sides == TargetSide::Before {
161 pre_idx -= 1;
162 for i in (0..=pre_idx).rev() {
163 if all_raws[i].is_code() {
164 pre_idx = i;
165 break;
166 }
167 }
168 }
169
170 if sides == TargetSide::Both || sides == TargetSide::After {
171 for (i, it) in all_raws.iter().enumerate().skip(post_idx) {
172 if it.is_code() {
173 post_idx = i;
174 break;
175 }
176 }
177 post_idx += 1;
178 }
179
180 let segments = &all_raws[pre_idx..post_idx];
181 ReflowSequence::from_raw_segments(segments.to_vec(), root_segment, config, None)
182 }
183
184 pub fn insert(
185 self,
186 insertion: ErasedSegment,
187 target: ErasedSegment,
188 pos: ReflowInsertPosition,
189 ) -> Self {
190 let target_idx = self.find_element_idx_with(&target);
191
192 let new_block = ReflowBlock::from_config(
193 insertion.clone(),
194 self.reflow_config,
195 self.depth_map.get_depth_info(&target),
196 );
197
198 if pos == ReflowInsertPosition::Before {
199 let mut new_elements = self.elements[..target_idx].to_vec();
200 new_elements.push(new_block.into());
201 new_elements.push(ReflowPoint::default().into());
202 new_elements.extend_from_slice(&self.elements[target_idx..]);
203
204 let new_lint_result = LintResult::new(
205 target.clone().into(),
206 vec![LintFix::create_before(target, vec![insertion])],
207 None,
208 None,
209 );
210
211 return ReflowSequence {
212 root_segment: self.root_segment,
213 elements: new_elements,
214 lint_results: vec![new_lint_result],
215 reflow_config: self.reflow_config,
216 depth_map: self.depth_map,
217 };
218 }
219
220 self
221 }
222
223 fn find_element_idx_with(&self, target: &ErasedSegment) -> usize {
224 self.elements
225 .iter()
226 .position(|elem| elem.segments().contains(target))
227 .unwrap_or_else(|| panic!("Target [{target:?}] not found in ReflowSequence."))
228 }
229
230 pub fn without(self, target: &ErasedSegment) -> ReflowSequence<'a, 'b> {
231 let removal_idx = self.find_element_idx_with(target);
232 if removal_idx == 0 || removal_idx == self.elements.len() - 1 {
233 panic!("Unexpected removal at one end of a ReflowSequence.");
234 }
235 if let ReflowElement::Point(_) = &self.elements[removal_idx] {
236 panic!("Not expected removal of whitespace in ReflowSequence.");
237 }
238 let merged_point = ReflowPoint::new(
239 [
240 self.elements[removal_idx - 1].segments(),
241 self.elements[removal_idx + 1].segments(),
242 ]
243 .concat(),
244 );
245 let mut new_elements = self.elements[..removal_idx - 1].to_vec();
246 new_elements.push(ReflowElement::Point(merged_point));
247 new_elements.extend_from_slice(&self.elements[removal_idx + 2..]);
248
249 ReflowSequence {
250 elements: new_elements,
251 root_segment: self.root_segment,
252 lint_results: vec![LintResult::new(
253 target.clone().into(),
254 vec![LintFix::delete(target.clone())],
255 None,
256 None,
257 )],
258 reflow_config: self.reflow_config,
259 depth_map: self.depth_map,
260 }
261 }
262
263 pub fn respace(mut self, tables: &Tables, strip_newlines: bool, filter: Filter) -> Self {
264 let mut lint_results = take(&mut self.lint_results);
265 let mut new_elements = Vec::new();
266
267 for (point, pre, post) in self.iter_points_with_constraints() {
268 let lint_results_len = lint_results.len();
269 let (mut new_lint_results, mut new_point) = point.respace_point(
270 tables,
271 pre,
272 post,
273 self.root_segment,
274 lint_results,
275 strip_newlines,
276 "before",
277 );
278
279 let ignore = if new_point
280 .segments()
281 .iter()
282 .any(|seg| seg.is_type(SyntaxKind::Newline))
283 || post
284 .as_ref()
285 .is_some_and(|p| p.class_types().contains(SyntaxKind::EndOfFile))
286 {
287 filter == Filter::Inline
288 } else {
289 filter == Filter::Newline
290 };
291
292 if ignore {
293 new_point = point.clone();
294 new_lint_results.truncate(lint_results_len);
295 }
296
297 lint_results = new_lint_results;
298
299 if let Some(pre_value) = pre
300 && (new_elements.is_empty() || new_elements.last().unwrap() != pre_value)
301 {
302 new_elements.push(pre_value.clone().into());
303 }
304
305 new_elements.push(new_point.into());
306
307 if let Some(post) = post {
308 new_elements.push(post.clone().into());
309 }
310 }
311
312 self.elements = new_elements;
313 self.lint_results = lint_results;
314
315 self
316 }
317
318 pub fn rebreak(self, tables: &Tables, rebreak_type: RebreakType) -> Self {
319 if !self.lint_results.is_empty() {
320 panic!("rebreak cannot currently handle pre-existing embodied fixes");
321 }
322
323 let (elem_buff, lint_results) = if rebreak_type == RebreakType::Lines {
324 rebreak_sequence(tables, self.elements, self.root_segment)
325 } else {
326 rebreak_keywords_sequence(tables, self.elements, self.root_segment)
327 };
328
329 ReflowSequence {
330 root_segment: self.root_segment,
331 elements: elem_buff,
332 lint_results,
333 reflow_config: self.reflow_config,
334 depth_map: self.depth_map,
335 }
336 }
337
338 pub fn replace(mut self, target: ErasedSegment, edit: &[ErasedSegment]) -> Self {
340 let target_raws = target.get_raw_segments();
341
342 let mut edit_raws: Vec<ErasedSegment> = Vec::new();
343
344 for seg in edit {
345 edit_raws.extend_from_slice(&seg.get_raw_segments());
346 }
347
348 let trim_amount = target.path_to(&target_raws[0]).len();
349
350 for edit_raw in &edit_raws {
351 self.depth_map.copy_depth_info(
352 &target_raws[0],
353 edit_raw,
354 trim_amount.try_into().unwrap(),
355 );
356 }
357
358 let current_raws: Vec<ErasedSegment> = self
359 .elements
360 .iter()
361 .flat_map(|elem| elem.segments().iter().cloned())
362 .collect();
363
364 let start_idx = current_raws
365 .iter()
366 .position(|s| *s == target_raws[0])
367 .unwrap();
368 let last_idx = current_raws
369 .iter()
370 .position(|s| *s == *target_raws.last().unwrap())
371 .unwrap();
372
373 let new_elements = Self::elements_from_raw_segments(
374 current_raws[..start_idx]
375 .iter()
376 .chain(edit_raws.iter())
377 .chain(current_raws[last_idx + 1..].iter())
378 .cloned()
379 .collect(),
380 &self.depth_map,
381 self.reflow_config,
382 );
383
384 ReflowSequence {
385 elements: new_elements,
386 root_segment: self.root_segment,
387 reflow_config: self.reflow_config,
388 depth_map: self.depth_map,
389 lint_results: vec![LintResult::new(
390 target.clone().into(),
391 vec![LintFix::replace(target.clone(), edit.to_vec(), None)],
392 None,
393 None,
394 )],
395 }
396 }
397
398 pub fn reindent(self, tables: &Tables) -> Self {
399 if !self.lint_results.is_empty() {
400 panic!("reindent cannot currently handle pre-existing embodied fixes");
401 }
402
403 let single_indent = construct_single_indent(self.reflow_config.indent_unit);
404
405 let (elements, indent_results) = lint_indent_points(
406 tables,
407 self.elements,
408 &single_indent,
409 <_>::default(),
410 self.reflow_config.allow_implicit_indents,
411 );
412
413 Self {
414 root_segment: self.root_segment,
415 elements,
416 lint_results: indent_results,
417 reflow_config: self.reflow_config,
418 depth_map: self.depth_map,
419 }
420 }
421
422 pub fn break_long_lines(self, tables: &Tables) -> Self {
423 if !self.lint_results.is_empty() {
424 panic!("break_long_lines cannot currently handle pre-existing embodied fixes");
425 }
426
427 let single_indent = construct_single_indent(self.reflow_config.indent_unit);
428
429 let (elements, length_results) = lint_line_length(
430 tables,
431 &self.elements,
432 self.root_segment,
433 &single_indent,
434 self.reflow_config.max_line_length,
435 self.reflow_config.allow_implicit_indents,
436 self.reflow_config.trailing_comments,
437 );
438
439 ReflowSequence {
440 root_segment: self.root_segment,
441 elements,
442 lint_results: length_results,
443 reflow_config: self.reflow_config,
444 depth_map: self.depth_map,
445 }
446 }
447
448 fn iter_points_with_constraints(
449 &self,
450 ) -> impl Iterator<Item = (&ReflowPoint, Option<&ReflowBlock>, Option<&ReflowBlock>)> + '_ {
451 self.elements.iter().enumerate().filter_map(|(idx, elem)| {
452 let point = elem.as_point()?;
453 let mut pre = None;
454 let mut post = None;
455
456 if idx > 0 {
457 pre = Some(self.elements[idx - 1].as_block().unwrap());
458 }
459
460 if idx < self.elements.len() - 1 {
461 post = Some(self.elements[idx + 1].as_block().unwrap());
462 }
463
464 (point, pre, post).into()
465 })
466 }
467
468 pub fn elements(&self) -> &[ReflowElement] {
469 &self.elements
470 }
471}
472
473#[derive(Clone, Copy, PartialEq, Eq)]
474pub enum Filter {
475 All,
476 Inline,
477 Newline,
478}