use {
super::common::{find_path, get_program_path},
crate::parser::context::CrateContext,
anyhow::{anyhow, Result},
cargo_toml::Manifest,
quote::ToTokens,
std::{
env, fs,
path::{Path, PathBuf},
},
};
pub fn get_external_type(name: &str, path: impl AsRef<Path>) -> Result<Option<syn::Type>> {
let use_path = get_uses(path.as_ref())?
.into_iter()
.find(|u| u.split("::").last().unwrap() == name)
.ok_or_else(|| anyhow!("`{name}` not found in use statements"))?;
let program_path = get_program_path()?;
let lock_path = find_path("Cargo.lock", program_path)?;
let lock_file = parse_lock_file(lock_path)?;
let registry_path = get_registry_path()?;
recursively_find_type(name, &use_path, ®istry_path, &lock_file)
}
fn recursively_find_type(
defined_name: &str,
use_path: &str,
registry_path: &Path,
lock_file: &[(String, String)],
) -> Result<Option<syn::Type>> {
let crate_name = use_path.split("::").next().unwrap();
let (crate_name, version) = lock_file
.iter()
.find(|(name, _)| name == crate_name || name == &crate_name.replace('_', "-"))
.ok_or_else(|| anyhow!("Crate should exist in the lock file"))?;
let crate_path = registry_path.join(format!("{crate_name}-{version}"));
let lib_path = crate_path.join("src").join("lib.rs");
let ctx = CrateContext::parse(&lib_path)?;
let alias = ctx.type_aliases().find(|item| item.ident == defined_name);
match alias {
Some(alias) => Ok(Some(*alias.ty.to_owned())),
None => {
let cargo_toml_path = find_path("Cargo.toml", &lib_path)?;
let deps = Manifest::from_path(cargo_toml_path)?.dependencies;
let paths = use_path.split("::").skip(1).collect::<Vec<_>>();
let paths = paths.iter().enumerate().filter_map(|(i, path)| {
if deps.contains_key(*path) || deps.contains_key(&path.replace('_', "-")) {
Some(paths.iter().skip(i).copied().collect::<Vec<_>>().join("::"))
} else {
None
}
});
for path in paths {
let result = recursively_find_type(defined_name, &path, registry_path, lock_file);
if result.is_ok() {
return result;
}
}
Ok(None)
}
}
}
fn get_registry_path() -> Result<PathBuf> {
#[allow(deprecated)]
let path = env::home_dir()
.unwrap()
.join(".cargo")
.join("registry")
.join("src");
fs::read_dir(&path)?
.filter_map(|entry| entry.ok())
.find_map(|entry| {
let file_name = entry.file_name();
if file_name.to_string_lossy().starts_with("index.crates.io") {
Some(file_name)
} else {
None
}
})
.map(|name| path.join(name))
.ok_or_else(|| anyhow!("crates.io registry not found"))
}
fn parse_lock_file(path: impl AsRef<Path>) -> Result<Vec<(String, String)>> {
let parsed = fs::read_to_string(path.as_ref())?
.split("[[package]]")
.skip(1)
.map(|pkg| {
let get_value = |key: &str| -> String {
pkg.lines()
.find(|line| line.starts_with(key))
.expect(&format!("`{key}` line not found"))
.split('"')
.nth(1)
.unwrap()
.to_owned()
};
let name = get_value("name");
let version = get_value("version");
(name, version)
})
.collect::<Vec<_>>();
Ok(parsed)
}
fn get_uses(path: impl AsRef<Path>) -> Result<Vec<String>> {
let content = fs::read_to_string(path.as_ref())?;
let uses = syn::parse_file(&content)?
.items
.into_iter()
.filter_map(|item| match item {
syn::Item::Use(u) => Some(flatten_uses(&u.tree)),
_ => None,
})
.flatten()
.collect::<Vec<_>>();
Ok(uses)
}
fn flatten_uses(tree: &syn::UseTree) -> Vec<String> {
match tree {
syn::UseTree::Group(group) => group.items.iter().flat_map(flatten_uses).collect(),
syn::UseTree::Path(path) => flatten_uses(&path.tree)
.into_iter()
.map(|item| format!("{}::{}", path.ident, item))
.collect(),
syn::UseTree::Glob(glob) => {
vec![format!("{}", glob.star_token.to_token_stream().to_string())]
}
syn::UseTree::Name(name) => vec![name.ident.to_string()],
syn::UseTree::Rename(rename) => vec![rename.ident.to_string()],
}
}