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::base::{ErasedSegment, SegmentBuilder, Tables};
11
12use super::config::{ReflowConfig, Spacing};
13use super::depth_map::DepthInfo;
14use super::respace::determine_constraints;
15use crate::core::rules::base::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.clone());
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.clone());
130 }
131 }
132 indent
133 }
134
135 pub(crate) fn num_newlines(&self) -> usize {
136 self.segments
137 .iter()
138 .map(|seg| {
139 let newline_in_class = seg.class_types().contains(SyntaxKind::Newline) as usize;
140
141 let consumed_whitespace = get_consumed_whitespace(seg.into()).unwrap_or_default();
142 newline_in_class + consumed_whitespace.matches('\n').count()
143 })
144 .sum()
145 }
146
147 pub fn get_indent(&self) -> Option<String> {
148 if self.num_newlines() == 0 {
149 return None;
150 }
151
152 let seg = self.get_indent_segment();
153 let consumed_whitespace = get_consumed_whitespace(seg.as_ref());
154
155 if let Some(consumed_whitespace) = consumed_whitespace {
156 return consumed_whitespace
157 .split('\n')
158 .next_back()
159 .unwrap()
160 .to_owned()
161 .into();
162 }
163
164 if let Some(seg) = seg {
165 Some(seg.raw().to_string())
166 } else {
167 String::new().into()
168 }
169 }
170
171 pub fn indent_to(
172 &self,
173 tables: &Tables,
174 desired_indent: &str,
175 after: Option<ErasedSegment>,
176 before: Option<ErasedSegment>,
177 description: Option<&str>,
178 source: Option<&str>,
179 ) -> (Vec<LintResult>, ReflowPoint) {
180 assert!(
181 !desired_indent.contains('\n'),
182 "Newline found in desired indent."
183 );
184 let indent_seg = self.get_indent_segment();
186
187 if indent_seg
188 .as_ref()
189 .filter(|indent_seg| indent_seg.is_type(SyntaxKind::Placeholder))
190 .is_some()
191 {
192 unimplemented!()
193 } else if self.num_newlines() != 0 {
194 if let Some(indent_seg) = indent_seg {
195 if indent_seg.raw() == desired_indent {
196 unimplemented!()
197 } else if desired_indent.is_empty() {
198 let idx = self
199 .segments
200 .iter()
201 .position(|seg| seg == &indent_seg)
202 .unwrap();
203 return (
204 vec![LintResult::new(
205 indent_seg.clone().into(),
206 vec![LintFix::delete(indent_seg.clone())],
207 Some(
208 description
209 .map_or_else(
210 || "Line should not be indented.".to_owned(),
211 ToOwned::to_owned,
212 )
213 .to_string(),
214 ),
215 source.map(|s| s.to_string()),
216 )],
217 ReflowPoint::new(
218 self.segments[..idx]
219 .iter()
220 .chain(self.segments[idx + 1..].iter())
221 .cloned()
222 .collect(),
223 ),
224 );
225 };
226
227 let new_indent =
228 indent_seg.edit(tables.next_id(), desired_indent.to_owned().into(), None);
229 let idx = self
230 .segments
231 .iter()
232 .position(|it| it == &indent_seg)
233 .unwrap();
234
235 let description = format!("Expected {}.", indent_description(desired_indent));
236
237 let lint_result = LintResult::new(
238 indent_seg.clone().into(),
239 vec![LintFix::replace(indent_seg, vec![new_indent.clone()], None)],
240 description.into(),
241 None,
242 );
243
244 let mut new_segments = Vec::new();
245 new_segments.extend_from_slice(&self.segments[..idx]);
246 new_segments.push(new_indent);
247 new_segments.extend_from_slice(&self.segments[idx + 1..]);
248
249 let new_reflow_point = ReflowPoint::new(new_segments);
250
251 (vec![lint_result], new_reflow_point)
252 } else {
253 if desired_indent.is_empty() {
254 return (Vec::new(), self.clone());
255 }
256
257 let new_indent = SegmentBuilder::whitespace(tables.next_id(), desired_indent);
258
259 let (last_newline_idx, last_newline) = self
260 .segments
261 .iter()
262 .enumerate()
263 .rev()
264 .find(|(_, it)| {
265 it.is_type(SyntaxKind::Newline)
266 && it.get_position_marker().unwrap().is_literal()
267 })
268 .unwrap();
269
270 let mut new_segments = self.segments[..=last_newline_idx].to_vec();
271 new_segments.push(new_indent.clone());
272 new_segments.extend_from_slice(&self.segments[last_newline_idx + 1..]);
273
274 return (
275 vec![LintResult::new(
276 if let Some(before) = before {
277 before.into()
278 } else {
279 unimplemented!()
280 },
281 vec![LintFix::replace(
282 last_newline.clone(),
283 vec![last_newline.clone(), new_indent],
284 None,
285 )],
286 format!("Expected {}", indent_description(desired_indent)).into(),
287 None,
288 )],
289 ReflowPoint::new(new_segments),
290 );
291 }
292 } else {
293 let new_newline = SegmentBuilder::newline(tables.next_id(), "\n");
295 let ws_seg = self
297 .segments
298 .iter()
299 .find(|seg| seg.is_type(SyntaxKind::Whitespace));
300
301 if let Some(ws_seg) = ws_seg {
302 let new_segs = if desired_indent.is_empty() {
303 vec![new_newline]
304 } else {
305 vec![
306 new_newline,
307 ws_seg.edit(tables.next_id(), desired_indent.to_owned().into(), None),
308 ]
309 };
310 let idx = self.segments.iter().position(|it| ws_seg == it).unwrap();
311 let description = if let Some(before_seg) = before {
312 format!(
313 "Expected line break and {} before {:?}.",
314 indent_description(desired_indent),
315 before_seg.raw()
316 )
317 } else if let Some(after_seg) = after {
318 format!(
319 "Expected line break and {} after {:?}.",
320 indent_description(desired_indent),
321 after_seg.raw()
322 )
323 } else {
324 format!(
325 "Expected line break and {}.",
326 indent_description(desired_indent)
327 )
328 };
329
330 let fix = LintFix::replace(ws_seg.clone(), new_segs.clone(), None);
331 let new_point = ReflowPoint::new({
332 let mut new_segments = Vec::new();
333
334 if idx > 0 {
336 new_segments.extend_from_slice(&self.segments[..idx]);
337 }
338
339 new_segments.extend(new_segs);
341
342 if idx < self.segments.len() {
344 new_segments.extend_from_slice(&self.segments[idx + 1..]);
345 }
346
347 new_segments
348 });
349
350 return (
351 vec![LintResult::new(
352 ws_seg.clone().into(),
353 vec![fix],
354 description.into(),
355 source.map(ToOwned::to_owned),
356 )],
357 new_point,
358 );
359 } else {
360 let new_indent = SegmentBuilder::whitespace(tables.next_id(), desired_indent);
361
362 if before.is_none() && after.is_none() {
363 unimplemented!(
364 "Not set up to handle empty points in this scenario without provided \
365 before/after anchor: {:?}",
366 self.segments
367 );
368 } else if let Some(before) = before {
369 let fix = LintFix::create_before(
370 before.clone(),
371 vec![new_newline.clone(), new_indent.clone()],
372 );
373
374 return (
375 vec![LintResult::new(
376 before.clone().into(),
377 vec![fix],
378 Some(format!(
379 "Expected line break and {} before {:?}",
380 indent_description(desired_indent),
381 before.raw()
382 )),
383 source.map(ToOwned::to_owned),
384 )],
385 ReflowPoint::new(vec![new_newline, new_indent]),
386 );
387 } else {
388 let after = after.unwrap();
389 let fix = LintFix::create_after(
390 after.clone(),
391 vec![new_newline.clone(), new_indent.clone()],
392 None,
393 );
394 let description = format!(
395 "Expected line break and {} after {:?}.",
396 indent_description(desired_indent),
397 after.raw()
398 );
399
400 return (
401 vec![LintResult::new(
402 Some(after),
403 vec![fix],
404 Some(description),
405 source.map(ToOwned::to_owned),
406 )],
407 ReflowPoint::new(vec![new_newline, new_indent]),
408 );
409 }
410 }
411 }
412 }
413
414 #[allow(clippy::too_many_arguments)]
415 pub fn respace_point(
416 &self,
417 tables: &Tables,
418 prev_block: Option<&ReflowBlock>,
419 next_block: Option<&ReflowBlock>,
420 root_segment: &ErasedSegment,
421 lint_results: Vec<LintResult>,
422 strip_newlines: bool,
423 anchor_on: &'static str,
424 ) -> (Vec<LintResult>, ReflowPoint) {
425 let mut existing_results = lint_results;
426 let (pre_constraint, post_constraint, strip_newlines) =
427 determine_constraints(prev_block, next_block, strip_newlines);
428
429 let (mut segment_buffer, mut last_whitespace, mut new_results) =
431 process_spacing(&self.segments, strip_newlines);
432
433 if let Some((_, whitespace)) = next_block
434 .zip(last_whitespace.clone())
435 .filter(|(next_block, _)| next_block.class_types().contains(SyntaxKind::EndOfFile))
436 {
437 new_results.push(LintResult::new(
438 None,
439 vec![LintFix::delete(whitespace.clone())],
440 Some("Unnecessary trailing whitespace at end of file.".into()),
441 None,
442 ));
443
444 let pos = segment_buffer
445 .iter()
446 .position(|it| it == &whitespace)
447 .unwrap();
448 segment_buffer.remove(pos);
449
450 last_whitespace = None;
451 }
452
453 if segment_buffer
454 .iter()
455 .any(|seg| seg.is_type(SyntaxKind::Newline))
456 && !strip_newlines
457 || (next_block.is_some()
458 && next_block
459 .unwrap()
460 .class_types()
461 .contains(SyntaxKind::EndOfFile))
462 {
463 if let Some(last_whitespace) = last_whitespace {
464 let ws_idx = self
465 .segments
466 .iter()
467 .position(|it| it == &last_whitespace)
468 .unwrap();
469 if ws_idx > 0 {
470 let segments_slice = &self.segments[..ws_idx];
471
472 let prev_seg = segments_slice
473 .iter()
474 .rev()
475 .find(|seg| {
476 !matches!(seg.get_type(), SyntaxKind::Indent | SyntaxKind::Implicit)
477 })
478 .unwrap();
479
480 if prev_seg.is_type(SyntaxKind::Newline)
481 && prev_seg.get_end_loc() < last_whitespace.get_start_loc()
482 {
483 segment_buffer.remove(ws_idx);
484
485 let temp_idx = last_whitespace
486 .get_position_marker()
487 .unwrap()
488 .templated_slice
489 .start;
490
491 if let Some((index, _)) =
492 existing_results.iter().enumerate().find(|(_, res)| {
493 res.anchor
494 .as_ref()
495 .and_then(|a| a.get_position_marker())
496 .is_some_and(|pm| pm.templated_slice.end == temp_idx)
497 })
498 {
499 let mut res = existing_results.remove(index);
500
501 res.fixes.push(LintFix::delete(last_whitespace));
502 let new_result = LintResult::new(res.anchor, res.fixes, None, None);
503 new_results.push(new_result);
504 } else {
505 panic!("Could not find removal result.");
506 }
507 }
508 }
509 }
510
511 existing_results.extend(new_results);
512 return (existing_results, ReflowPoint::new(segment_buffer));
513 }
514
515 let segment_buffer = if let Some(last_whitespace) = last_whitespace {
517 let (segment_buffer, results) = handle_respace_inline_with_space(
519 tables,
520 pre_constraint,
521 post_constraint,
522 prev_block,
523 next_block,
524 root_segment,
525 segment_buffer,
526 last_whitespace,
527 );
528
529 new_results.extend(results);
530 segment_buffer
531 } else {
532 let (segment_buffer, results, _edited) = handle_respace_inline_without_space(
535 tables,
536 pre_constraint,
537 post_constraint,
538 prev_block,
539 next_block,
540 segment_buffer,
541 chain(existing_results, new_results).collect_vec(),
542 anchor_on,
543 );
544
545 existing_results = Vec::new();
546 new_results = results;
547
548 segment_buffer
549 };
550
551 existing_results.extend(new_results);
552 (existing_results, ReflowPoint::new(segment_buffer))
553 }
554
555 pub fn segments(&self) -> &[ErasedSegment] {
556 &self.segments
557 }
558
559 pub fn indent_impulse(&self) -> &IndentStats {
560 self.stats
561 .get_or_init(|| Self::generate_indent_stats(self.segments()))
562 }
563}
564
565fn indent_description(indent: &str) -> String {
566 match indent {
567 "" => "no indent".to_string(),
568 _ if indent.contains(' ') && indent.contains('\t') => "mixed indent".to_string(),
569 _ if indent.starts_with(' ') => {
570 assert!(indent.chars().all(|c| c == ' '));
571 format!("indent of {} spaces", indent.len())
572 }
573 _ if indent.starts_with('\t') => {
574 assert!(indent.chars().all(|c| c == '\t'));
575 format!("indent of {} tabs", indent.len())
576 }
577 _ => panic!("Invalid indent construction: {:?}", indent),
578 }
579}
580
581#[derive(Debug, Clone, Default, PartialEq)]
582pub struct IndentStats {
583 pub impulse: isize,
584 pub trough: isize,
585 pub implicit_indents: Rc<[isize]>,
586}
587
588impl IndentStats {
589 pub fn from_combination(first: Option<IndentStats>, second: &IndentStats) -> Self {
590 match first {
591 Some(first_stats) => IndentStats {
592 impulse: first_stats.impulse + second.impulse,
593 trough: std::cmp::min(first_stats.trough, first_stats.impulse + second.trough),
594 implicit_indents: second.implicit_indents.clone(),
595 },
596 None => second.clone(),
597 }
598 }
599}
600
601#[derive(Debug, PartialEq)]
602pub struct ReflowBlockData {
603 segment: ErasedSegment,
604 spacing_before: Spacing,
605 spacing_after: Spacing,
606 line_position: Option<Vec<LinePosition>>,
607 depth_info: DepthInfo,
608 stack_spacing_configs: IntMap<u64, Spacing>,
609 line_position_configs: IntMap<u64, &'static str>,
610}
611
612#[derive(Debug, PartialEq, Clone)]
613pub struct ReflowBlock {
614 value: Rc<ReflowBlockData>,
615}
616
617impl Deref for ReflowBlock {
618 type Target = ReflowBlockData;
619
620 fn deref(&self) -> &Self::Target {
621 self.value.as_ref()
622 }
623}
624
625impl ReflowBlock {
626 pub fn segment(&self) -> &ErasedSegment {
627 &self.segment
628 }
629
630 pub fn spacing_before(&self) -> Spacing {
631 self.spacing_before
632 }
633
634 pub fn spacing_after(&self) -> Spacing {
635 self.spacing_after
636 }
637
638 pub fn line_position(&self) -> Option<&[LinePosition]> {
639 self.line_position.as_deref()
640 }
641
642 pub fn depth_info(&self) -> &DepthInfo {
643 &self.depth_info
644 }
645
646 pub fn class_types(&self) -> &SyntaxSet {
647 self.segment.class_types()
648 }
649
650 pub fn stack_spacing_configs(&self) -> &IntMap<u64, Spacing> {
651 &self.stack_spacing_configs
652 }
653
654 pub fn line_position_configs(&self) -> &IntMap<u64, &'static str> {
655 &self.line_position_configs
656 }
657}
658
659impl ReflowBlock {
660 pub fn from_config(
661 segment: ErasedSegment,
662 config: &ReflowConfig,
663 depth_info: DepthInfo,
664 ) -> Self {
665 let block_config = config.get_block_config(segment.class_types(), Some(&depth_info));
666
667 let mut stack_spacing_configs = IntMap::default();
668 let mut line_position_configs = IntMap::default();
669
670 for (hash, class_types) in zip(&depth_info.stack_hashes, &depth_info.stack_class_types) {
671 let cfg = config.get_block_config(class_types, None);
672
673 if let Some(spacing_within) = cfg.spacing_within {
674 stack_spacing_configs.insert(*hash, spacing_within);
675 }
676
677 if let Some(line_position) = cfg.line_position {
678 line_position_configs.insert(*hash, line_position);
679 }
680 }
681
682 let line_position = block_config.line_position.map(|line_position| {
683 line_position
684 .split(':')
685 .map(|it| it.parse().unwrap())
686 .collect()
687 });
688
689 Self {
690 value: Rc::new(ReflowBlockData {
691 segment,
692 spacing_before: block_config.spacing_before,
693 spacing_after: block_config.spacing_after,
694 line_position,
695 depth_info,
696 stack_spacing_configs,
697 line_position_configs,
698 }),
699 }
700 }
701}
702
703impl From<ReflowBlock> for ReflowElement {
704 fn from(value: ReflowBlock) -> Self {
705 Self::Block(value)
706 }
707}
708
709impl From<ReflowPoint> for ReflowElement {
710 fn from(value: ReflowPoint) -> Self {
711 Self::Point(value)
712 }
713}
714
715#[derive(Debug, Clone, PartialEq)]
716pub enum ReflowElement {
717 Block(ReflowBlock),
718 Point(ReflowPoint),
719}
720
721impl ReflowElement {
722 pub fn raw(&self) -> String {
723 self.segments().iter().map(|it| it.raw()).join("")
724 }
725
726 pub fn segments(&self) -> &[ErasedSegment] {
727 match self {
728 ReflowElement::Block(block) => std::slice::from_ref(&block.segment),
729 ReflowElement::Point(point) => &point.segments,
730 }
731 }
732
733 pub fn class_types(&self) -> &SyntaxSet {
734 match self {
735 ReflowElement::Block(reflow_block) => reflow_block.class_types(),
736 ReflowElement::Point(reflow_point) => reflow_point.class_types(),
737 }
738 }
739
740 pub fn num_newlines(&self) -> usize {
741 self.segments()
742 .iter()
743 .map(|seg| {
744 let newline_in_class = seg.class_types().contains(SyntaxKind::Newline) as usize;
745
746 let consumed_whitespace = get_consumed_whitespace(seg.into()).unwrap_or_default();
747 newline_in_class + consumed_whitespace.matches('\n').count()
748 })
749 .sum()
750 }
751
752 pub fn as_point(&self) -> Option<&ReflowPoint> {
753 if let Self::Point(v) = self {
754 Some(v)
755 } else {
756 None
757 }
758 }
759
760 pub fn as_block(&self) -> Option<&ReflowBlock> {
761 if let Self::Block(v) = self {
762 Some(v)
763 } else {
764 None
765 }
766 }
767}
768
769impl PartialEq<ReflowBlock> for ReflowElement {
770 fn eq(&self, other: &ReflowBlock) -> bool {
771 match self {
772 ReflowElement::Block(this) => this == other,
773 ReflowElement::Point(_) => false,
774 }
775 }
776}
777
778pub type ReflowSequenceType = Vec<ReflowElement>;