use failure::{bail, Fallible};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use syn::visit::Visit;
#[derive(Serialize, Deserialize, Debug)]
pub(crate) enum SourceDependency {
Mod(String),
}
pub(crate) fn get_dependencies(contents: &syn::File) -> Vec<SourceDependency> {
let mut visitor = Visitor::new();
visitor.visit_file(contents);
visitor.deps
}
pub(crate) fn resolve_dependency(source_path: &Path, dep: &SourceDependency) -> Fallible<PathBuf> {
match dep {
SourceDependency::Mod(module_name) => find_module(source_path, &module_name),
}
}
fn find_module(source_path: &Path, module_name: &str) -> Fallible<PathBuf> {
let mut path = source_path.to_owned();
path.pop();
path.push(format!("{}.rs", module_name));
if path.exists() {
return Ok(path);
}
path.pop();
path.push(format!("{}/mod.rs", module_name));
if path.exists() {
return Ok(path);
}
bail!("Unable to locate source code for module '{}'", module_name);
}
struct Visitor {
deps: Vec<SourceDependency>,
}
impl Visitor {
fn new() -> Visitor {
Visitor { deps: vec![] }
}
}
impl<'ast> Visit<'ast> for Visitor {
fn visit_item_mod(&mut self, i: &'ast syn::ItemMod) {
syn::visit::visit_item_mod(self, i);
if i.content == None {
let mut module_name = i.ident.to_string();
let module_name = if module_name.starts_with("r#") {
module_name.split_off(2)
} else {
module_name
};
self.deps.push(SourceDependency::Mod(module_name));
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::testdata::*;
use failure::Error;
use std::fs::File;
use std::io::Read;
fn parse(source_path: &Path) -> Fallible<syn::File> {
let mut file = File::open(source_path)?;
let mut src = String::new();
file.read_to_string(&mut src)?;
Ok(syn::parse_file(&src)?)
}
fn find_sources(entrypoint: &Path) -> (Vec<PathBuf>, Vec<(PathBuf, Error)>) {
let mut source_paths: Vec<PathBuf> = Vec::new();
let mut source_errors: Vec<(PathBuf, Error)> = Vec::new();
match parse(entrypoint) {
Ok(ast) => {
source_paths.push(entrypoint.to_owned());
let deps = get_dependencies(&ast);
for dep in deps {
match resolve_dependency(entrypoint, &dep) {
Ok(dep_path) => {
let (mut dep_paths, mut dep_errors) = find_sources(&dep_path);
source_paths.append(&mut dep_paths);
source_errors.append(&mut dep_errors);
}
Err(e) => source_errors.push((entrypoint.to_owned(), e)),
}
}
}
Err(e) => source_errors.push((entrypoint.to_owned(), e)),
}
(source_paths, source_errors)
}
#[test]
fn test_case() {
for case in TEST_CRATES.iter() {
for target in case.targets.iter() {
let entrypoint = case.root_directory.join(target.entrypoint);
let (paths, errors) = find_sources(&entrypoint);
let mut expected_paths = vec![entrypoint];
let mut additional_paths: Vec<_> = target
.additional_source_files
.iter()
.map(|p| case.root_directory.join(p))
.collect();
expected_paths.append(&mut additional_paths);
assert_eq!(expected_paths, paths);
let unexpected_errors = errors
.iter()
.filter(|(file, error_msg)| {
!target
.expected_errors
.iter()
.any(|(expected_file, expected_substring)| {
&case.root_directory.join(expected_file) == file
&& error_msg.to_string().contains(expected_substring)
})
})
.map(|(file, error_msg)| format!("{}: {}", file.to_str().unwrap(), error_msg))
.collect::<Vec<_>>();
assert_eq!(
Vec::<String>::new(),
unexpected_errors,
"Some unexpected errors were reported"
);
let missing_errors = target
.expected_errors
.iter()
.filter(|(expected_file, expected_substring)| {
!errors.iter().any(|(file, error_msg)| {
&case.root_directory.join(expected_file) == file
&& error_msg.to_string().contains(expected_substring)
})
})
.map(|(expected_file, expected_substring)| {
format!("{}: {}", expected_file, expected_substring)
})
.collect::<Vec<_>>();
assert_eq!(
Vec::<String>::new(),
missing_errors,
"Some expected errors were not reported"
);
}
}
}
}