1use std::cmp::PartialEq;
2use std::str::FromStr;
3
4use sqruff_lib_core::dialects::syntax::SyntaxKind;
5use sqruff_lib_core::helpers::capitalize;
6use sqruff_lib_core::lint_fix::LintFix;
7use sqruff_lib_core::parser::segments::{ErasedSegment, Tables};
8use strum_macros::{AsRefStr, EnumString};
9
10use super::elements::{ReflowElement, ReflowSequenceType};
11use crate::core::rules::LintResult;
12use crate::utils::reflow::depth_map::StackPositionType;
13use crate::utils::reflow::elements::ReflowPoint;
14use crate::utils::reflow::helpers::{deduce_line_indent, fixes_from_results, pretty_segment_name};
15
16#[derive(Debug)]
17pub struct RebreakSpan {
18 pub(crate) target: ErasedSegment,
19 pub(crate) start_idx: usize,
20 pub(crate) end_idx: usize,
21 pub(crate) line_position: LinePosition,
22 pub(crate) strict: bool,
23}
24
25#[derive(Debug)]
26pub struct RebreakIndices {
27 _dir: i32,
28 adj_pt_idx: isize,
29 newline_pt_idx: isize,
30 pre_code_pt_idx: isize,
31}
32
33impl RebreakIndices {
34 fn from_elements(elements: &ReflowSequenceType, start_idx: usize, dir: i32) -> Option<Self> {
35 assert!(dir == 1 || dir == -1);
36 let limit = if dir == -1 { 0 } else { elements.len() };
37 let adj_point_idx = start_idx as isize + dir as isize;
38
39 if adj_point_idx < 0 || adj_point_idx >= elements.len() as isize {
40 return None;
41 }
42
43 let mut newline_point_idx = adj_point_idx;
44 while (dir == 1 && newline_point_idx < limit as isize)
45 || (dir == -1 && newline_point_idx >= 0)
46 {
47 let adjacent_idx = newline_point_idx + dir as isize;
52 if adjacent_idx < 0 || adjacent_idx >= elements.len() as isize {
53 break;
54 }
55 if elements[newline_point_idx as usize]
56 .class_types()
57 .contains(SyntaxKind::Newline)
58 || elements[adjacent_idx as usize]
59 .segments()
60 .iter()
61 .any(|seg| seg.is_code())
62 {
63 break;
64 }
65 newline_point_idx += 2 * dir as isize;
66 }
67
68 if newline_point_idx < 0 || newline_point_idx >= elements.len() as isize {
71 newline_point_idx = adj_point_idx;
72 }
73
74 let mut pre_code_point_idx = newline_point_idx;
75 while (dir == 1 && pre_code_point_idx < limit as isize)
76 || (dir == -1 && pre_code_point_idx >= 0)
77 {
78 let adjacent_idx = pre_code_point_idx + dir as isize;
80 if adjacent_idx < 0 || adjacent_idx >= elements.len() as isize {
81 break;
82 }
83 if elements[adjacent_idx as usize]
84 .segments()
85 .iter()
86 .any(|seg| seg.is_code())
87 {
88 break;
89 }
90 pre_code_point_idx += 2 * dir as isize;
91 }
92
93 if pre_code_point_idx < 0 || pre_code_point_idx >= elements.len() as isize {
95 pre_code_point_idx = newline_point_idx;
96 }
97
98 RebreakIndices {
99 _dir: dir,
100 adj_pt_idx: adj_point_idx,
101 newline_pt_idx: newline_point_idx,
102 pre_code_pt_idx: pre_code_point_idx,
103 }
104 .into()
105 }
106}
107
108#[derive(Debug)]
109pub struct RebreakLocation {
110 target: ErasedSegment,
111 prev: RebreakIndices,
112 next: RebreakIndices,
113 line_position: LinePosition,
114 strict: bool,
115}
116
117#[derive(Debug, PartialEq, Eq, Clone, Copy, AsRefStr, EnumString)]
118#[strum(serialize_all = "lowercase")]
119pub enum LinePosition {
120 Leading,
121 Trailing,
122 Alone,
123 Strict,
124}
125
126impl RebreakLocation {
127 pub fn from_span(span: RebreakSpan, elements: &ReflowSequenceType) -> Option<Self> {
129 Self {
130 target: span.target,
131 prev: RebreakIndices::from_elements(elements, span.start_idx, -1)?,
132 next: RebreakIndices::from_elements(elements, span.end_idx, 1)?,
133 line_position: span.line_position,
134 strict: span.strict,
135 }
136 .into()
137 }
138
139 fn has_inappropriate_newlines(&self, elements: &ReflowSequenceType, strict: bool) -> bool {
140 let n_prev_newlines = elements[self.prev.newline_pt_idx as usize].num_newlines();
141 let n_next_newlines = elements[self.next.newline_pt_idx as usize].num_newlines();
142
143 let newlines_on_neither_side = n_prev_newlines + n_next_newlines == 0;
144 let newlines_on_both_sides = n_prev_newlines > 0 && n_next_newlines > 0;
145
146 (newlines_on_neither_side && !strict) || newlines_on_both_sides
147 }
148
149 fn pretty_target_name(&self) -> String {
150 pretty_segment_name(&self.target)
151 }
152}
153
154pub fn identify_rebreak_spans(
155 element_buffer: &ReflowSequenceType,
156 root_segment: &ErasedSegment,
157) -> Vec<RebreakSpan> {
158 let mut spans = Vec::new();
159
160 for (idx, elem) in element_buffer
161 .iter()
162 .enumerate()
163 .take(element_buffer.len() - 2)
164 .skip(2)
165 {
166 let ReflowElement::Block(block) = elem else {
167 continue;
168 };
169
170 if let Some(original_line_position) = block.line_position() {
171 let Some(pos_marker) = elem
172 .segments()
173 .first()
174 .and_then(|seg| seg.get_position_marker())
175 else {
176 continue;
177 };
178 if !pos_marker.is_literal() {
179 continue;
180 }
181
182 spans.push(RebreakSpan {
183 target: elem.segments().first().cloned().unwrap(),
184 start_idx: idx,
185 end_idx: idx,
186 line_position: original_line_position.position(),
187 strict: original_line_position.is_strict(),
188 });
189 }
190
191 for key in block.line_position_configs().keys() {
192 let mut final_idx = None;
193 if block.depth_info().stack_positions[key].idx != 0 {
194 continue;
195 }
196
197 for (end_idx, end_elem) in element_buffer.iter().enumerate().skip(idx) {
198 let ReflowElement::Block(end_block) = end_elem else {
199 continue;
200 };
201
202 if !end_block.depth_info().stack_positions.contains_key(key) {
203 if final_idx.is_none() {
205 final_idx = (end_idx - 2).into();
206 }
207 break;
208 } else if matches!(
209 end_block.depth_info().stack_positions[key].type_,
210 Some(StackPositionType::End) | Some(StackPositionType::Solo)
211 ) {
212 final_idx = end_idx.into();
216 }
217 }
218
219 if let Some(final_idx) = final_idx {
220 let target_depth = block
221 .depth_info()
222 .stack_hashes
223 .iter()
224 .position(|it| it == key)
225 .unwrap();
226 let target = root_segment.path_to(&element_buffer[idx].segments()[0])[target_depth]
227 .segment
228 .clone();
229
230 let line_position_config = block.line_position_configs()[key];
231
232 spans.push(RebreakSpan {
233 target,
234 start_idx: idx,
235 end_idx: final_idx,
236 line_position: line_position_config.position(),
237 strict: line_position_config.is_strict(),
238 });
239 }
240 }
241 }
242
243 spans
244}
245
246pub fn identify_keyword_rebreak_spans(element_buffer: &ReflowSequenceType) -> Vec<RebreakSpan> {
247 let mut spans = Vec::new();
248
249 for idx in 2..element_buffer.len().saturating_sub(2) {
250 let ReflowElement::Block(block) = &element_buffer[idx] else {
251 continue;
252 };
253
254 for key in block.keyword_line_position_configs().keys() {
255 let line_position_config = &block.keyword_line_position_configs()[key];
256 if line_position_config.eq_ignore_ascii_case("none") {
257 continue;
258 }
259
260 if block.depth_info().stack_positions[key].idx > 1 {
261 continue;
262 }
263
264 let configured_depth = block
265 .depth_info()
266 .stack_hashes
267 .iter()
268 .position(|it| it == key)
269 .unwrap();
270 if block.depth_info().stack_depth > configured_depth + 1 {
271 continue;
272 }
273
274 if element_buffer[idx].segments().is_empty()
275 || !element_buffer[idx].segments()[0].is_type(SyntaxKind::Keyword)
276 {
277 continue;
278 }
279
280 for end_idx in idx..element_buffer.len() {
281 let end_elem = &element_buffer[end_idx];
282 let final_idx = match end_elem {
283 ReflowElement::Point(point)
284 if point.segments().iter().any(|seg| seg.is_indent()) =>
285 {
286 Some(end_idx - 1)
287 }
288 ReflowElement::Point(_) => continue,
289 ReflowElement::Block(end_block)
290 if matches!(
291 end_block.depth_info().stack_positions[key].type_,
292 Some(StackPositionType::End) | Some(StackPositionType::Solo)
293 ) =>
294 {
295 Some(end_idx)
296 }
297 ReflowElement::Block(_) => continue,
298 };
299
300 if let Some(final_idx) = final_idx {
301 let parent_exclusion = block
302 .keyword_line_position_exclusions_configs()
303 .get(key)
304 .cloned()
305 .unwrap_or_default();
306 if block
307 .depth_info()
308 .stack_class_types
309 .iter()
310 .any(|class_types| class_types.intersects(&parent_exclusion))
311 {
312 break;
313 }
314
315 let line_position =
316 LinePosition::from_str(line_position_config.split(':').next().unwrap())
317 .unwrap();
318
319 spans.push(RebreakSpan {
320 target: element_buffer[idx].segments()[0].clone(),
321 start_idx: idx,
322 end_idx: final_idx,
323 line_position,
324 strict: line_position_config.ends_with("strict"),
325 });
326 break;
327 }
328 }
329 }
330 }
331
332 spans
333}
334
335fn locations_from_spans(
336 spans: Vec<RebreakSpan>,
337 elements: &ReflowSequenceType,
338) -> Vec<RebreakLocation> {
339 spans
340 .into_iter()
341 .filter_map(|span| RebreakLocation::from_span(span, elements))
342 .collect()
343}
344
345pub fn rebreak_sequence(
346 tables: &Tables,
347 elements: ReflowSequenceType,
348 root_segment: &ErasedSegment,
349) -> (ReflowSequenceType, Vec<LintResult>) {
350 let mut lint_results = Vec::new();
351 let mut fixes = Vec::new();
352 let mut elem_buff = elements.clone();
353
354 let spans = identify_rebreak_spans(&elem_buff, root_segment);
363 let locations = locations_from_spans(spans, &elements);
364
365 for loc in locations {
367 if loc.has_inappropriate_newlines(&elements, loc.strict) {
368 continue;
369 }
370
371 let prev_point = elem_buff[loc.prev.adj_pt_idx as usize]
377 .as_point()
378 .unwrap()
379 .clone();
380 let next_point = elem_buff[loc.next.adj_pt_idx as usize]
381 .as_point()
382 .unwrap()
383 .clone();
384
385 let new_results = if loc.line_position == LinePosition::Leading {
387 if elem_buff[loc.prev.newline_pt_idx as usize].num_newlines() != 0 {
388 continue;
390 }
391
392 let pretty_name = loc.pretty_target_name();
394 let _desc = if loc.strict {
395 format!(
396 "{} should always start a new line.",
397 capitalize(&pretty_name)
398 )
399 } else {
400 format!("Found trailing {pretty_name}. Expected only leading near line breaks.")
401 };
402
403 if loc.next.adj_pt_idx == loc.next.pre_code_pt_idx
404 && elem_buff[loc.next.newline_pt_idx as usize].num_newlines() == 1
405 {
406 let desired_indent = next_point.get_indent().unwrap_or_default();
411
412 let (new_results, prev_point) = prev_point.indent_to(
413 tables,
414 &desired_indent,
415 None,
416 loc.target.clone().into(),
417 None,
418 None,
419 );
420
421 let (new_results, next_point) = next_point.respace_point(
422 tables,
423 elem_buff[loc.next.adj_pt_idx as usize - 1].as_block(),
424 elem_buff[loc.next.adj_pt_idx as usize + 1].as_block(),
425 root_segment,
426 new_results,
427 true,
428 "before",
429 );
430
431 elem_buff[loc.prev.adj_pt_idx as usize] = prev_point.into();
433 elem_buff[loc.next.adj_pt_idx as usize] = next_point.into();
434
435 new_results
436 } else {
437 fixes.push(LintFix::delete(loc.target.clone()));
438 for seg in elem_buff[loc.prev.adj_pt_idx as usize].segments() {
439 fixes.push(LintFix::delete(seg.clone()));
440 }
441
442 let (new_results, new_point) = ReflowPoint::new(Vec::new()).respace_point(
443 tables,
444 elem_buff[(loc.next.adj_pt_idx - 1) as usize].as_block(),
445 elem_buff[(loc.next.pre_code_pt_idx + 1) as usize].as_block(),
446 root_segment,
447 Vec::new(),
448 false,
449 "after",
450 );
451
452 let mut create_anchor = None;
453 for i in 0..loc.next.pre_code_pt_idx {
454 let idx = loc.next.pre_code_pt_idx - i;
455 if let Some(elem) = elem_buff.get(idx as usize)
456 && let Some(segments) = elem.segments().last()
457 {
458 create_anchor = Some(segments.clone());
459 break;
460 }
461 }
462
463 if create_anchor.is_none() {
464 panic!("Could not find anchor for creation.");
465 }
466
467 fixes.push(LintFix::create_after(
468 create_anchor.unwrap(),
469 vec![loc.target.clone()],
470 None,
471 ));
472
473 rearrange_and_insert(&mut elem_buff, &loc, new_point);
474
475 new_results
476 }
477 } else if loc.line_position == LinePosition::Trailing {
478 if elem_buff[loc.next.newline_pt_idx as usize].num_newlines() != 0 {
479 continue;
480 }
481
482 let pretty_name = loc.pretty_target_name();
483 let _desc = if loc.strict {
484 format!(
485 "{} should always be at the end of a line.",
486 capitalize(&pretty_name)
487 )
488 } else {
489 format!("Found leading {pretty_name}. Expected only trailing near line breaks.")
490 };
491
492 if loc.prev.adj_pt_idx == loc.prev.pre_code_pt_idx
493 && elem_buff[loc.prev.newline_pt_idx as usize].num_newlines() == 1
494 {
495 let (new_results, next_point) = next_point.indent_to(
496 tables,
497 prev_point.get_indent().as_deref().unwrap_or_default(),
498 Some(loc.target.clone()),
499 None,
500 None,
501 None,
502 );
503
504 let (new_results, prev_point) = prev_point.respace_point(
505 tables,
506 elem_buff[loc.prev.adj_pt_idx as usize - 1].as_block(),
507 elem_buff[loc.prev.adj_pt_idx as usize + 1].as_block(),
508 root_segment,
509 new_results,
510 true,
511 "before",
512 );
513
514 elem_buff[loc.prev.adj_pt_idx as usize] = prev_point.into();
516 elem_buff[loc.next.adj_pt_idx as usize] = next_point.into();
517
518 new_results
519 } else {
520 fixes.push(LintFix::delete(loc.target.clone()));
521 for seg in elem_buff[loc.next.adj_pt_idx as usize].segments() {
522 fixes.push(LintFix::delete(seg.clone()));
523 }
524
525 let (new_results, new_point) = ReflowPoint::new(Vec::new()).respace_point(
526 tables,
527 elem_buff[(loc.prev.pre_code_pt_idx - 1) as usize].as_block(),
528 elem_buff[(loc.prev.adj_pt_idx + 1) as usize].as_block(),
529 root_segment,
530 Vec::new(),
531 false,
532 "before",
533 );
534
535 fixes.push(LintFix::create_before(
536 elem_buff[loc.prev.pre_code_pt_idx as usize].segments()[0].clone(),
537 vec![loc.target.clone()],
538 ));
539
540 reorder_and_insert(&mut elem_buff, &loc, new_point);
541
542 new_results
543 }
544 } else if loc.line_position == LinePosition::Alone {
545 let mut new_results = Vec::new();
546
547 let needs_next_newline =
548 elem_buff[loc.next.newline_pt_idx as usize].num_newlines() == 0;
549 let needs_prev_newline = elem_buff[loc.prev.adj_pt_idx as usize].num_newlines() == 0;
550
551 let next_code_idx = (loc.next.pre_code_pt_idx + 1) as usize;
555 let next_is_statement_terminator = next_code_idx < elem_buff.len()
556 && elem_buff[next_code_idx]
557 .segments()
558 .iter()
559 .any(|seg| seg.get_type() == SyntaxKind::StatementTerminator);
560
561 let skip_next_newline = needs_next_newline && next_is_statement_terminator;
562
563 if (!needs_next_newline || skip_next_newline) && !needs_prev_newline {
564 continue;
565 }
566
567 if needs_next_newline && !skip_next_newline {
568 let (results, next_point) = next_point.indent_to(
569 tables,
570 &deduce_line_indent(
571 loc.target.get_raw_segments().last().unwrap(),
572 root_segment,
573 ),
574 loc.target.clone().into(),
575 None,
576 None,
577 None,
578 );
579
580 new_results = results;
581 elem_buff[loc.next.adj_pt_idx as usize] = next_point.into();
582 }
583
584 if needs_prev_newline {
585 let (results, prev_point) = prev_point.indent_to(
586 tables,
587 &deduce_line_indent(
588 loc.target.get_raw_segments().first().unwrap(),
589 root_segment,
590 ),
591 None,
592 loc.target.clone().into(),
593 None,
594 None,
595 );
596
597 new_results = results;
598 elem_buff[loc.prev.adj_pt_idx as usize] = prev_point.into();
599 }
600
601 new_results
602 } else {
603 unimplemented!(
604 "Unexpected line_position config: {}",
605 loc.line_position.as_ref()
606 )
607 };
608
609 let fixes = fixes_from_results(new_results.into_iter())
610 .chain(std::mem::take(&mut fixes))
611 .collect();
612 lint_results.push(LintResult::new(
613 loc.target.clone().into(),
614 fixes,
615 None,
616 None,
617 ));
618 }
619
620 (elem_buff, lint_results)
621}
622
623pub fn rebreak_keywords_sequence(
624 tables: &Tables,
625 elements: ReflowSequenceType,
626 root_segment: &ErasedSegment,
627) -> (ReflowSequenceType, Vec<LintResult>) {
628 let mut lint_results = Vec::new();
629 let mut fixes = Vec::new();
630 let mut elem_buff = elements.clone();
631
632 let spans = identify_keyword_rebreak_spans(&elem_buff);
633 let locations = locations_from_spans(spans, &elements);
634
635 for loc in locations {
636 if loc.has_inappropriate_newlines(&elem_buff, true) {
637 continue;
638 }
639
640 let prev_point = elem_buff[loc.prev.adj_pt_idx as usize]
641 .as_point()
642 .unwrap()
643 .clone();
644 let next_point = elem_buff[loc.next.adj_pt_idx as usize]
645 .as_point()
646 .unwrap()
647 .clone();
648
649 let (desc, new_results) = if loc.line_position == LinePosition::Leading {
650 if elem_buff[loc.prev.newline_pt_idx as usize].num_newlines() != 0 {
651 continue;
652 }
653
654 let pretty_name = loc.pretty_target_name();
655 let desc = format!("The {pretty_name} should always start a new line.");
656
657 let (new_results, prev_point) = prev_point.indent_to(
658 tables,
659 next_point.get_indent().as_deref().unwrap_or_default(),
660 None,
661 elem_buff[loc.prev.adj_pt_idx as usize + 1]
662 .segments()
663 .first()
664 .cloned(),
665 None,
666 None,
667 );
668 let (new_results, next_point) = next_point.respace_point(
669 tables,
670 elem_buff[loc.next.adj_pt_idx as usize - 1].as_block(),
671 elem_buff[loc.next.adj_pt_idx as usize + 1].as_block(),
672 root_segment,
673 new_results,
674 true,
675 "before",
676 );
677
678 elem_buff[loc.prev.adj_pt_idx as usize] = prev_point.into();
679 elem_buff[loc.next.adj_pt_idx as usize] = next_point.into();
680
681 (desc, new_results)
682 } else if loc.line_position == LinePosition::Trailing {
683 if elem_buff[loc.next.newline_pt_idx as usize].num_newlines() != 0 {
684 continue;
685 }
686
687 let pretty_name = loc.pretty_target_name();
688 let desc = format!("The {pretty_name} should always be at the end of a line.");
689
690 let (new_results, next_point) = next_point.indent_to(
691 tables,
692 prev_point.get_indent().as_deref().unwrap_or_default(),
693 Some(
694 elem_buff[loc.next.adj_pt_idx as usize - 1]
695 .segments()
696 .last()
697 .cloned()
698 .unwrap(),
699 ),
700 None,
701 None,
702 None,
703 );
704 let (new_results, prev_point) = prev_point.respace_point(
705 tables,
706 elem_buff[loc.prev.adj_pt_idx as usize - 1].as_block(),
707 elem_buff[loc.prev.adj_pt_idx as usize + 1].as_block(),
708 root_segment,
709 new_results,
710 true,
711 "before",
712 );
713
714 elem_buff[loc.prev.adj_pt_idx as usize] = prev_point.into();
715 elem_buff[loc.next.adj_pt_idx as usize] = next_point.into();
716
717 (desc, new_results)
718 } else if loc.line_position == LinePosition::Alone {
719 let pretty_name = loc.pretty_target_name();
720 let desc =
721 format!("The {pretty_name} should always have a line break both before and after.");
722 let mut new_results = Vec::new();
723
724 if elem_buff[loc.next.newline_pt_idx as usize].num_newlines() == 0 {
725 let (results, next_point) = next_point.indent_to(
726 tables,
727 prev_point.get_indent().as_deref().unwrap_or_default(),
728 Some(
729 elem_buff[loc.next.adj_pt_idx as usize - 1]
730 .segments()
731 .last()
732 .cloned()
733 .unwrap(),
734 ),
735 None,
736 None,
737 None,
738 );
739 new_results = results;
740 elem_buff[loc.next.adj_pt_idx as usize] = next_point.into();
741 }
742
743 if elem_buff[loc.prev.adj_pt_idx as usize].num_newlines() == 0 {
744 let (results, prev_point) = prev_point.indent_to(
745 tables,
746 next_point.get_indent().as_deref().unwrap_or_default(),
747 None,
748 elem_buff[loc.prev.adj_pt_idx as usize + 1]
749 .segments()
750 .first()
751 .cloned(),
752 None,
753 None,
754 );
755 new_results = results;
756 elem_buff[loc.prev.adj_pt_idx as usize] = prev_point.into();
757 }
758
759 (desc, new_results)
760 } else {
761 unimplemented!(
762 "Unexpected line_position config: {}",
763 loc.line_position.as_ref()
764 )
765 };
766
767 let fixes = fixes_from_results(new_results.into_iter())
768 .chain(std::mem::take(&mut fixes))
769 .collect();
770 lint_results.push(LintResult::new(
771 loc.target.clone().into(),
772 fixes,
773 Some(desc),
774 None,
775 ));
776 }
777
778 (elem_buff, lint_results)
779}
780
781fn rearrange_and_insert(
782 elem_buff: &mut Vec<ReflowElement>,
783 loc: &RebreakLocation,
784 new_point: ReflowPoint,
785) {
786 let mut new_buff = Vec::with_capacity(elem_buff.len() + 1);
787
788 new_buff.extend_from_slice(&elem_buff[..loc.prev.adj_pt_idx as usize]);
790
791 new_buff.extend_from_slice(
793 &elem_buff[loc.next.adj_pt_idx as usize..=loc.next.pre_code_pt_idx as usize],
794 );
795
796 if loc.prev.adj_pt_idx + 1 < loc.next.adj_pt_idx {
799 new_buff.extend_from_slice(
800 &elem_buff[loc.prev.adj_pt_idx as usize + 1..loc.next.adj_pt_idx as usize],
801 );
802 }
803
804 new_buff.push(new_point.into());
806
807 if loc.next.pre_code_pt_idx as usize + 1 < elem_buff.len() {
809 new_buff.extend_from_slice(&elem_buff[loc.next.pre_code_pt_idx as usize + 1..]);
810 }
811
812 *elem_buff = new_buff;
814}
815
816fn reorder_and_insert(
817 elem_buff: &mut Vec<ReflowElement>,
818 loc: &RebreakLocation,
819 new_point: ReflowPoint,
820) {
821 let mut new_buff = Vec::with_capacity(elem_buff.len() + 1);
822
823 new_buff.extend_from_slice(&elem_buff[..loc.prev.pre_code_pt_idx as usize]);
825
826 new_buff.push(new_point.into());
828
829 if loc.prev.adj_pt_idx + 1 < loc.next.adj_pt_idx {
832 new_buff.extend_from_slice(
833 &elem_buff[loc.prev.adj_pt_idx as usize + 1..loc.next.adj_pt_idx as usize],
834 );
835 }
836
837 new_buff.extend_from_slice(
840 &elem_buff[loc.prev.pre_code_pt_idx as usize..=loc.prev.adj_pt_idx as usize],
841 );
842
843 if loc.next.adj_pt_idx as usize + 1 < elem_buff.len() {
845 new_buff.extend_from_slice(&elem_buff[loc.next.adj_pt_idx as usize + 1..]);
846 }
847
848 *elem_buff = new_buff;
850}
851
852#[cfg(test)]
853mod tests {
854 use sqruff_lib::core::test_functions::parse_ansi_string;
855 use sqruff_lib_core::helpers::enter_panic;
856 use sqruff_lib_core::parser::segments::Tables;
857
858 use crate::utils::reflow::sequence::{RebreakType, ReflowSequence, TargetSide};
859
860 #[test]
861 fn test_reflow_sequence_rebreak_root() {
862 let cases = [
863 ("select 1", "select 1"),
865 ("select 1\n+2", "select 1\n+2"),
867 ("select 1+\n2", "select 1\n+ 2"), ("select\n 1 +\n 2", "select\n 1\n + 2"),
869 (
870 "select\n 1 +\n -- comment\n 2",
871 "select\n 1\n -- comment\n + 2",
872 ),
873 ("select a,b", "select a,b"),
875 ("select a\n,b", "select a,\nb"),
876 ("select\n a\n , b", "select\n a,\n b"),
877 ("select\n a\n , b", "select\n a,\n b"),
878 ("select\n a\n , b", "select\n a,\n b"),
879 (
880 "select\n a\n -- comment\n , b",
881 "select\n a,\n -- comment\n b",
882 ),
883 ];
884
885 let tables = Tables::default();
886 for (raw_sql_in, raw_sql_out) in cases {
887 let _panic = enter_panic(format!("{raw_sql_in:?}"));
888
889 let root = parse_ansi_string(raw_sql_in);
890 let config = <_>::default();
891 let seq = ReflowSequence::from_root(&root, &config);
892 let new_seq = seq.rebreak(&tables, RebreakType::Lines);
893
894 assert_eq!(new_seq.raw(), raw_sql_out);
895 }
896 }
897
898 #[test]
899 fn test_reflow_sequence_rebreak_target() {
900 let cases = [
901 ("select 1+\n(2+3)", 4, "1+\n(", "1\n+ ("),
902 ("select a,\n(b+c)", 4, "a,\n(", "a,\n("),
903 ("select a\n , (b+c)", 6, "a\n , (", "a,\n ("),
904 ("select a,\n(b+c)", 6, ",\n(b", ",\n(b"),
907 ("select a<=b", 4, "a<=", "a<="),
909 ];
910
911 let tables = Tables::default();
912 for (raw_sql_in, target_idx, seq_sql_in, seq_sql_out) in cases {
913 let root = parse_ansi_string(raw_sql_in);
914 let target = &root.get_raw_segments()[target_idx];
915 let config = <_>::default();
916 let seq = ReflowSequence::from_around_target(target, &root, TargetSide::Both, &config);
917
918 assert_eq!(seq.raw(), seq_sql_in);
919
920 let new_seq = seq.rebreak(&tables, RebreakType::Lines);
921 assert_eq!(new_seq.raw(), seq_sql_out);
922 }
923 }
924}