sqruff_lib/utils/reflow/
sequence.rs

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