use std::borrow::Cow;
use std::path::Path;
use syn::visit_mut::VisitMut;
use syn::ItemMod;
use crate::{Error, FileResolver, FsResolver, InlineError, ModContext};
pub(crate) struct Visitor<'a, R: Clone> {
path: &'a Path,
root: bool,
mod_context: ModContext,
resolver: Cow<'a, R>,
error_log: Option<&'a mut Vec<InlineError>>,
}
impl<'a, R: FileResolver + Default + Clone> Visitor<'a, R> {
fn new(path: &'a Path, root: bool, error_log: Option<&'a mut Vec<InlineError>>) -> Self {
Self::with_resolver(path, root, error_log, Cow::Owned(R::default()))
}
}
impl<'a, R: FileResolver + Clone> Visitor<'a, R> {
pub fn with_resolver(
path: &'a Path,
root: bool,
error_log: Option<&'a mut Vec<InlineError>>,
resolver: Cow<'a, R>,
) -> Self {
Self {
path,
root,
resolver,
error_log,
mod_context: Default::default(),
}
}
pub fn visit(&mut self) -> Result<syn::File, Error> {
let mut syntax = self.resolver.resolve(self.path)?;
self.visit_file_mut(&mut syntax);
Ok(syntax)
}
}
impl<'a, R: FileResolver + Clone> VisitMut for Visitor<'a, R> {
fn visit_item_mod_mut(&mut self, i: &mut ItemMod) {
self.mod_context.push(i.into());
if let Some((_, items)) = &mut i.content {
for item in items {
self.visit_item_mut(item);
}
} else {
let candidates = self.mod_context.relative_to(self.path, self.root);
let first_candidate = candidates
.iter()
.find(|p| self.resolver.path_exists(p))
.unwrap_or_else(|| {
candidates
.iter()
.last()
.expect("candidates should be non-empty")
});
let mut visitor = Visitor::with_resolver(
&first_candidate,
false,
self.error_log.as_mut().map(|v| &mut **v),
self.resolver.clone(),
);
match visitor.visit() {
Ok(syn::File { attrs, items, .. }) => {
i.attrs.extend(attrs);
i.content = Some((Default::default(), items));
}
Err(kind) => {
if let Some(ref mut errors) = self.error_log {
errors.push(InlineError::new(self.path, i, first_candidate, kind));
}
}
}
}
self.mod_context.pop();
}
}
impl<'a> From<&'a Path> for Visitor<'a, FsResolver> {
fn from(path: &'a Path) -> Self {
Visitor::<FsResolver>::new(path, true, None)
}
}
#[cfg(test)]
mod tests {
use quote::{quote, ToTokens};
use std::path::Path;
use syn::visit_mut::VisitMut;
use super::Visitor;
use crate::PathCommentResolver;
#[test]
fn ident_in_lib() {
let path = Path::new("./lib.rs");
let mut visitor = Visitor::<PathCommentResolver>::new(&path, true, None);
let mut file = syn::parse_file("mod c;").unwrap();
visitor.visit_file_mut(&mut file);
assert_eq!(
file.into_token_stream().to_string(),
quote! {
mod c {
const PATH: &str = "./c.rs";
}
}
.to_string()
);
}
#[test]
fn path_attr() {
let path = std::path::Path::new("./lib.rs");
let mut visitor = Visitor::<PathCommentResolver>::new(&path, true, None);
let mut file = syn::parse_file(r#"#[path = "foo/bar.rs"] mod c;"#).unwrap();
visitor.visit_file_mut(&mut file);
assert_eq!(
file.into_token_stream().to_string(),
quote! {
#[path = "foo/bar.rs"]
mod c {
const PATH: &str = "./foo/bar.rs";
}
}
.to_string()
);
}
}