use syn::{ItemUse, UseTree};
use super::model::ImportInfo;
pub(super) struct ImportExtraction {
pub(super) imports: Vec<ImportInfo>,
pub(super) has_glob: bool,
pub(super) has_relative: bool,
}
fn is_relative_path_keyword(ident: &str) -> bool {
matches!(ident, "self" | "super" | "crate")
}
pub(super) 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 my_crate::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, "my_crate::MyType");
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 my_crate::{MyType, MyStruct, MyAddr};
};
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(&"MyType"));
assert!(names.contains(&"MyStruct"));
assert!(names.contains(&"MyAddr"));
let my_type = extraction
.imports
.iter()
.find(|i| i.name == "MyType")
.unwrap();
assert_eq!(my_type.path, "my_crate::MyType");
}
#[test]
fn test_extract_imports_glob() {
let use_stmt: ItemUse = syn::parse_quote! {
use my_crate::*;
};
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 my_crate::{MyType, events::*};
};
let extraction = imports_from_use(&use_stmt);
assert_eq!(extraction.imports.len(), 1);
assert_eq!(extraction.imports[0].name, "MyType");
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);
}
}