use std::ops::Range;
use hashbrown::HashSet;
use crate::parser::segments::ErasedSegment;
use crate::templaters::{RawFileSlice, TemplatedFile};
#[derive(Debug, Clone)]
pub enum LintFix {
CreateBefore {
anchor: ErasedSegment,
edit: Vec<ErasedSegment>,
},
CreateAfter {
anchor: ErasedSegment,
edit: Vec<ErasedSegment>,
source: Vec<ErasedSegment>,
},
Replace {
anchor: ErasedSegment,
edit: Vec<ErasedSegment>,
source: Vec<ErasedSegment>,
},
Delete {
anchor: ErasedSegment,
},
}
impl LintFix {
pub fn anchor(&self) -> &ErasedSegment {
match self {
LintFix::CreateBefore { anchor, .. } => anchor,
LintFix::CreateAfter { anchor, .. } => anchor,
LintFix::Replace { anchor, .. } => anchor,
LintFix::Delete { anchor } => anchor,
}
}
fn prepare_edit_segments(mut edit: Vec<ErasedSegment>) -> Vec<ErasedSegment> {
for seg in &mut edit {
if seg.get_position_marker().is_some() {
seg.make_mut().set_position_marker(None);
};
}
edit
}
fn prepare_source_segments(source: Option<Vec<ErasedSegment>>) -> Vec<ErasedSegment> {
source.map_or(Vec::new(), |source| {
source
.into_iter()
.filter(|seg| seg.get_position_marker().is_some())
.collect()
})
}
pub fn create_before(anchor: ErasedSegment, edit_segments: Vec<ErasedSegment>) -> Self {
LintFix::CreateBefore {
anchor,
edit: Self::prepare_edit_segments(edit_segments),
}
}
pub fn create_after(
anchor: ErasedSegment,
edit_segments: Vec<ErasedSegment>,
source: Option<Vec<ErasedSegment>>,
) -> Self {
LintFix::CreateAfter {
anchor,
edit: Self::prepare_edit_segments(edit_segments),
source: Self::prepare_source_segments(source),
}
}
pub fn replace(
anchor_segment: ErasedSegment,
edit_segments: Vec<ErasedSegment>,
source: Option<Vec<ErasedSegment>>,
) -> Self {
LintFix::Replace {
anchor: anchor_segment,
edit: Self::prepare_edit_segments(edit_segments),
source: Self::prepare_source_segments(source),
}
}
pub fn delete(anchor_segment: ErasedSegment) -> Self {
LintFix::Delete {
anchor: anchor_segment,
}
}
pub fn is_just_source_edit(&self) -> bool {
match self {
LintFix::Replace { anchor, edit, .. } => {
edit.len() == 1 && edit[0].raw() == anchor.raw()
}
_ => false,
}
}
fn fix_slices(
&self,
templated_file: &TemplatedFile,
within_only: bool,
) -> HashSet<RawFileSlice> {
let anchor = match self {
LintFix::CreateBefore { anchor, .. } => anchor,
LintFix::CreateAfter { anchor, .. } => anchor,
LintFix::Replace { anchor, .. } => anchor,
LintFix::Delete { anchor } => anchor,
};
let anchor_slice = anchor
.get_position_marker()
.unwrap()
.templated_slice
.clone();
let adjust_boundary = if !within_only { 1 } else { 0 };
let templated_slice = match self {
LintFix::CreateBefore { .. } => {
anchor_slice.start.saturating_sub(1)..anchor_slice.start + adjust_boundary
}
LintFix::CreateAfter { .. } => anchor_slice.end - adjust_boundary..anchor_slice.end + 1,
LintFix::Replace { anchor, edit, .. } => {
let pos = anchor.get_position_marker().unwrap();
if pos.source_slice.start == pos.source_slice.end {
return HashSet::new();
} else if edit
.iter()
.all(|it| it.segments().is_empty() && !it.get_source_fixes().is_empty())
{
let source_edit_slices: Vec<_> = edit
.iter()
.flat_map(|edit| edit.get_source_fixes())
.map(|source_fixe| source_fixe.source_slice.clone())
.collect();
let slice =
templated_file.raw_slices_spanning_source_slice(&source_edit_slices[0]);
return HashSet::from_iter(slice);
}
anchor_slice
}
LintFix::Delete { .. } => anchor_slice,
};
self.raw_slices_from_templated_slices(
templated_file,
std::iter::once(templated_slice),
RawFileSlice::new(String::new(), "literal".to_string(), usize::MAX, None, None).into(),
)
}
fn raw_slices_from_templated_slices(
&self,
templated_file: &TemplatedFile,
templated_slices: impl Iterator<Item = Range<usize>>,
file_end_slice: Option<RawFileSlice>,
) -> HashSet<RawFileSlice> {
let mut raw_slices = HashSet::new();
for templated_slice in templated_slices {
let templated_slice =
templated_file.templated_slice_to_source_slice(templated_slice.clone());
match templated_slice {
Ok(templated_slice) => raw_slices
.extend(templated_file.raw_slices_spanning_source_slice(&templated_slice)),
Err(_) => {
if let Some(file_end_slice) = file_end_slice.clone() {
raw_slices.insert(file_end_slice);
}
}
}
}
raw_slices
}
pub fn has_template_conflicts(&self, templated_file: &TemplatedFile) -> bool {
if let LintFix::Replace { anchor, edit, .. } = self
&& edit.len() == 1
{
let edit_seg = &edit[0];
if edit_seg.raw() == anchor.raw() && !edit_seg.get_source_fixes().is_empty() {
return false;
}
}
let check_fn = match self {
LintFix::CreateAfter { .. } | LintFix::CreateBefore { .. } => itertools::all,
_ => itertools::any,
};
let fix_slices = self.fix_slices(templated_file, false);
let result = check_fn(fix_slices, |fs: RawFileSlice| fs.slice_type == "templated");
let source_is_empty = match self {
LintFix::CreateAfter { source, .. } | LintFix::Replace { source, .. } => {
source.is_empty()
}
_ => true,
};
if result || source_is_empty {
return result;
}
let templated_slices = None;
let raw_slices = self.raw_slices_from_templated_slices(
templated_file,
templated_slices.into_iter(),
None,
);
raw_slices.iter().any(|fs| fs.slice_type == "templated")
}
}
impl PartialEq for LintFix {
fn eq(&self, other: &Self) -> bool {
use std::mem::discriminant;
if discriminant(self) != discriminant(other) {
return false;
}
match (self, other) {
(
LintFix::CreateBefore {
anchor: a1,
edit: e1,
},
LintFix::CreateBefore {
anchor: a2,
edit: e2,
},
) => {
a1.get_type() == a2.get_type()
&& a1.id() == a2.id()
&& e1.len() == e2.len()
&& e1.iter().zip(e2).all(|(s1, s2)| {
s1.raw() == s2.raw() && s1.get_source_fixes() == s2.get_source_fixes()
})
}
(
LintFix::CreateAfter {
anchor: a1,
edit: e1,
source: s1,
},
LintFix::CreateAfter {
anchor: a2,
edit: e2,
source: s2,
},
) => {
a1.get_type() == a2.get_type()
&& a1.id() == a2.id()
&& e1.len() == e2.len()
&& e1.iter().zip(e2).all(|(seg1, seg2)| {
seg1.raw() == seg2.raw()
&& seg1.get_source_fixes() == seg2.get_source_fixes()
})
&& s1 == s2
}
(
LintFix::Replace {
anchor: a1,
edit: e1,
source: s1,
},
LintFix::Replace {
anchor: a2,
edit: e2,
source: s2,
},
) => {
a1.get_type() == a2.get_type()
&& a1.id() == a2.id()
&& e1.len() == e2.len()
&& e1.iter().zip(e2).all(|(seg1, seg2)| {
seg1.raw() == seg2.raw()
&& seg1.get_source_fixes() == seg2.get_source_fixes()
})
&& s1 == s2
}
(LintFix::Delete { anchor: a1 }, LintFix::Delete { anchor: a2 }) => {
a1.get_type() == a2.get_type() && a1.id() == a2.id()
}
_ => false,
}
}
}