Skip to main content

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