1use std::cell::OnceCell;
2use std::iter::zip;
3use std::ops::Deref;
4use std::rc::Rc;
5
6use itertools::{Itertools, chain};
7use nohash_hasher::IntMap;
8use sqruff_lib_core::dialects::syntax::{SyntaxKind, SyntaxSet};
9use sqruff_lib_core::lint_fix::LintFix;
10use sqruff_lib_core::parser::segments::{ErasedSegment, SegmentBuilder, Tables};
11
12use super::config::{LinePositionConfig, ReflowConfig, Spacing};
13use super::depth_map::DepthInfo;
14use super::respace::determine_constraints;
15use crate::core::rules::LintResult;
16use crate::utils::reflow::rebreak::LinePosition;
17use crate::utils::reflow::respace::{
18 handle_respace_inline_with_space, handle_respace_inline_without_space, process_spacing,
19};
20
21fn get_consumed_whitespace(segment: Option<&ErasedSegment>) -> Option<String> {
22 let segment = segment?;
23
24 if segment.is_type(SyntaxKind::Placeholder) {
25 None
26 } else {
27 None
32 }
33}
34
35#[derive(Debug, Clone, Default, PartialEq)]
36pub struct ReflowPointData {
37 segments: Vec<ErasedSegment>,
38 stats: OnceCell<IndentStats>,
39 class_types: OnceCell<SyntaxSet>,
40}
41
42#[derive(Debug, Clone, Default, PartialEq)]
43pub struct ReflowPoint {
44 value: Rc<ReflowPointData>,
45}
46
47impl Deref for ReflowPoint {
48 type Target = ReflowPointData;
49
50 fn deref(&self) -> &Self::Target {
51 self.value.as_ref()
52 }
53}
54
55impl ReflowPoint {
56 pub fn new(segments: Vec<ErasedSegment>) -> Self {
57 Self {
58 value: Rc::new(ReflowPointData {
59 segments,
60 stats: OnceCell::new(),
61 class_types: OnceCell::new(),
62 }),
63 }
64 }
65
66 pub fn raw(&self) -> String {
67 self.segments.iter().map(|it| it.raw()).join("")
68 }
69
70 pub fn class_types(&self) -> &SyntaxSet {
71 self.class_types.get_or_init(|| {
72 self.segments
73 .iter()
74 .flat_map(|it| it.class_types())
75 .collect()
76 })
77 }
78
79 fn generate_indent_stats(segments: &[ErasedSegment]) -> IndentStats {
80 let mut trough = 0;
81 let mut running_sum = 0;
82 let mut implicit_indents = Vec::new();
83
84 for seg in segments {
85 if seg.is_indent() {
86 running_sum += seg.indent_val() as isize;
87
88 if seg.is_type(SyntaxKind::Implicit) {
89 implicit_indents.push(running_sum);
90 }
91 }
92
93 if running_sum < trough {
94 trough = running_sum
95 }
96 }
97
98 IndentStats {
99 impulse: running_sum,
100 trough,
101 implicit_indents: implicit_indents.into(),
102 }
103 }
104
105 pub fn get_indent_segment(&self) -> Option<&ErasedSegment> {
106 let mut indent = None;
107 for seg in self.segments.iter().rev() {
108 if seg
109 .get_position_marker()
110 .filter(|pos_marker| !pos_marker.is_literal())
111 .is_some()
112 {
113 continue;
114 }
115
116 match seg.get_type() {
117 SyntaxKind::Newline => return indent,
118 SyntaxKind::Whitespace => {
119 indent = Some(seg);
120 continue;
121 }
122 _ => {}
123 }
124
125 if get_consumed_whitespace(Some(seg))
126 .unwrap_or_default()
127 .contains('\n')
128 {
129 return Some(seg);
130 }
131 }
132 indent
133 }
134
135 pub(crate) fn get_indent_segment_vals(&self, exclude_block_indents: bool) -> Vec<isize> {
136 self.segments
137 .iter()
138 .filter(|seg| seg.is_type(SyntaxKind::Indent))
139 .filter(|seg| !(exclude_block_indents && seg.block_uuid().is_some()))
140 .map(|seg| seg.indent_val() as isize)
141 .collect()
142 }
143
144 pub(crate) fn num_newlines(&self) -> usize {
145 self.segments
146 .iter()
147 .map(|seg| {
148 let newline_in_class = seg.class_types().contains(SyntaxKind::Newline) as usize;
149
150 let consumed_whitespace = get_consumed_whitespace(seg.into()).unwrap_or_default();
151 newline_in_class + consumed_whitespace.matches('\n').count()
152 })
153 .sum()
154 }
155
156 pub fn get_indent(&self) -> Option<String> {
157 if self.num_newlines() == 0 {
158 return None;
159 }
160
161 let seg = self.get_indent_segment();
162 let consumed_whitespace = get_consumed_whitespace(seg);
163
164 if let Some(consumed_whitespace) = consumed_whitespace {
165 return consumed_whitespace
166 .split('\n')
167 .next_back()
168 .unwrap()
169 .to_owned()
170 .into();
171 }
172
173 if let Some(seg) = seg {
174 Some(seg.raw().to_string())
175 } else {
176 String::new().into()
177 }
178 }
179
180 pub fn indent_to(
181 &self,
182 tables: &Tables,
183 desired_indent: &str,
184 after: Option<ErasedSegment>,
185 before: Option<ErasedSegment>,
186 description: Option<&str>,
187 source: Option<&str>,
188 ) -> (Vec<LintResult>, ReflowPoint) {
189 assert!(
190 !desired_indent.contains('\n'),
191 "Newline found in desired indent."
192 );
193 let indent_seg = self.get_indent_segment();
195
196 if indent_seg
197 .as_ref()
198 .filter(|indent_seg| indent_seg.is_type(SyntaxKind::Placeholder))
199 .is_some()
200 {
201 unimplemented!()
202 } else if self.num_newlines() != 0 {
203 if let Some(indent_seg) = indent_seg {
204 if indent_seg.raw() == desired_indent {
205 unimplemented!()
206 } else if desired_indent.is_empty() {
207 let idx = self
208 .segments
209 .iter()
210 .position(|seg| seg == indent_seg)
211 .unwrap();
212 return (
213 vec![LintResult::new(
214 indent_seg.clone().into(),
215 vec![LintFix::delete(indent_seg.clone())],
216 Some(
217 description
218 .map_or_else(
219 || "Line should not be indented.".to_owned(),
220 ToOwned::to_owned,
221 )
222 .to_string(),
223 ),
224 source.map(|s| s.to_string()),
225 )],
226 ReflowPoint::new(
227 self.segments[..idx]
228 .iter()
229 .chain(self.segments[idx + 1..].iter())
230 .cloned()
231 .collect(),
232 ),
233 );
234 };
235
236 let new_indent =
237 indent_seg.edit(tables.next_id(), desired_indent.to_owned().into(), None);
238 let idx = self
239 .segments
240 .iter()
241 .position(|it| it == indent_seg)
242 .unwrap();
243
244 let description = format!("Expected {}.", indent_description(desired_indent));
245
246 let lint_result = LintResult::new(
247 indent_seg.clone().into(),
248 vec![LintFix::replace(
249 indent_seg.clone(),
250 vec![new_indent.clone()],
251 None,
252 )],
253 description.into(),
254 None,
255 );
256
257 let mut new_segments = Vec::new();
258 new_segments.extend_from_slice(&self.segments[..idx]);
259 new_segments.push(new_indent);
260 new_segments.extend_from_slice(&self.segments[idx + 1..]);
261
262 let new_reflow_point = ReflowPoint::new(new_segments);
263
264 (vec![lint_result], new_reflow_point)
265 } else {
266 if desired_indent.is_empty() {
267 return (Vec::new(), self.clone());
268 }
269
270 let new_indent = SegmentBuilder::whitespace(tables.next_id(), desired_indent);
271
272 let Some((last_newline_idx, last_newline)) =
273 self.segments.iter().enumerate().rev().find(|(_, it)| {
274 it.is_type(SyntaxKind::Newline)
275 && it
276 .get_position_marker()
277 .is_some_and(|marker| marker.is_literal())
278 })
279 else {
280 return (Vec::new(), self.clone());
281 };
282
283 let mut new_segments = self.segments[..=last_newline_idx].to_vec();
284 new_segments.push(new_indent.clone());
285 new_segments.extend_from_slice(&self.segments[last_newline_idx + 1..]);
286
287 (
288 vec![LintResult::new(
289 if let Some(before) = before {
290 before.into()
291 } else {
292 unimplemented!()
293 },
294 vec![LintFix::replace(
295 last_newline.clone(),
296 vec![last_newline.clone(), new_indent],
297 None,
298 )],
299 format!("Expected {}", indent_description(desired_indent)).into(),
300 None,
301 )],
302 ReflowPoint::new(new_segments),
303 )
304 }
305 } else {
306 let new_newline = SegmentBuilder::newline(tables.next_id(), "\n");
308 let ws_seg = self
310 .segments
311 .iter()
312 .find(|seg| seg.is_type(SyntaxKind::Whitespace));
313
314 if let Some(ws_seg) = ws_seg {
315 let new_segs = if desired_indent.is_empty() {
316 vec![new_newline]
317 } else {
318 vec![
319 new_newline,
320 ws_seg.edit(tables.next_id(), desired_indent.to_owned().into(), None),
321 ]
322 };
323 let idx = self.segments.iter().position(|it| ws_seg == it).unwrap();
324 let description = if let Some(before_seg) = before {
325 format!(
326 "Expected line break and {} before {:?}.",
327 indent_description(desired_indent),
328 before_seg.raw()
329 )
330 } else if let Some(after_seg) = after {
331 format!(
332 "Expected line break and {} after {:?}.",
333 indent_description(desired_indent),
334 after_seg.raw()
335 )
336 } else {
337 format!(
338 "Expected line break and {}.",
339 indent_description(desired_indent)
340 )
341 };
342
343 let fix = LintFix::replace(ws_seg.clone(), new_segs.clone(), None);
344 let new_point = ReflowPoint::new({
345 let mut new_segments = Vec::new();
346
347 if idx > 0 {
349 new_segments.extend_from_slice(&self.segments[..idx]);
350 }
351
352 new_segments.extend(new_segs);
354
355 if idx < self.segments.len() {
357 new_segments.extend_from_slice(&self.segments[idx + 1..]);
358 }
359
360 new_segments
361 });
362
363 (
364 vec![LintResult::new(
365 ws_seg.clone().into(),
366 vec![fix],
367 description.into(),
368 source.map(ToOwned::to_owned),
369 )],
370 new_point,
371 )
372 } else {
373 let new_indent = SegmentBuilder::whitespace(tables.next_id(), desired_indent);
374
375 if before.is_none() && after.is_none() {
376 unimplemented!(
377 "Not set up to handle empty points in this scenario without provided \
378 before/after anchor: {:?}",
379 self.segments
380 );
381 } else if let Some(before) = before {
382 let fix = LintFix::create_before(
383 before.clone(),
384 vec![new_newline.clone(), new_indent.clone()],
385 );
386
387 (
388 vec![LintResult::new(
389 before.clone().into(),
390 vec![fix],
391 Some(format!(
392 "Expected line break and {} before {:?}",
393 indent_description(desired_indent),
394 before.raw()
395 )),
396 source.map(ToOwned::to_owned),
397 )],
398 ReflowPoint::new(vec![new_newline, new_indent]),
399 )
400 } else {
401 let after = after.unwrap();
402 let fix = LintFix::create_after(
403 after.clone(),
404 vec![new_newline.clone(), new_indent.clone()],
405 None,
406 );
407 let description = format!(
408 "Expected line break and {} after {:?}.",
409 indent_description(desired_indent),
410 after.raw()
411 );
412
413 (
414 vec![LintResult::new(
415 Some(after),
416 vec![fix],
417 Some(description),
418 source.map(ToOwned::to_owned),
419 )],
420 ReflowPoint::new(vec![new_newline, new_indent]),
421 )
422 }
423 }
424 }
425 }
426
427 #[allow(clippy::too_many_arguments)]
428 pub fn respace_point(
429 &self,
430 tables: &Tables,
431 prev_block: Option<&ReflowBlock>,
432 next_block: Option<&ReflowBlock>,
433 root_segment: &ErasedSegment,
434 lint_results: Vec<LintResult>,
435 strip_newlines: bool,
436 anchor_on: &'static str,
437 ) -> (Vec<LintResult>, ReflowPoint) {
438 let mut existing_results = lint_results;
439
440 if let (Some(prev_block), Some(next_block)) = (prev_block, next_block)
445 && prev_block.within_unparsable()
446 && next_block.within_unparsable()
447 {
448 return (existing_results, self.clone());
449 }
450
451 let (pre_constraint, post_constraint, strip_newlines) =
452 determine_constraints(prev_block, next_block, strip_newlines);
453
454 let (mut segment_buffer, mut last_whitespace, mut new_results) =
456 process_spacing(&self.segments, strip_newlines);
457
458 if let Some((_, whitespace)) = next_block
459 .zip(last_whitespace.clone())
460 .filter(|(next_block, _)| next_block.class_types().contains(SyntaxKind::EndOfFile))
461 {
462 new_results.push(LintResult::new(
463 None,
464 vec![LintFix::delete(whitespace.clone())],
465 Some("Unnecessary trailing whitespace at end of file.".into()),
466 None,
467 ));
468
469 let pos = segment_buffer
470 .iter()
471 .position(|it| it == &whitespace)
472 .unwrap();
473 segment_buffer.remove(pos);
474
475 last_whitespace = None;
476 }
477
478 if segment_buffer
479 .iter()
480 .any(|seg| seg.is_type(SyntaxKind::Newline))
481 && !strip_newlines
482 || (next_block.is_some()
483 && next_block
484 .unwrap()
485 .class_types()
486 .contains(SyntaxKind::EndOfFile))
487 {
488 if let Some(last_whitespace) = last_whitespace {
489 let ws_idx = self
490 .segments
491 .iter()
492 .position(|it| it == &last_whitespace)
493 .unwrap();
494 if ws_idx > 0 {
495 let segments_slice = &self.segments[..ws_idx];
496
497 let prev_seg = segments_slice
498 .iter()
499 .rev()
500 .find(|seg| {
501 !matches!(seg.get_type(), SyntaxKind::Indent | SyntaxKind::Implicit)
502 })
503 .unwrap();
504
505 if prev_seg.is_type(SyntaxKind::Newline)
506 && prev_seg.get_end_loc() < last_whitespace.get_start_loc()
507 {
508 segment_buffer.remove(ws_idx);
509
510 let temp_idx = last_whitespace
511 .get_position_marker()
512 .unwrap()
513 .templated_slice
514 .start;
515
516 if let Some((index, _)) =
517 existing_results.iter().enumerate().find(|(_, res)| {
518 res.anchor
519 .as_ref()
520 .and_then(|a| a.get_position_marker())
521 .is_some_and(|pm| pm.templated_slice.end == temp_idx)
522 })
523 {
524 let mut res = existing_results.remove(index);
525
526 res.fixes.push(LintFix::delete(last_whitespace));
527 let new_result = LintResult::new(res.anchor, res.fixes, None, None);
528 new_results.push(new_result);
529 } else {
530 panic!("Could not find removal result.");
531 }
532 }
533 }
534 }
535
536 existing_results.extend(new_results);
537 return (existing_results, ReflowPoint::new(segment_buffer));
538 }
539
540 let segment_buffer = if let Some(last_whitespace) = last_whitespace {
542 let (segment_buffer, results) = handle_respace_inline_with_space(
544 tables,
545 pre_constraint,
546 post_constraint,
547 prev_block,
548 next_block,
549 root_segment,
550 segment_buffer,
551 last_whitespace,
552 );
553
554 new_results.extend(results);
555 segment_buffer
556 } else {
557 let (segment_buffer, results, _edited) = handle_respace_inline_without_space(
560 tables,
561 pre_constraint,
562 post_constraint,
563 prev_block,
564 next_block,
565 segment_buffer,
566 chain(existing_results, new_results).collect_vec(),
567 anchor_on,
568 );
569
570 existing_results = Vec::new();
571 new_results = results;
572
573 segment_buffer
574 };
575
576 existing_results.extend(new_results);
577 (existing_results, ReflowPoint::new(segment_buffer))
578 }
579
580 pub fn segments(&self) -> &[ErasedSegment] {
581 &self.segments
582 }
583
584 pub fn indent_impulse(&self) -> &IndentStats {
585 self.stats
586 .get_or_init(|| Self::generate_indent_stats(self.segments()))
587 }
588}
589
590fn indent_description(indent: &str) -> String {
591 match indent {
592 "" => "no indent".to_string(),
593 _ if indent.contains(' ') && indent.contains('\t') => "mixed indent".to_string(),
594 _ if indent.starts_with(' ') => {
595 assert!(indent.chars().all(|c| c == ' '));
596 format!("indent of {} spaces", indent.len())
597 }
598 _ if indent.starts_with('\t') => {
599 assert!(indent.chars().all(|c| c == '\t'));
600 format!("indent of {} tabs", indent.len())
601 }
602 _ => panic!("Invalid indent construction: {indent:?}"),
603 }
604}
605
606#[derive(Debug, Clone, Default, PartialEq)]
607pub struct IndentStats {
608 pub impulse: isize,
609 pub trough: isize,
610 pub implicit_indents: Rc<[isize]>,
611}
612
613impl IndentStats {
614 pub fn from_combination(first: Option<IndentStats>, second: &IndentStats) -> Self {
615 match first {
616 Some(first_stats) => IndentStats {
617 impulse: first_stats.impulse + second.impulse,
618 trough: std::cmp::min(first_stats.trough, first_stats.impulse + second.trough),
619 implicit_indents: second.implicit_indents.clone(),
620 },
621 None => second.clone(),
622 }
623 }
624}
625
626#[derive(Debug, PartialEq)]
627pub struct ReflowBlockData {
628 segment: ErasedSegment,
629 spacing_before: Spacing,
630 spacing_after: Spacing,
631 line_position: Option<LinePositionConfig>,
632 depth_info: DepthInfo,
633 stack_spacing_configs: IntMap<u64, Spacing>,
634 line_position_configs: IntMap<u64, LinePositionConfig>,
635 keyword_line_position: Option<Vec<LinePosition>>,
636 keyword_line_position_configs: IntMap<u64, String>,
637 keyword_line_position_exclusions: SyntaxSet,
638 keyword_line_position_exclusions_configs: IntMap<u64, SyntaxSet>,
639}
640
641#[derive(Debug, PartialEq, Clone)]
642pub struct ReflowBlock {
643 value: Rc<ReflowBlockData>,
644}
645
646impl Deref for ReflowBlock {
647 type Target = ReflowBlockData;
648
649 fn deref(&self) -> &Self::Target {
650 self.value.as_ref()
651 }
652}
653
654impl ReflowBlock {
655 pub fn segment(&self) -> &ErasedSegment {
656 &self.segment
657 }
658
659 pub fn spacing_before(&self) -> Spacing {
660 self.spacing_before
661 }
662
663 pub fn spacing_after(&self) -> Spacing {
664 self.spacing_after
665 }
666
667 pub fn line_position(&self) -> Option<LinePositionConfig> {
668 self.line_position
669 }
670
671 pub fn depth_info(&self) -> &DepthInfo {
672 &self.depth_info
673 }
674
675 pub fn class_types(&self) -> &SyntaxSet {
676 self.segment.class_types()
677 }
678
679 pub fn is_all_unrendered(&self) -> bool {
681 matches!(
682 self.segment.get_type(),
683 SyntaxKind::Whitespace
684 | SyntaxKind::Placeholder
685 | SyntaxKind::Newline
686 | SyntaxKind::Indent
687 | SyntaxKind::TemplateLoop
688 )
689 }
690
691 pub fn within_unparsable(&self) -> bool {
693 self.depth_info
694 .stack_class_types
695 .iter()
696 .any(|types| types.contains(SyntaxKind::Unparsable))
697 }
698
699 pub fn stack_spacing_configs(&self) -> &IntMap<u64, Spacing> {
700 &self.stack_spacing_configs
701 }
702
703 pub fn line_position_configs(&self) -> &IntMap<u64, LinePositionConfig> {
704 &self.line_position_configs
705 }
706
707 pub fn keyword_line_position(&self) -> Option<&[LinePosition]> {
708 self.keyword_line_position.as_deref()
709 }
710
711 pub fn keyword_line_position_configs(&self) -> &IntMap<u64, String> {
712 &self.keyword_line_position_configs
713 }
714
715 pub fn keyword_line_position_exclusions(&self) -> &SyntaxSet {
716 &self.keyword_line_position_exclusions
717 }
718
719 pub fn keyword_line_position_exclusions_configs(&self) -> &IntMap<u64, SyntaxSet> {
720 &self.keyword_line_position_exclusions_configs
721 }
722}
723
724impl ReflowBlock {
725 pub fn from_config(
726 segment: ErasedSegment,
727 config: &ReflowConfig,
728 depth_info: DepthInfo,
729 ) -> Self {
730 let block_config = config.get_block_config(segment.class_types(), Some(&depth_info));
731
732 let mut stack_spacing_configs = IntMap::default();
733 let mut line_position_configs = IntMap::default();
734 let mut keyword_line_position_configs = IntMap::default();
735 let mut keyword_line_position_exclusions_configs = IntMap::default();
736
737 for (hash, class_types) in zip(&depth_info.stack_hashes, &depth_info.stack_class_types) {
738 let cfg = config.get_block_config(class_types, None);
739
740 if let Some(spacing_within) = cfg.spacing_within {
741 stack_spacing_configs.insert(*hash, spacing_within);
742 }
743
744 if let Some(line_position) = cfg.line_position {
745 line_position_configs.insert(*hash, line_position);
746 }
747
748 if let Some(keyword_line_position) = cfg.keyword_line_position {
749 keyword_line_position_configs.insert(*hash, keyword_line_position);
750 }
751
752 if !cfg.keyword_line_position_exclusions.is_empty() {
753 keyword_line_position_exclusions_configs
754 .insert(*hash, cfg.keyword_line_position_exclusions);
755 }
756 }
757
758 let keyword_line_position = block_config
759 .keyword_line_position
760 .as_deref()
761 .map(parse_line_position_config);
762
763 Self {
764 value: Rc::new(ReflowBlockData {
765 segment,
766 spacing_before: block_config.spacing_before,
767 spacing_after: block_config.spacing_after,
768 line_position: block_config.line_position,
769 depth_info,
770 stack_spacing_configs,
771 line_position_configs,
772 keyword_line_position,
773 keyword_line_position_configs,
774 keyword_line_position_exclusions: block_config.keyword_line_position_exclusions,
775 keyword_line_position_exclusions_configs,
776 }),
777 }
778 }
779}
780
781fn parse_line_position_config(line_position: &str) -> Vec<LinePosition> {
782 line_position
783 .split(':')
784 .map(|it| it.parse().unwrap())
785 .collect()
786}
787
788impl From<ReflowBlock> for ReflowElement {
789 fn from(value: ReflowBlock) -> Self {
790 Self::Block(value)
791 }
792}
793
794impl From<ReflowPoint> for ReflowElement {
795 fn from(value: ReflowPoint) -> Self {
796 Self::Point(value)
797 }
798}
799
800#[derive(Debug, Clone, PartialEq)]
801pub enum ReflowElement {
802 Block(ReflowBlock),
803 Point(ReflowPoint),
804}
805
806impl ReflowElement {
807 pub fn raw(&self) -> String {
808 self.segments().iter().map(|it| it.raw()).join("")
809 }
810
811 pub fn segments(&self) -> &[ErasedSegment] {
812 match self {
813 ReflowElement::Block(block) => std::slice::from_ref(&block.segment),
814 ReflowElement::Point(point) => &point.segments,
815 }
816 }
817
818 pub fn class_types(&self) -> &SyntaxSet {
819 match self {
820 ReflowElement::Block(reflow_block) => reflow_block.class_types(),
821 ReflowElement::Point(reflow_point) => reflow_point.class_types(),
822 }
823 }
824
825 pub fn num_newlines(&self) -> usize {
826 self.segments()
827 .iter()
828 .map(|seg| {
829 let newline_in_class = seg.class_types().contains(SyntaxKind::Newline) as usize;
830
831 let consumed_whitespace = get_consumed_whitespace(seg.into()).unwrap_or_default();
832 newline_in_class + consumed_whitespace.matches('\n').count()
833 })
834 .sum()
835 }
836
837 pub fn as_point(&self) -> Option<&ReflowPoint> {
838 if let Self::Point(v) = self {
839 Some(v)
840 } else {
841 None
842 }
843 }
844
845 pub fn as_block(&self) -> Option<&ReflowBlock> {
846 if let Self::Block(v) = self {
847 Some(v)
848 } else {
849 None
850 }
851 }
852}
853
854impl PartialEq<ReflowBlock> for ReflowElement {
855 fn eq(&self, other: &ReflowBlock) -> bool {
856 match self {
857 ReflowElement::Block(this) => this == other,
858 ReflowElement::Point(_) => false,
859 }
860 }
861}
862
863pub type ReflowSequenceType = Vec<ReflowElement>;
864
865#[cfg(test)]
866mod tests {
867 use sqruff_lib_core::dialects::syntax::SyntaxKind;
868 use sqruff_lib_core::parser::markers::PositionMarker;
869 use sqruff_lib_core::parser::segments::{SegmentBuilder, Tables};
870 use sqruff_lib_core::templaters::{
871 RawFileSlice, TemplateSliceKind, TemplatedFile, TemplatedFileSlice,
872 };
873
874 use super::ReflowPoint;
875
876 #[test]
877 fn indent_to_leaves_non_literal_newline_unchanged() {
878 let templated_file = TemplatedFile::new(
879 "{{ source('connection', 'table') }}".to_string(),
880 "model.sql".to_string(),
881 Some("\n".to_string()),
882 Some(vec![TemplatedFileSlice::new(
883 TemplateSliceKind::Templated,
884 0..34,
885 0..1,
886 )]),
887 Some(vec![RawFileSlice::new(
888 "{{ source('connection', 'table') }}".to_string(),
889 TemplateSliceKind::Templated,
890 0,
891 None,
892 None,
893 )]),
894 )
895 .unwrap();
896 let marker = PositionMarker::new(0..34, 0..1, templated_file, Some(1), Some(1));
897 let newline = SegmentBuilder::token(0, "\n", SyntaxKind::Newline)
898 .with_position(marker)
899 .finish();
900 let point = ReflowPoint::new(vec![newline]);
901 let tables = Tables::default();
902
903 let (results, new_point) = point.indent_to(
904 &tables,
905 " ",
906 None,
907 Some(SegmentBuilder::keyword(1, "select")),
908 None,
909 None,
910 );
911
912 assert!(results.is_empty());
913 assert_eq!(new_point, point);
914 }
915
916 #[test]
917 fn indent_to_leaves_markerless_newline_unchanged() {
918 let newline = SegmentBuilder::newline(0, "\n");
919 let point = ReflowPoint::new(vec![newline]);
920 let tables = Tables::default();
921
922 let (results, new_point) = point.indent_to(
923 &tables,
924 " ",
925 None,
926 Some(SegmentBuilder::keyword(1, "select")),
927 None,
928 None,
929 );
930
931 assert!(results.is_empty());
932 assert_eq!(new_point, point);
933 }
934}