1use std::ops::Range;
2
3use hashbrown::HashSet;
4
5use crate::parser::segments::ErasedSegment;
6use crate::templaters::{RawFileSlice, TemplateSliceKind, TemplatedFile};
7
8#[derive(Debug, Clone)]
10pub enum LintFix {
11 CreateBefore {
13 anchor: ErasedSegment,
15 edit: Vec<ErasedSegment>,
17 },
18 CreateAfter {
20 anchor: ErasedSegment,
22 edit: Vec<ErasedSegment>,
24 source: Vec<ErasedSegment>,
26 },
27 Replace {
29 anchor: ErasedSegment,
31 edit: Vec<ErasedSegment>,
33 source: Vec<ErasedSegment>,
35 },
36 Delete {
38 anchor: ErasedSegment,
40 },
41}
42
43impl LintFix {
44 pub fn anchor(&self) -> &ErasedSegment {
46 match self {
47 LintFix::CreateBefore { anchor, .. } => anchor,
48 LintFix::CreateAfter { anchor, .. } => anchor,
49 LintFix::Replace { anchor, .. } => anchor,
50 LintFix::Delete { anchor } => anchor,
51 }
52 }
53
54 fn prepare_edit_segments(mut edit: Vec<ErasedSegment>) -> Vec<ErasedSegment> {
55 for seg in &mut edit {
59 if seg.get_position_marker().is_some() {
60 seg.make_mut().set_position_marker(None);
61 };
62 }
63 edit
64 }
65
66 fn prepare_source_segments(source: Option<Vec<ErasedSegment>>) -> Vec<ErasedSegment> {
67 source.map_or(Vec::new(), |source| {
69 source
70 .into_iter()
71 .filter(|seg| seg.get_position_marker().is_some())
72 .collect()
73 })
74 }
75
76 pub fn create_before(anchor: ErasedSegment, edit_segments: Vec<ErasedSegment>) -> Self {
77 LintFix::CreateBefore {
78 anchor,
79 edit: Self::prepare_edit_segments(edit_segments),
80 }
81 }
82
83 pub fn create_after(
84 anchor: ErasedSegment,
85 edit_segments: Vec<ErasedSegment>,
86 source: Option<Vec<ErasedSegment>>,
87 ) -> Self {
88 LintFix::CreateAfter {
89 anchor,
90 edit: Self::prepare_edit_segments(edit_segments),
91 source: Self::prepare_source_segments(source),
92 }
93 }
94
95 pub fn replace(
96 anchor_segment: ErasedSegment,
97 edit_segments: Vec<ErasedSegment>,
98 source: Option<Vec<ErasedSegment>>,
99 ) -> Self {
100 LintFix::Replace {
101 anchor: anchor_segment,
102 edit: Self::prepare_edit_segments(edit_segments),
103 source: Self::prepare_source_segments(source),
104 }
105 }
106
107 pub fn delete(anchor_segment: ErasedSegment) -> Self {
108 LintFix::Delete {
109 anchor: anchor_segment,
110 }
111 }
112
113 pub fn is_just_source_edit(&self) -> bool {
115 match self {
116 LintFix::Replace { anchor, edit, .. } => {
117 edit.len() == 1 && edit[0].raw() == anchor.raw()
118 }
119 _ => false,
120 }
121 }
122
123 fn fix_slices(
124 &self,
125 templated_file: &TemplatedFile,
126 within_only: bool,
127 ) -> HashSet<RawFileSlice> {
128 let anchor = match self {
129 LintFix::CreateBefore { anchor, .. } => anchor,
130 LintFix::CreateAfter { anchor, .. } => anchor,
131 LintFix::Replace { anchor, .. } => anchor,
132 LintFix::Delete { anchor } => anchor,
133 };
134
135 let anchor_slice = anchor
136 .get_position_marker()
137 .unwrap()
138 .templated_slice
139 .clone();
140
141 let adjust_boundary = if !within_only { 1 } else { 0 };
142
143 let templated_slice = match self {
144 LintFix::CreateBefore { .. } => {
145 anchor_slice.start.saturating_sub(1)..anchor_slice.start + adjust_boundary
146 }
147 LintFix::CreateAfter { .. } => anchor_slice.end - adjust_boundary..anchor_slice.end + 1,
148 LintFix::Replace { anchor, edit, .. } => {
149 let pos = anchor.get_position_marker().unwrap();
150 if pos.source_slice.start == pos.source_slice.end {
151 return HashSet::new();
152 } else if edit
153 .iter()
154 .all(|it| it.segments().is_empty() && !it.get_source_fixes().is_empty())
155 {
156 let source_edit_slices: Vec<_> = edit
157 .iter()
158 .flat_map(|edit| edit.get_source_fixes())
159 .map(|source_fixe| source_fixe.source_slice.clone())
160 .collect();
161
162 let slice =
163 templated_file.raw_slices_spanning_source_slice(&source_edit_slices[0]);
164 return HashSet::from_iter(slice);
165 }
166
167 anchor_slice
168 }
169 LintFix::Delete { .. } => anchor_slice,
170 };
171
172 self.raw_slices_from_templated_slices(
173 templated_file,
174 std::iter::once(templated_slice),
175 RawFileSlice::new(
176 String::new(),
177 TemplateSliceKind::Literal,
178 usize::MAX,
179 None,
180 None,
181 )
182 .into(),
183 )
184 }
185
186 fn raw_slices_from_templated_slices(
187 &self,
188 templated_file: &TemplatedFile,
189 templated_slices: impl Iterator<Item = Range<usize>>,
190 file_end_slice: Option<RawFileSlice>,
191 ) -> HashSet<RawFileSlice> {
192 let mut raw_slices = HashSet::new();
193
194 for templated_slice in templated_slices {
195 let templated_slice =
196 templated_file.templated_slice_to_source_slice(templated_slice.clone());
197
198 match templated_slice {
199 Ok(templated_slice) => raw_slices
200 .extend(templated_file.raw_slices_spanning_source_slice(&templated_slice)),
201 Err(_) => {
202 if let Some(file_end_slice) = file_end_slice.clone() {
203 raw_slices.insert(file_end_slice);
204 }
205 }
206 }
207 }
208
209 raw_slices
210 }
211
212 pub fn has_template_conflicts(&self, templated_file: &TemplatedFile) -> bool {
213 if let LintFix::Replace { anchor, edit, .. } = self
214 && edit.len() == 1
215 {
216 let edit_seg = &edit[0];
217 if edit_seg.raw() == anchor.raw() && !edit_seg.get_source_fixes().is_empty() {
218 return false;
219 }
220 }
221
222 let check_fn = match self {
223 LintFix::CreateAfter { .. } | LintFix::CreateBefore { .. } => itertools::all,
224 _ => itertools::any,
225 };
226
227 let fix_slices = self.fix_slices(templated_file, false);
228 let result = check_fn(fix_slices, |fs: RawFileSlice| {
229 fs.has_slice_kind(TemplateSliceKind::Templated)
230 });
231
232 let source_is_empty = match self {
233 LintFix::CreateAfter { source, .. } | LintFix::Replace { source, .. } => {
234 source.is_empty()
235 }
236 _ => true,
237 };
238
239 if result || source_is_empty {
240 return result;
241 }
242
243 let templated_slices = None;
244 let raw_slices = self.raw_slices_from_templated_slices(
245 templated_file,
246 templated_slices.into_iter(),
247 None,
248 );
249 raw_slices
250 .iter()
251 .any(|fs| fs.has_slice_kind(TemplateSliceKind::Templated))
252 }
253}
254
255impl PartialEq for LintFix {
256 fn eq(&self, other: &Self) -> bool {
257 use std::mem::discriminant;
258
259 if discriminant(self) != discriminant(other) {
261 return false;
262 }
263
264 match (self, other) {
265 (
266 LintFix::CreateBefore {
267 anchor: a1,
268 edit: e1,
269 },
270 LintFix::CreateBefore {
271 anchor: a2,
272 edit: e2,
273 },
274 ) => {
275 a1.get_type() == a2.get_type()
276 && a1.id() == a2.id()
277 && e1.len() == e2.len()
278 && e1.iter().zip(e2).all(|(s1, s2)| {
279 s1.raw() == s2.raw() && s1.get_source_fixes() == s2.get_source_fixes()
280 })
281 }
282 (
283 LintFix::CreateAfter {
284 anchor: a1,
285 edit: e1,
286 source: s1,
287 },
288 LintFix::CreateAfter {
289 anchor: a2,
290 edit: e2,
291 source: s2,
292 },
293 ) => {
294 a1.get_type() == a2.get_type()
295 && a1.id() == a2.id()
296 && e1.len() == e2.len()
297 && e1.iter().zip(e2).all(|(seg1, seg2)| {
298 seg1.raw() == seg2.raw()
299 && seg1.get_source_fixes() == seg2.get_source_fixes()
300 })
301 && s1 == s2
302 }
303 (
304 LintFix::Replace {
305 anchor: a1,
306 edit: e1,
307 source: s1,
308 },
309 LintFix::Replace {
310 anchor: a2,
311 edit: e2,
312 source: s2,
313 },
314 ) => {
315 a1.get_type() == a2.get_type()
316 && a1.id() == a2.id()
317 && e1.len() == e2.len()
318 && e1.iter().zip(e2).all(|(seg1, seg2)| {
319 seg1.raw() == seg2.raw()
320 && seg1.get_source_fixes() == seg2.get_source_fixes()
321 })
322 && s1 == s2
323 }
324 (LintFix::Delete { anchor: a1 }, LintFix::Delete { anchor: a2 }) => {
325 a1.get_type() == a2.get_type() && a1.id() == a2.id()
326 }
327 _ => false,
328 }
329 }
330}