sqruff_lib_core/
lint_fix.rs1use std::ops::Range;
2
3use ahash::AHashSet;
4
5use crate::edit_type::EditType;
6use crate::parser::segments::base::ErasedSegment;
7use crate::templaters::base::{RawFileSlice, TemplatedFile};
8
9#[derive(Debug, Clone)]
11pub struct LintFix {
12 pub edit_type: EditType,
14 pub anchor: ErasedSegment,
19 pub edit: Vec<ErasedSegment>,
22 pub source: Vec<ErasedSegment>,
26}
27
28impl LintFix {
29 fn new(
30 edit_type: EditType,
31 anchor: ErasedSegment,
32 mut edit: Vec<ErasedSegment>,
33 source: Option<Vec<ErasedSegment>>,
34 ) -> Self {
35 for seg in &mut edit {
39 if seg.get_position_marker().is_some() {
40 seg.make_mut().set_position_marker(None);
41 };
42 }
43
44 let clean_source = source.map_or(Vec::new(), |source| {
46 source
47 .into_iter()
48 .filter(|seg| seg.get_position_marker().is_some())
49 .collect()
50 });
51
52 LintFix {
53 edit_type,
54 anchor,
55 edit,
56 source: clean_source,
57 }
58 }
59
60 pub fn create_before(anchor: ErasedSegment, edit_segments: Vec<ErasedSegment>) -> Self {
61 Self::new(EditType::CreateBefore, anchor, edit_segments, None)
62 }
63
64 pub fn create_after(
65 anchor: ErasedSegment,
66 edit_segments: Vec<ErasedSegment>,
67 source: Option<Vec<ErasedSegment>>,
68 ) -> Self {
69 Self::new(EditType::CreateAfter, anchor, edit_segments, source)
70 }
71
72 pub fn replace(
73 anchor_segment: ErasedSegment,
74 edit_segments: Vec<ErasedSegment>,
75 source: Option<Vec<ErasedSegment>>,
76 ) -> Self {
77 Self::new(EditType::Replace, anchor_segment, edit_segments, source)
78 }
79
80 pub fn delete(anchor_segment: ErasedSegment) -> Self {
81 Self::new(EditType::Delete, anchor_segment, Vec::new(), None)
82 }
83
84 pub fn is_just_source_edit(&self) -> bool {
86 self.edit_type == EditType::Replace
87 && self.edit.len() == 1
88 && self.edit[0].raw() == self.anchor.raw()
89 }
90
91 fn fix_slices(
92 &self,
93 templated_file: &TemplatedFile,
94 within_only: bool,
95 ) -> AHashSet<RawFileSlice> {
96 let anchor_slice = self
97 .anchor
98 .get_position_marker()
99 .unwrap()
100 .templated_slice
101 .clone();
102
103 let adjust_boundary = if !within_only { 1 } else { 0 };
104
105 let templated_slice = match self.edit_type {
106 EditType::CreateBefore => {
107 anchor_slice.start.saturating_sub(1)..anchor_slice.start + adjust_boundary
108 }
109 EditType::CreateAfter => anchor_slice.end - adjust_boundary..anchor_slice.end + 1,
110 EditType::Replace => {
111 let pos = self.anchor.get_position_marker().unwrap();
112 if pos.source_slice.start == pos.source_slice.end {
113 return AHashSet::new();
114 } else if self
115 .edit
116 .iter()
117 .all(|it| it.segments().is_empty() && !it.get_source_fixes().is_empty())
118 {
119 let source_edit_slices: Vec<_> = self
120 .edit
121 .iter()
122 .flat_map(|edit| edit.get_source_fixes())
123 .map(|source_fixe| source_fixe.source_slice.clone())
124 .collect();
125
126 let slice =
127 templated_file.raw_slices_spanning_source_slice(&source_edit_slices[0]);
128 return AHashSet::from_iter(slice);
129 }
130
131 anchor_slice
132 }
133 _ => anchor_slice,
134 };
135
136 self.raw_slices_from_templated_slices(
137 templated_file,
138 std::iter::once(templated_slice),
139 RawFileSlice::new(String::new(), "literal".to_string(), usize::MAX, None, None).into(),
140 )
141 }
142
143 fn raw_slices_from_templated_slices(
144 &self,
145 templated_file: &TemplatedFile,
146 templated_slices: impl Iterator<Item = Range<usize>>,
147 file_end_slice: Option<RawFileSlice>,
148 ) -> AHashSet<RawFileSlice> {
149 let mut raw_slices = AHashSet::new();
150
151 for templated_slice in templated_slices {
152 let templated_slice =
153 templated_file.templated_slice_to_source_slice(templated_slice.clone());
154
155 match templated_slice {
156 Ok(templated_slice) => raw_slices
157 .extend(templated_file.raw_slices_spanning_source_slice(&templated_slice)),
158 Err(_) => {
159 if let Some(file_end_slice) = file_end_slice.clone() {
160 raw_slices.insert(file_end_slice);
161 }
162 }
163 }
164 }
165
166 raw_slices
167 }
168
169 pub fn has_template_conflicts(&self, templated_file: &TemplatedFile) -> bool {
170 if self.edit_type == EditType::Replace && self.edit.len() == 1 {
171 let edit = &self.edit[0];
172 if edit.raw() == self.anchor.raw() && !edit.get_source_fixes().is_empty() {
173 return false;
174 }
175 }
176
177 let check_fn = if let EditType::CreateAfter | EditType::CreateBefore = self.edit_type {
178 itertools::all
179 } else {
180 itertools::any
181 };
182
183 let fix_slices = self.fix_slices(templated_file, false);
184 let result = check_fn(fix_slices, |fs: RawFileSlice| fs.slice_type == "templated");
185
186 if result || self.source.is_empty() {
187 return result;
188 }
189
190 let templated_slices = None;
191 let raw_slices = self.raw_slices_from_templated_slices(
192 templated_file,
193 templated_slices.into_iter(),
194 None,
195 );
196 raw_slices.iter().any(|fs| fs.slice_type == "templated")
197 }
198}
199
200impl PartialEq for LintFix {
201 fn eq(&self, other: &Self) -> bool {
202 if self.edit_type != other.edit_type {
204 return false;
205 }
206 if self.anchor.get_type() != other.anchor.get_type() {
208 return false;
209 }
210 if self.anchor.id() != other.anchor.id() {
212 return false;
213 }
214
215 if self.edit.len() != other.edit.len() {
217 return false;
218 }
219 for (self_base_segment, other_base_segment) in self.edit.iter().zip(&other.edit) {
221 if self_base_segment.raw() != other_base_segment.raw()
222 || self_base_segment.get_source_fixes() != other_base_segment.get_source_fixes()
223 {
224 return false;
225 }
226 }
227 true
229 }
230}