1use std::ops::Range;
2
3use ahash::AHashSet;
4
5use crate::parser::segments::ErasedSegment;
6use crate::templaters::{RawFileSlice, 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 ) -> AHashSet<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 AHashSet::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 AHashSet::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(String::new(), "literal".to_string(), usize::MAX, None, None).into(),
176 )
177 }
178
179 fn raw_slices_from_templated_slices(
180 &self,
181 templated_file: &TemplatedFile,
182 templated_slices: impl Iterator<Item = Range<usize>>,
183 file_end_slice: Option<RawFileSlice>,
184 ) -> AHashSet<RawFileSlice> {
185 let mut raw_slices = AHashSet::new();
186
187 for templated_slice in templated_slices {
188 let templated_slice =
189 templated_file.templated_slice_to_source_slice(templated_slice.clone());
190
191 match templated_slice {
192 Ok(templated_slice) => raw_slices
193 .extend(templated_file.raw_slices_spanning_source_slice(&templated_slice)),
194 Err(_) => {
195 if let Some(file_end_slice) = file_end_slice.clone() {
196 raw_slices.insert(file_end_slice);
197 }
198 }
199 }
200 }
201
202 raw_slices
203 }
204
205 pub fn has_template_conflicts(&self, templated_file: &TemplatedFile) -> bool {
206 if let LintFix::Replace { anchor, edit, .. } = self
207 && edit.len() == 1
208 {
209 let edit_seg = &edit[0];
210 if edit_seg.raw() == anchor.raw() && !edit_seg.get_source_fixes().is_empty() {
211 return false;
212 }
213 }
214
215 let check_fn = match self {
216 LintFix::CreateAfter { .. } | LintFix::CreateBefore { .. } => itertools::all,
217 _ => itertools::any,
218 };
219
220 let fix_slices = self.fix_slices(templated_file, false);
221 let result = check_fn(fix_slices, |fs: RawFileSlice| fs.slice_type == "templated");
222
223 let source_is_empty = match self {
224 LintFix::CreateAfter { source, .. } | LintFix::Replace { source, .. } => {
225 source.is_empty()
226 }
227 _ => true,
228 };
229
230 if result || source_is_empty {
231 return result;
232 }
233
234 let templated_slices = None;
235 let raw_slices = self.raw_slices_from_templated_slices(
236 templated_file,
237 templated_slices.into_iter(),
238 None,
239 );
240 raw_slices.iter().any(|fs| fs.slice_type == "templated")
241 }
242}
243
244impl PartialEq for LintFix {
245 fn eq(&self, other: &Self) -> bool {
246 use std::mem::discriminant;
247
248 if discriminant(self) != discriminant(other) {
250 return false;
251 }
252
253 match (self, other) {
254 (
255 LintFix::CreateBefore {
256 anchor: a1,
257 edit: e1,
258 },
259 LintFix::CreateBefore {
260 anchor: a2,
261 edit: e2,
262 },
263 ) => {
264 a1.get_type() == a2.get_type()
265 && a1.id() == a2.id()
266 && e1.len() == e2.len()
267 && e1.iter().zip(e2).all(|(s1, s2)| {
268 s1.raw() == s2.raw() && s1.get_source_fixes() == s2.get_source_fixes()
269 })
270 }
271 (
272 LintFix::CreateAfter {
273 anchor: a1,
274 edit: e1,
275 source: s1,
276 },
277 LintFix::CreateAfter {
278 anchor: a2,
279 edit: e2,
280 source: s2,
281 },
282 ) => {
283 a1.get_type() == a2.get_type()
284 && a1.id() == a2.id()
285 && e1.len() == e2.len()
286 && e1.iter().zip(e2).all(|(seg1, seg2)| {
287 seg1.raw() == seg2.raw()
288 && seg1.get_source_fixes() == seg2.get_source_fixes()
289 })
290 && s1 == s2
291 }
292 (
293 LintFix::Replace {
294 anchor: a1,
295 edit: e1,
296 source: s1,
297 },
298 LintFix::Replace {
299 anchor: a2,
300 edit: e2,
301 source: s2,
302 },
303 ) => {
304 a1.get_type() == a2.get_type()
305 && a1.id() == a2.id()
306 && e1.len() == e2.len()
307 && e1.iter().zip(e2).all(|(seg1, seg2)| {
308 seg1.raw() == seg2.raw()
309 && seg1.get_source_fixes() == seg2.get_source_fixes()
310 })
311 && s1 == s2
312 }
313 (LintFix::Delete { anchor: a1 }, LintFix::Delete { anchor: a2 }) => {
314 a1.get_type() == a2.get_type() && a1.id() == a2.id()
315 }
316 _ => false,
317 }
318 }
319}