use std::collections::{HashMap, HashSet};
use proc_macro2::{Spacing, TokenStream, TokenTree};
use syn::visit::Visit;
use syn::{ItemMod, ItemUse, UseTree};
use tracing::debug;
use crate::constants::{PATH_QUALIFIER_CRATE, PATH_QUALIFIER_SELF, PATH_QUALIFIER_SUPER};
use crate::reference::{GroupItem, PathPrefix, TypeReference};
use crate::utils::has_cfg_test;
pub(crate) fn resolve_reference(reference: TypeReference, module_path: &[String]) -> TypeReference {
match reference.prefix() {
PathPrefix::SelfModule => {
let mut new_segments = module_path.to_vec();
new_segments.extend(reference.segments().iter().cloned());
reference.with_segments_and_prefix(new_segments, PathPrefix::Crate)
}
PathPrefix::Super(levels) => {
if module_path.len() >= levels {
let parent_depth = module_path.len() - levels;
let mut new_segments = module_path[..parent_depth].to_vec();
new_segments.extend(reference.segments().iter().cloned());
reference.with_segments_and_prefix(new_segments, PathPrefix::Crate)
} else {
debug!(
"Cannot resolve super::{} — {levels} levels from module {module_path:?}",
reference.segments().join("::")
);
reference
}
}
PathPrefix::Crate | PathPrefix::None => reference,
}
}
pub(super) struct CollectedReferences {
pub use_statements: Vec<TypeReference>,
pub type_refs: Vec<TypeReference>,
pub value_refs: Vec<TypeReference>,
pub macro_calls: Vec<TypeReference>,
}
impl CollectedReferences {
const fn new() -> Self {
Self {
use_statements: Vec::new(),
type_refs: Vec::new(),
value_refs: Vec::new(),
macro_calls: Vec::new(),
}
}
pub(super) fn all(&self) -> impl Iterator<Item = &TypeReference> {
self.use_statements
.iter()
.chain(self.type_refs.iter())
.chain(self.value_refs.iter())
.chain(self.macro_calls.iter())
}
}
pub(super) struct ModuleVisitor {
module_name: String,
module_path: Vec<String>,
children: HashSet<String>,
pub(super) references: CollectedReferences,
in_test_module: bool,
package_name: Option<String>,
imported_modules: HashMap<String, (PathPrefix, Vec<String>)>,
}
impl ModuleVisitor {
pub(super) fn new(
module_name: impl Into<String>,
children: HashSet<String>,
package_name: Option<String>,
) -> Self {
let module_name = module_name.into();
let module_path: Vec<String> = if module_name.is_empty() {
vec![]
} else {
module_name.split("::").map(String::from).collect()
};
Self {
module_name,
module_path,
children,
references: CollectedReferences::new(),
in_test_module: false,
package_name,
imported_modules: HashMap::new(),
}
}
fn is_internal_path(&self, path: &syn::Path) -> bool {
path.segments.first().is_some_and(|first_segment| {
let ident = first_segment.ident.to_string();
matches!(
ident.as_str(),
PATH_QUALIFIER_CRATE | PATH_QUALIFIER_SELF | PATH_QUALIFIER_SUPER
) || self.children.contains(&ident)
})
}
fn build_reference(&self, path: &syn::Path) -> Option<TypeReference> {
if !self.is_internal_path(path) {
return None;
}
let mut segments = Vec::new();
let mut path_prefix = PathPrefix::None;
for (i, segment) in path.segments.iter().enumerate() {
let ident = segment.ident.to_string();
if i == 0 {
match ident.as_str() {
PATH_QUALIFIER_CRATE => {
path_prefix = PathPrefix::Crate;
continue;
}
PATH_QUALIFIER_SELF => {
path_prefix = PathPrefix::SelfModule;
continue;
}
PATH_QUALIFIER_SUPER => {
path_prefix = PathPrefix::Super(1);
continue;
}
_ => {}
}
} else if ident == PATH_QUALIFIER_SUPER {
let levels = match path_prefix {
PathPrefix::Super(n) => n + 1,
_ => 1,
};
path_prefix = PathPrefix::Super(levels);
continue;
}
segments.push(ident);
}
if segments.is_empty() {
return None;
}
Some(resolve_reference(
TypeReference::new(segments).with_prefix(path_prefix),
&self.module_path,
))
}
fn build_reference_from_segments(&self, segments: &[String]) -> Option<TypeReference> {
if segments.is_empty() {
return None;
}
let first = &segments[0];
let is_internal = matches!(
first.as_str(),
PATH_QUALIFIER_CRATE | PATH_QUALIFIER_SELF | PATH_QUALIFIER_SUPER
) || self.children.contains(first);
if !is_internal {
return None;
}
let mut prefix = PathPrefix::None;
let mut start = 0;
match segments[0].as_str() {
PATH_QUALIFIER_CRATE => {
prefix = PathPrefix::Crate;
start = 1;
}
PATH_QUALIFIER_SELF => {
prefix = PathPrefix::SelfModule;
start = 1;
}
PATH_QUALIFIER_SUPER => {
let levels = segments
.iter()
.take_while(|s| s.as_str() == PATH_QUALIFIER_SUPER)
.count();
prefix = PathPrefix::Super(levels);
start = levels;
}
_ => {}
}
let real_segments: Vec<String> = segments[start..].to_vec();
if real_segments.is_empty() {
return None;
}
Some(resolve_reference(
TypeReference::new(real_segments).with_prefix(prefix),
&self.module_path,
))
}
fn try_record_import(
&mut self,
imported_name: String,
segments: &[String],
prefix: PathPrefix,
) {
let entry = match prefix {
PathPrefix::Crate => Some((PathPrefix::Crate, segments.to_vec())),
PathPrefix::SelfModule => {
let mut abs = self.module_path.clone();
abs.extend(segments.iter().cloned());
Some((PathPrefix::Crate, abs))
}
PathPrefix::Super(levels) => {
if self.module_path.len() >= levels {
let mut abs = self.module_path[..self.module_path.len() - levels].to_vec();
abs.extend(segments.iter().cloned());
Some((PathPrefix::Crate, abs))
} else {
None
}
}
PathPrefix::None => self.package_name.as_ref().and_then(|pkg| {
if segments.first().is_some_and(|s| s == pkg) {
Some((PathPrefix::None, segments.to_vec()))
} else {
None
}
}),
};
if let Some((pfx, path)) = entry {
if !path.is_empty() {
self.imported_modules.insert(imported_name, (pfx, path));
}
}
}
fn resolve_via_import(&self, segments: &[String]) -> Option<TypeReference> {
let (prefix, imported_path) = self.imported_modules.get(&segments[0])?;
let mut full_segments = imported_path.clone();
full_segments.extend(segments[1..].iter().cloned());
Some(TypeReference::new(full_segments).with_prefix(*prefix))
}
fn extract_paths_from_tokens(&mut self, tokens: &TokenStream) {
let mut iter = tokens.clone().into_iter().peekable();
while let Some(token) = iter.next() {
match token {
TokenTree::Ident(ref ident) => {
let mut segments = vec![ident.to_string()];
loop {
let is_path_sep = matches!(
iter.peek(),
Some(TokenTree::Punct(p)) if p.as_char() == ':' && p.spacing() == Spacing::Joint
);
if !is_path_sep {
break;
}
iter.next(); iter.next();
if let Some(TokenTree::Ident(next_ident)) = iter.peek() {
segments.push(next_ident.to_string());
iter.next(); } else {
break;
}
}
if segments.len() >= 2 {
if let Some(r) = self
.build_reference_from_segments(&segments)
.or_else(|| self.resolve_via_import(&segments))
{
self.references.value_refs.push(r);
}
}
}
TokenTree::Group(group) => {
self.extract_paths_from_tokens(&group.stream());
}
_ => {}
}
}
}
fn process_use_tree(&mut self, tree: &UseTree, prefix: Vec<String>, path_prefix: PathPrefix) {
match tree {
UseTree::Path(p) => {
let ident = p.ident.to_string();
let (new_prefix, new_path_prefix) = if prefix.is_empty() {
match ident.as_str() {
PATH_QUALIFIER_CRATE => (Vec::new(), PathPrefix::Crate),
PATH_QUALIFIER_SELF => (Vec::new(), PathPrefix::SelfModule),
PATH_QUALIFIER_SUPER => {
let levels = match path_prefix {
PathPrefix::Super(n) => n + 1,
_ => 1,
};
(Vec::new(), PathPrefix::Super(levels))
}
_ => {
let mut new_prefix = prefix;
new_prefix.push(ident);
(new_prefix, path_prefix)
}
}
} else if ident == PATH_QUALIFIER_SUPER {
let levels = match path_prefix {
PathPrefix::Super(n) => n + 1,
_ => 1,
};
(prefix, PathPrefix::Super(levels))
} else {
let mut new_prefix = prefix;
new_prefix.push(ident);
(new_prefix, path_prefix)
};
self.process_use_tree(&p.tree, new_prefix, new_path_prefix);
}
UseTree::Name(n) => {
let imported_name = n.ident.to_string();
let mut segments = prefix;
segments.push(imported_name.clone());
self.try_record_import(imported_name, &segments, path_prefix);
let reference = resolve_reference(
TypeReference::new(segments).with_prefix(path_prefix),
&self.module_path,
);
self.references.use_statements.push(reference);
}
UseTree::Rename(r) => {
let alias = r.rename.to_string();
let mut segments = prefix;
segments.push(r.ident.to_string());
self.try_record_import(alias.clone(), &segments, path_prefix);
let reference = resolve_reference(
TypeReference::new(segments)
.with_prefix(path_prefix)
.with_alias(alias),
&self.module_path,
);
self.references.use_statements.push(reference);
}
UseTree::Glob(_) => {
let reference = resolve_reference(
TypeReference::new(prefix)
.with_prefix(path_prefix)
.with_glob(),
&self.module_path,
);
self.references.use_statements.push(reference);
}
UseTree::Group(g) => {
for item in &g.items {
match item {
UseTree::Name(n) => {
let name = n.ident.to_string();
let mut item_segments = prefix.clone();
item_segments.push(name.clone());
self.try_record_import(name, &item_segments, path_prefix);
}
UseTree::Rename(r) => {
let alias = r.rename.to_string();
let mut item_segments = prefix.clone();
item_segments.push(r.ident.to_string());
self.try_record_import(alias, &item_segments, path_prefix);
}
_ => {}
}
}
let group_items = self.convert_group(&g.items);
let reference = resolve_reference(
TypeReference::new(prefix)
.with_prefix(path_prefix)
.with_group(group_items),
&self.module_path,
);
self.references.use_statements.push(reference);
}
}
}
fn convert_group(
&self,
items: &syn::punctuated::Punctuated<UseTree, syn::token::Comma>,
) -> Vec<GroupItem> {
items
.iter()
.map(|item| self.convert_use_tree(item))
.collect()
}
fn convert_use_tree(&self, tree: &UseTree) -> GroupItem {
match tree {
UseTree::Name(n) => {
let ident = n.ident.to_string();
if ident == PATH_QUALIFIER_SELF {
GroupItem::SelfItem { alias: None }
} else {
GroupItem::Simple(ident)
}
}
UseTree::Rename(r) => {
let ident = r.ident.to_string();
let alias = r.rename.to_string();
if ident == PATH_QUALIFIER_SELF {
GroupItem::SelfItem { alias: Some(alias) }
} else {
GroupItem::Aliased { name: ident, alias }
}
}
UseTree::Glob(_) => GroupItem::Glob,
UseTree::Path(p) => {
let mut prefix = vec![p.ident.to_string()];
let mut current = &*p.tree;
while let UseTree::Path(inner) = current {
prefix.push(inner.ident.to_string());
current = &*inner.tree;
}
match current {
UseTree::Group(g) => GroupItem::Nested {
prefix,
items: self.convert_group(&g.items),
},
UseTree::Name(n) => GroupItem::Nested {
prefix,
items: vec![GroupItem::Simple(n.ident.to_string())],
},
UseTree::Rename(r) => {
prefix.push(r.ident.to_string());
GroupItem::Nested {
prefix: prefix[..prefix.len() - 1].to_vec(),
items: vec![GroupItem::Aliased {
name: r.ident.to_string(),
alias: r.rename.to_string(),
}],
}
}
UseTree::Glob(_) => GroupItem::Nested {
prefix,
items: vec![GroupItem::Glob],
},
UseTree::Path(_) => GroupItem::Nested {
prefix,
items: Vec::new(),
},
}
}
UseTree::Group(g) => GroupItem::Nested {
prefix: Vec::new(),
items: self.convert_group(&g.items),
},
}
}
}
impl<'ast> Visit<'ast> for ModuleVisitor {
fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
if !self.in_test_module {
if let Some(r) = self.build_reference(&node.path) {
self.references.value_refs.push(r);
}
}
syn::visit::visit_expr_path(self, node);
}
fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
if !self.in_test_module {
if let Some(r) = self.build_reference(&node.path) {
self.references.value_refs.push(r);
}
}
syn::visit::visit_expr_struct(self, node);
}
fn visit_item_impl(&mut self, node: &'ast syn::ItemImpl) {
if !self.in_test_module {
if let Some((_, trait_path, _)) = &node.trait_ {
if let Some(r) = self.build_reference(trait_path) {
self.references.type_refs.push(r);
}
}
}
syn::visit::visit_item_impl(self, node);
}
fn visit_item_mod(&mut self, i: &'ast ItemMod) {
let was_in_test = self.in_test_module;
if has_cfg_test(&i.attrs) {
self.in_test_module = true;
}
let module_ident = i.ident.to_string();
let should_visit = if self.module_name.is_empty() {
true
} else if self.module_name == module_ident {
true
} else if self.module_name.contains("::") {
self.module_name.split("::").any(|seg| seg == module_ident)
|| self.module_name.ends_with(&format!("::{module_ident}"))
} else {
self.module_name == module_ident
};
if !should_visit {
debug!(
"Skipping module '{module_ident}' (filter: '{}')",
self.module_name
);
}
if should_visit && let Some((_, items)) = &i.content {
for item in items {
self.visit_item(item);
}
}
self.in_test_module = was_in_test;
}
fn visit_item_use(&mut self, node: &'ast ItemUse) {
if !self.in_test_module {
self.process_use_tree(&node.tree, Vec::new(), PathPrefix::None);
}
}
fn visit_attribute(&mut self, attr: &'ast syn::Attribute) {
if !self.in_test_module {
if let syn::Meta::List(meta_list) = &attr.meta {
self.extract_paths_from_tokens(&meta_list.tokens);
}
}
syn::visit::visit_attribute(self, attr);
}
fn visit_macro(&mut self, node: &'ast syn::Macro) {
if !self.in_test_module {
if let Some(r) = self.build_reference(&node.path) {
self.references.macro_calls.push(r);
}
self.extract_paths_from_tokens(&node.tokens);
}
syn::visit::visit_macro(self, node);
}
fn visit_pat_struct(&mut self, node: &'ast syn::PatStruct) {
if !self.in_test_module {
if let Some(r) = self.build_reference(&node.path) {
self.references.value_refs.push(r);
}
}
syn::visit::visit_pat_struct(self, node);
}
fn visit_pat_tuple_struct(&mut self, node: &'ast syn::PatTupleStruct) {
if !self.in_test_module {
if let Some(r) = self.build_reference(&node.path) {
self.references.value_refs.push(r);
}
}
syn::visit::visit_pat_tuple_struct(self, node);
}
fn visit_trait_bound(&mut self, node: &'ast syn::TraitBound) {
if !self.in_test_module {
if let Some(r) = self.build_reference(&node.path) {
self.references.type_refs.push(r);
}
}
syn::visit::visit_trait_bound(self, node);
}
fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
if !self.in_test_module {
if let Some(r) = self.build_reference(&node.path) {
self.references.type_refs.push(r);
}
if let Some(qself) = &node.qself {
syn::visit::visit_type(self, &qself.ty);
}
}
syn::visit::visit_type_path(self, node);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolve_self_prefix() {
let r = TypeReference::new(["foo", "Bar"]).with_self_prefix();
let module_path = vec!["utils".to_owned(), "parser".to_owned()];
let resolved = resolve_reference(r, &module_path);
assert_eq!(resolved.prefix(), PathPrefix::Crate);
assert_eq!(resolved.segments(), &["utils", "parser", "foo", "Bar"]);
assert_eq!(resolved.to_path_string(), "crate::utils::parser::foo::Bar");
}
#[test]
fn test_resolve_self_prefix_at_crate_root() {
let r = TypeReference::new(["foo", "Bar"]).with_self_prefix();
let module_path: Vec<String> = vec![];
let resolved = resolve_reference(r, &module_path);
assert_eq!(resolved.prefix(), PathPrefix::Crate);
assert_eq!(resolved.segments(), &["foo", "Bar"]);
assert_eq!(resolved.to_path_string(), "crate::foo::Bar");
}
#[test]
fn test_resolve_super_single_level() {
let r = TypeReference::new(["sibling", "Type"]).with_super(1);
let module_path = vec!["parent".to_owned(), "child".to_owned()];
let resolved = resolve_reference(r, &module_path);
assert_eq!(resolved.prefix(), PathPrefix::Crate);
assert_eq!(resolved.segments(), &["parent", "sibling", "Type"]);
assert_eq!(resolved.to_path_string(), "crate::parent::sibling::Type");
}
#[test]
fn test_resolve_super_multiple_levels() {
let r = TypeReference::new(["ancestor", "Type"]).with_super(2);
let module_path = vec!["a".to_owned(), "b".to_owned(), "c".to_owned()];
let resolved = resolve_reference(r, &module_path);
assert_eq!(resolved.prefix(), PathPrefix::Crate);
assert_eq!(resolved.segments(), &["a", "ancestor", "Type"]);
assert_eq!(resolved.to_path_string(), "crate::a::ancestor::Type");
}
#[test]
fn test_resolve_super_at_crate_root() {
let r = TypeReference::new(["foo", "Bar"]).with_super(1);
let module_path: Vec<String> = vec![];
let resolved = resolve_reference(r, &module_path);
assert_eq!(resolved.prefix(), PathPrefix::Super(1));
assert_eq!(resolved.segments(), &["foo", "Bar"]);
}
#[test]
fn test_resolve_super_too_many_levels() {
let r = TypeReference::new(["foo", "Bar"]).with_super(5);
let module_path = vec!["a".to_owned(), "b".to_owned()];
let resolved = resolve_reference(r, &module_path);
assert_eq!(resolved.prefix(), PathPrefix::Super(5));
assert_eq!(resolved.segments(), &["foo", "Bar"]);
}
#[test]
fn test_resolve_crate_prefix_unchanged() {
let r = TypeReference::new(["module", "Type"]).with_crate_prefix();
let module_path = vec!["utils".to_owned()];
let resolved = resolve_reference(r, &module_path);
assert_eq!(resolved.prefix(), PathPrefix::Crate);
assert_eq!(resolved.segments(), &["module", "Type"]);
assert_eq!(resolved.to_path_string(), "crate::module::Type");
}
#[test]
fn test_resolve_no_prefix_unchanged() {
let r = TypeReference::new(["std", "collections", "HashMap"]);
let module_path = vec!["utils".to_owned()];
let resolved = resolve_reference(r, &module_path);
assert_eq!(resolved.prefix(), PathPrefix::None);
assert_eq!(resolved.segments(), &["std", "collections", "HashMap"]);
assert_eq!(resolved.to_path_string(), "std::collections::HashMap");
}
#[test]
fn test_resolve_preserves_suffix() {
let r = TypeReference::new(["foo", "Bar"])
.with_self_prefix()
.with_alias("MyBar");
let module_path = vec!["utils".to_owned()];
let resolved = resolve_reference(r, &module_path);
assert_eq!(resolved.to_path_string(), "crate::utils::foo::Bar as MyBar");
}
#[test]
fn test_resolve_with_glob() {
let r = TypeReference::new(["foo"]).with_self_prefix().with_glob();
let module_path = vec!["utils".to_owned()];
let resolved = resolve_reference(r, &module_path);
assert_eq!(resolved.to_path_string(), "crate::utils::foo::*");
}
fn visitor_with_children(children: &[&str]) -> ModuleVisitor {
let children = children.iter().map(|s| (*s).to_owned()).collect();
ModuleVisitor::new("test_mod", children, None)
}
fn tokens(code: &str) -> TokenStream {
code.parse().expect("failed to parse token stream")
}
#[test]
fn test_extract_crate_path_from_tokens() {
let mut v = visitor_with_children(&[]);
v.extract_paths_from_tokens(&tokens("foo, crate::version::VERSION, bar"));
assert_eq!(v.references.value_refs.len(), 1);
assert_eq!(
v.references.value_refs[0].to_path_string(),
"crate::version::VERSION"
);
}
#[test]
fn test_extract_child_module_path_from_tokens() {
let mut v = visitor_with_children(&["version"]);
v.extract_paths_from_tokens(&tokens("version::NAME"));
assert_eq!(v.references.value_refs.len(), 1);
assert_eq!(v.references.value_refs[0].to_path_string(), "version::NAME");
}
#[test]
fn test_extract_skips_external_paths() {
let mut v = visitor_with_children(&["version"]);
v.extract_paths_from_tokens(&tokens("std::fmt::Display, tracing::info"));
assert!(v.references.value_refs.is_empty());
}
#[test]
fn test_extract_skips_lone_ident() {
let mut v = visitor_with_children(&["version"]);
v.extract_paths_from_tokens(&tokens("version"));
assert!(v.references.value_refs.is_empty());
}
#[test]
fn test_extract_paths_from_nested_groups() {
let mut v = visitor_with_children(&[]);
v.extract_paths_from_tokens(&tokens("(crate::foo::bar, (crate::baz::qux))"));
assert_eq!(v.references.value_refs.len(), 2);
assert_eq!(
v.references.value_refs[0].to_path_string(),
"crate::foo::bar"
);
assert_eq!(
v.references.value_refs[1].to_path_string(),
"crate::baz::qux"
);
}
#[test]
fn test_extract_self_path_from_tokens() {
let mut v = visitor_with_children(&[]);
v.extract_paths_from_tokens(&tokens("self::utils::helper"));
assert_eq!(v.references.value_refs.len(), 1);
assert_eq!(
v.references.value_refs[0].to_path_string(),
"crate::test_mod::utils::helper"
);
}
#[test]
fn test_extract_super_path_from_tokens() {
let mut v = visitor_with_children(&[]);
v.extract_paths_from_tokens(&tokens("super::sibling::Type"));
assert_eq!(v.references.value_refs.len(), 1);
assert_eq!(
v.references.value_refs[0].to_path_string(),
"crate::sibling::Type"
);
}
#[test]
fn test_extract_multiple_paths_from_tokens() {
let mut v = visitor_with_children(&["version"]);
v.extract_paths_from_tokens(&tokens("\"fmt\", version::NAME, version::VERSION"));
assert_eq!(v.references.value_refs.len(), 2);
}
#[test]
fn test_extract_preserves_duplicates() {
let mut v = visitor_with_children(&["version"]);
v.extract_paths_from_tokens(&tokens("version::NAME, version::NAME"));
assert_eq!(v.references.value_refs.len(), 2);
}
fn visitor_with_package(package: &str) -> ModuleVisitor {
ModuleVisitor::new("test_mod", HashSet::new(), Some(package.to_owned()))
}
#[test]
fn test_import_resolution_via_package_name() {
let code = r#"
use myapp::version;
fn foo() {
info!("{}", version::NAME);
}
"#;
let syntax: syn::File = syn::parse_file(code).expect("parse");
let mut v = visitor_with_package("myapp");
v.visit_file(&syntax);
let value_paths: Vec<String> = v
.references
.value_refs
.iter()
.map(TypeReference::to_path_string)
.collect();
assert!(
value_paths.contains(&"myapp::version::NAME".to_owned()),
"Expected myapp::version::NAME in {value_paths:?}"
);
}
#[test]
fn test_import_resolution_via_crate_prefix() {
let code = r#"
use crate::utils;
fn foo() {
debug!("{}", utils::helper());
}
"#;
let syntax: syn::File = syn::parse_file(code).expect("parse");
let mut v = visitor_with_package("myapp");
v.visit_file(&syntax);
let value_paths: Vec<String> = v
.references
.value_refs
.iter()
.map(TypeReference::to_path_string)
.collect();
assert!(
value_paths.contains(&"crate::utils::helper".to_owned()),
"Expected crate::utils::helper in {value_paths:?}"
);
}
#[test]
fn test_import_resolution_with_alias() {
let code = r#"
use myapp::version as ver;
fn foo() {
println!("{}", ver::NAME);
}
"#;
let syntax: syn::File = syn::parse_file(code).expect("parse");
let mut v = visitor_with_package("myapp");
v.visit_file(&syntax);
let value_paths: Vec<String> = v
.references
.value_refs
.iter()
.map(TypeReference::to_path_string)
.collect();
assert!(
value_paths.contains(&"myapp::version::NAME".to_owned()),
"Expected myapp::version::NAME in {value_paths:?}"
);
}
#[test]
fn test_import_resolution_from_group() {
let code = r#"
use myapp::{version, model};
fn foo() {
info!("{}", version::NAME);
debug!("{}", model::Type);
}
"#;
let syntax: syn::File = syn::parse_file(code).expect("parse");
let mut v = visitor_with_package("myapp");
v.visit_file(&syntax);
let value_paths: Vec<String> = v
.references
.value_refs
.iter()
.map(TypeReference::to_path_string)
.collect();
assert!(
value_paths.contains(&"myapp::version::NAME".to_owned()),
"Expected myapp::version::NAME in {value_paths:?}"
);
assert!(
value_paths.contains(&"myapp::model::Type".to_owned()),
"Expected myapp::model::Type in {value_paths:?}"
);
}
#[test]
fn test_import_resolution_skips_external() {
let code = r#"
use serde::Serialize;
fn foo() {
info!("{}", Serialize::something);
}
"#;
let syntax: syn::File = syn::parse_file(code).expect("parse");
let mut v = visitor_with_package("myapp");
v.visit_file(&syntax);
assert!(
v.references.value_refs.is_empty(),
"External imports should not resolve: {:?}",
v.references
.value_refs
.iter()
.map(TypeReference::to_path_string)
.collect::<Vec<_>>()
);
}
#[test]
fn test_import_resolution_no_package_name() {
let code = r#"
use myapp::version;
fn foo() {
info!("{}", version::NAME);
}
"#;
let syntax: syn::File = syn::parse_file(code).expect("parse");
let mut v = ModuleVisitor::new("test_mod", HashSet::new(), None);
v.visit_file(&syntax);
assert!(
v.references.value_refs.is_empty(),
"Without package_name, should not resolve imported names"
);
}
}