Skip to main content

sqruff_lib/utils/reflow/
elements.rs

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        // match segment.block_type.as_ref() {
28        //     SyntaxKind::Literal => Some(segment.source_str),
29        //     _ => None,
30        // }
31        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        // Get the indent (or in the case of no newline, the last whitespace)
194        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            // There isn't currently a newline.
307            let new_newline = SegmentBuilder::newline(tables.next_id(), "\n");
308            // Check for whitespace
309            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                    // Add elements before the specified index
348                    if idx > 0 {
349                        new_segments.extend_from_slice(&self.segments[..idx]);
350                    }
351
352                    // Add new segments
353                    new_segments.extend(new_segs);
354
355                    // Add remaining elements after the specified index
356                    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        // Leave spacing untouched when it lives entirely inside an unparsable
441        // section. Since we couldn't parse that section we don't understand its
442        // tokens, so reformatting it is unsafe - e.g. splitting `>=` into `> =`
443        // when the right-hand side fails to parse (issue #2624).
444        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        // The buffer is used to create the new reflow point to return
455        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        // Do we at least have _some_ whitespace?
541        let segment_buffer = if let Some(last_whitespace) = last_whitespace {
542            // We do - is it the right size?
543            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            // No. Should we insert some?
558            // NOTE: This method operates on the existing fix buffer.
559            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    /// True if the block contains only whitespace/indent/placeholder/loop.
680    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    /// Whether this block sits inside an unparsable section.
692    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}