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::{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::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 [{target:?}] not found in ReflowSequence."))
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                && (new_elements.is_empty() || new_elements.last().unwrap() != pre_value)
294            {
295                new_elements.push(pre_value.clone().into());
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) = rebreak_sequence(tables, self.elements, &self.root_segment);
318
319        ReflowSequence {
320            root_segment: self.root_segment,
321            elements: elem_buff,
322            lint_results,
323            reflow_config: self.reflow_config,
324            depth_map: self.depth_map,
325        }
326    }
327
328    // https://github.com/sqlfluff/sqlfluff/blob/baceed9907908e055b79ca50ce6203bcd7949f39/src/sqlfluff/utils/reflow/sequence.py#L397
329    pub fn replace(mut self, target: ErasedSegment, edit: &[ErasedSegment]) -> Self {
330        let target_raws = target.get_raw_segments();
331
332        let mut edit_raws: Vec<ErasedSegment> = Vec::new();
333
334        for seg in edit {
335            edit_raws.extend_from_slice(&seg.get_raw_segments());
336        }
337
338        let trim_amount = target.path_to(&target_raws[0]).len();
339
340        for edit_raw in &edit_raws {
341            self.depth_map.copy_depth_info(
342                &target_raws[0],
343                edit_raw,
344                trim_amount.try_into().unwrap(),
345            );
346        }
347
348        let current_raws: Vec<ErasedSegment> = self
349            .elements
350            .iter()
351            .flat_map(|elem| elem.segments().iter().cloned())
352            .collect();
353
354        let start_idx = current_raws
355            .iter()
356            .position(|s| *s == target_raws[0])
357            .unwrap();
358        let last_idx = current_raws
359            .iter()
360            .position(|s| *s == *target_raws.last().unwrap())
361            .unwrap();
362
363        let new_elements = Self::elements_from_raw_segments(
364            current_raws[..start_idx]
365                .iter()
366                .chain(edit_raws.iter())
367                .chain(current_raws[last_idx + 1..].iter())
368                .cloned()
369                .collect(),
370            &self.depth_map,
371            self.reflow_config,
372        );
373
374        ReflowSequence {
375            elements: new_elements,
376            root_segment: self.root_segment,
377            reflow_config: self.reflow_config,
378            depth_map: self.depth_map,
379            lint_results: vec![LintResult::new(
380                target.clone().into(),
381                vec![LintFix::replace(target.clone(), edit.to_vec(), None)],
382                None,
383                None,
384            )],
385        }
386    }
387
388    pub fn reindent(self, tables: &Tables) -> Self {
389        if !self.lint_results.is_empty() {
390            panic!("reindent cannot currently handle pre-existing embodied fixes");
391        }
392
393        let single_indent = construct_single_indent(self.reflow_config.indent_unit);
394
395        let (elements, indent_results) = lint_indent_points(
396            tables,
397            self.elements,
398            &single_indent,
399            <_>::default(),
400            self.reflow_config.allow_implicit_indents,
401        );
402
403        Self {
404            root_segment: self.root_segment,
405            elements,
406            lint_results: indent_results,
407            reflow_config: self.reflow_config,
408            depth_map: self.depth_map,
409        }
410    }
411
412    pub fn break_long_lines(self, tables: &Tables) -> Self {
413        if !self.lint_results.is_empty() {
414            panic!("break_long_lines cannot currently handle pre-existing embodied fixes");
415        }
416
417        let single_indent = construct_single_indent(self.reflow_config.indent_unit);
418
419        let (elements, length_results) = lint_line_length(
420            tables,
421            &self.elements,
422            &self.root_segment,
423            &single_indent,
424            self.reflow_config.max_line_length,
425            self.reflow_config.allow_implicit_indents,
426            self.reflow_config.trailing_comments,
427        );
428
429        ReflowSequence {
430            root_segment: self.root_segment,
431            elements,
432            lint_results: length_results,
433            reflow_config: self.reflow_config,
434            depth_map: self.depth_map,
435        }
436    }
437
438    fn iter_points_with_constraints(
439        &self,
440    ) -> impl Iterator<Item = (&ReflowPoint, Option<&ReflowBlock>, Option<&ReflowBlock>)> + '_ {
441        self.elements.iter().enumerate().filter_map(|(idx, elem)| {
442            let point = elem.as_point()?;
443            let mut pre = None;
444            let mut post = None;
445
446            if idx > 0 {
447                pre = Some(self.elements[idx - 1].as_block().unwrap());
448            }
449
450            if idx < self.elements.len() - 1 {
451                post = Some(self.elements[idx + 1].as_block().unwrap());
452            }
453
454            (point, pre, post).into()
455        })
456    }
457
458    pub fn elements(&self) -> &[ReflowElement] {
459        &self.elements
460    }
461}
462
463#[derive(Clone, Copy, PartialEq, Eq)]
464pub enum Filter {
465    All,
466    Inline,
467    Newline,
468}