use crate::{Change, ChangeKind};
pub(super) struct Paths<'a, T>
where
T: AsRef<str>,
{
diff: &'a [Change],
resolved_spans: &'a [T],
source: &'a str,
paths: Vec<([&'a str; 4], usize)>,
}
impl<'a, T> Paths<'a, T>
where
T: AsRef<str>,
{
pub(super) fn new(diff: &'a [Change], resolved_spans: &'a [T], source: &'a str) -> Self {
let mut paths = diff
.iter()
.enumerate()
.map(|(idx, diff)| (split_path(&diff.path), idx))
.collect::<Vec<_>>();
paths.sort_unstable();
Paths {
diff,
source,
resolved_spans,
paths,
}
}
pub(super) fn iter_top_level<'b: 'a>(&'b self) -> impl Iterator<Item = ChangeView<'a, T>> + 'b {
self.paths
.iter()
.filter(|(change, _)| change[1].is_empty() && change[2].is_empty() && change[3].is_empty())
.map(|(_, idx)| ChangeView { paths: self, idx: *idx })
}
pub(super) fn iter_second_level<'b: 'a>(&'b self, parent: &'b str) -> impl Iterator<Item = ChangeView<'a, T>> + 'b {
self.paths
.iter()
.filter(move |(change, _)| change[0] == parent && !change[1].is_empty())
.map(|(_, idx)| ChangeView { paths: self, idx: *idx })
}
pub(super) fn iter_exact<'b: 'a>(&'b self, path: [&'b str; 4]) -> impl Iterator<Item = ChangeView<'a, T>> + 'b {
let first = self.paths.partition_point(|(diff_path, _)| diff_path < &path);
self.paths[first..]
.iter()
.take_while(move |(diff_path, _)| diff_path == &path)
.map(move |(_, idx)| ChangeView { paths: self, idx: *idx })
}
pub(crate) fn source(&self) -> &'a str {
self.source
}
pub(crate) fn added_interface_impls<'b>(&'b self, prefix: &'b str) -> impl Iterator<Item = &'a str> + 'b {
let start = self.paths.partition_point(|([found, _, _, _], _)| *found < prefix);
self.paths[start..]
.iter()
.take_while(move |([name, _, _, _], _)| *name == prefix)
.filter(|(_, idx)| matches!(self.diff[*idx].kind, ChangeKind::AddInterfaceImplementation))
.map(|([_, interface, _, _], _)| interface.trim_start_matches('&'))
}
pub(crate) fn is_interface_impl_removed(&self, prefix: &str, interface: &str) -> bool {
self.paths
.binary_search_by(|(path, idx)| {
[path[0], path[1].trim_start_matches('&')]
.cmp(&[prefix, interface])
.then(self.diff[*idx].kind.cmp(&ChangeKind::RemoveInterfaceImplementation))
})
.is_ok()
}
pub(crate) fn directive_usage_changes_at<'b: 'a>(
&'b self,
context: super::directives::DirectiveContext<'b>,
) -> impl Iterator<Item = ChangeView<'a, T>> + 'b {
self.paths
.iter()
.filter(move |(change, idx)| {
if !matches!(
self.diff[*idx].kind,
ChangeKind::AddDirective | ChangeKind::RemoveDirective
) {
return false;
}
match context {
super::directives::DirectiveContext::Type(type_name) => {
change[0] == type_name
&& change[1].starts_with('@')
&& change[2].is_empty()
&& change[3].is_empty()
}
super::directives::DirectiveContext::Field(type_name, field) => {
change[0] == type_name
&& change[1] == field
&& change[2].starts_with('@')
&& change[3].is_empty()
}
super::directives::DirectiveContext::Argument(type_name, field, arg) => {
change[0] == type_name && change[1] == field && change[2] == arg && change[3].starts_with('@')
}
}
})
.map(move |(_, idx)| ChangeView { paths: self, idx: *idx })
}
}
pub(super) struct ChangeView<'a, T>
where
T: AsRef<str>,
{
paths: &'a Paths<'a, T>,
idx: usize,
}
impl<T: AsRef<str>> Clone for ChangeView<'_, T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: AsRef<str>> Copy for ChangeView<'_, T> {}
impl<'a, T> ChangeView<'a, T>
where
T: AsRef<str>,
{
pub(crate) fn kind(self) -> ChangeKind {
self.paths.diff[self.idx].kind
}
pub(crate) fn resolved_str(self) -> &'a str {
self.paths.resolved_spans[self.idx].as_ref()
}
pub(crate) fn path(self) -> &'a str {
&self.paths.diff[self.idx].path
}
pub(crate) fn second_and_third_level(self) -> [&'a str; 2] {
let [_first, second, third, _fourth] = split_path(self.path());
debug_assert!(!second.is_empty());
debug_assert!(!third.is_empty());
[second, third]
}
pub(crate) fn second_level(self) -> Option<&'a str>
where
T: AsRef<str>,
{
Some(split_path(&self.paths.diff[self.idx].path)[1]).filter(|s| !s.is_empty())
}
}
fn split_path(path: &str) -> [&str; 4] {
let mut segments = path.split('.');
let path = std::array::from_fn(|_| segments.next().unwrap_or(""));
debug_assert!(segments.next().is_none());
path
}