use syn::{ItemUse, UseTree};
use crate::{is_relative_path_keyword, ImportExtraction, ImportInfo};
pub(crate) fn imports_from_use(item_use: &ItemUse) -> ImportExtraction {
extract_imports_from_tree(&item_use.tree, "")
}
fn extract_imports_from_tree(tree: &UseTree, prefix: &str) -> ImportExtraction {
match tree {
UseTree::Path(path) => {
let is_relative =
prefix.is_empty() && is_relative_path_keyword(&path.ident.to_string());
let new_prefix = if prefix.is_empty() {
path.ident.to_string()
} else {
format!("{prefix}::{}", path.ident)
};
let mut extraction = extract_imports_from_tree(&path.tree, &new_prefix);
extraction.has_relative = extraction.has_relative || is_relative;
extraction
}
UseTree::Name(name) => {
let full_path = if prefix.is_empty() {
name.ident.to_string()
} else {
format!("{prefix}::{}", name.ident)
};
ImportExtraction {
imports: vec![ImportInfo {
name: name.ident.to_string(),
path: full_path,
}],
has_glob: false,
has_relative: false,
}
}
UseTree::Rename(rename) => {
let full_path = if prefix.is_empty() {
rename.ident.to_string()
} else {
format!("{prefix}::{}", rename.ident)
};
ImportExtraction {
imports: vec![ImportInfo {
name: rename.rename.to_string(),
path: full_path,
}],
has_glob: false,
has_relative: false,
}
}
UseTree::Glob(_) => {
ImportExtraction {
imports: vec![],
has_glob: true,
has_relative: false,
}
}
UseTree::Group(group) => {
let mut imports = Vec::new();
let mut has_glob = false;
let mut has_relative = false;
for item in &group.items {
let extraction = extract_imports_from_tree(item, prefix);
imports.extend(extraction.imports);
has_glob = has_glob || extraction.has_glob;
has_relative = has_relative || extraction.has_relative;
}
ImportExtraction {
imports,
has_glob,
has_relative,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_imports_simple() {
let use_stmt: ItemUse = syn::parse_quote! {
use evm_core::standard_bridge::SetU64;
};
let extraction = imports_from_use(&use_stmt);
assert_eq!(extraction.imports.len(), 1);
assert_eq!(extraction.imports[0].name, "SetU64");
assert_eq!(
extraction.imports[0].path,
"evm_core::standard_bridge::SetU64"
);
assert!(!extraction.has_glob);
assert!(!extraction.has_relative);
}
#[test]
fn test_extract_imports_renamed() {
let use_stmt: ItemUse = syn::parse_quote! {
use dusk_core::Address as DSAddress;
};
let extraction = imports_from_use(&use_stmt);
assert_eq!(extraction.imports.len(), 1);
assert_eq!(extraction.imports[0].name, "DSAddress");
assert_eq!(extraction.imports[0].path, "dusk_core::Address");
assert!(!extraction.has_glob);
assert!(!extraction.has_relative);
}
#[test]
fn test_extract_imports_group() {
let use_stmt: ItemUse = syn::parse_quote! {
use evm_core::standard_bridge::{SetU64, Deposit, EVMAddress};
};
let extraction = imports_from_use(&use_stmt);
assert_eq!(extraction.imports.len(), 3);
assert!(!extraction.has_glob);
assert!(!extraction.has_relative);
let names: Vec<_> = extraction.imports.iter().map(|i| i.name.as_str()).collect();
assert!(names.contains(&"SetU64"));
assert!(names.contains(&"Deposit"));
assert!(names.contains(&"EVMAddress"));
let set_u64 = extraction
.imports
.iter()
.find(|i| i.name == "SetU64")
.unwrap();
assert_eq!(set_u64.path, "evm_core::standard_bridge::SetU64");
}
#[test]
fn test_extract_imports_glob() {
let use_stmt: ItemUse = syn::parse_quote! {
use evm_core::standard_bridge::*;
};
let extraction = imports_from_use(&use_stmt);
assert!(extraction.imports.is_empty());
assert!(extraction.has_glob);
assert!(!extraction.has_relative);
}
#[test]
fn test_extract_imports_group_with_glob() {
let use_stmt: ItemUse = syn::parse_quote! {
use evm_core::standard_bridge::{SetU64, events::*};
};
let extraction = imports_from_use(&use_stmt);
assert_eq!(extraction.imports.len(), 1);
assert_eq!(extraction.imports[0].name, "SetU64");
assert!(extraction.has_glob);
assert!(!extraction.has_relative);
}
#[test]
fn test_extract_imports_relative_self() {
let use_stmt: ItemUse = syn::parse_quote! {
use self::types::MyType;
};
let extraction = imports_from_use(&use_stmt);
assert_eq!(extraction.imports.len(), 1);
assert_eq!(extraction.imports[0].name, "MyType");
assert_eq!(extraction.imports[0].path, "self::types::MyType");
assert!(!extraction.has_glob);
assert!(extraction.has_relative);
}
#[test]
fn test_extract_imports_relative_super() {
let use_stmt: ItemUse = syn::parse_quote! {
use super::common::SharedType;
};
let extraction = imports_from_use(&use_stmt);
assert_eq!(extraction.imports.len(), 1);
assert_eq!(extraction.imports[0].name, "SharedType");
assert_eq!(extraction.imports[0].path, "super::common::SharedType");
assert!(!extraction.has_glob);
assert!(extraction.has_relative);
}
#[test]
fn test_extract_imports_relative_crate() {
let use_stmt: ItemUse = syn::parse_quote! {
use crate::utils::Helper;
};
let extraction = imports_from_use(&use_stmt);
assert_eq!(extraction.imports.len(), 1);
assert_eq!(extraction.imports[0].name, "Helper");
assert_eq!(extraction.imports[0].path, "crate::utils::Helper");
assert!(!extraction.has_glob);
assert!(extraction.has_relative);
}
#[test]
fn test_extract_imports_group_with_relative() {
let use_stmt: ItemUse = syn::parse_quote! {
use self::types::{TypeA, TypeB};
};
let extraction = imports_from_use(&use_stmt);
assert_eq!(extraction.imports.len(), 2);
assert!(!extraction.has_glob);
assert!(extraction.has_relative);
}
}