cargo-mend 0.21.2

Opinionated visibility auditing for Rust crates and workspaces
use syn::UseTree;

pub(in crate::fixes) enum UseBinding {
    Named { name: String, path: String },
    Glob { path: String },
}

impl UseBinding {
    pub(in crate::fixes) fn name(&self) -> Option<&str> {
        match self {
            Self::Named { name, .. } => Some(name),
            Self::Glob { .. } => None,
        }
    }

    pub(in crate::fixes) fn path(&self) -> &str {
        match self {
            Self::Named { path, .. } | Self::Glob { path } => path,
        }
    }

    pub(in crate::fixes) fn binds_path_leaf(&self) -> bool {
        self.name()
            .is_some_and(|name| self.path().rsplit("::").next() == Some(name))
    }
}

pub(super) fn collect_use_bindings(tree: &UseTree) -> Vec<UseBinding> {
    let mut bindings = Vec::new();
    collect(tree, &mut Vec::new(), &mut bindings);
    bindings
}

fn collect(tree: &UseTree, prefix: &mut Vec<String>, bindings: &mut Vec<UseBinding>) {
    match tree {
        UseTree::Path(path) => {
            prefix.push(path.ident.to_string());
            collect(&path.tree, prefix, bindings);
            prefix.pop();
        },
        UseTree::Name(name) => {
            let name = name.ident.to_string();
            if name == "self" {
                if let Some(bound_name) = prefix.last() {
                    bindings.push(UseBinding::Named {
                        name: bound_name.clone(),
                        path: prefix.join("::"),
                    });
                }
            } else {
                let mut path = prefix.clone();
                path.push(name.clone());
                bindings.push(UseBinding::Named {
                    name,
                    path: path.join("::"),
                });
            }
        },
        UseTree::Rename(rename) => {
            let mut path = prefix.clone();
            let source_name = rename.ident.to_string();
            if source_name != "self" {
                path.push(source_name);
            }
            if !path.is_empty() {
                bindings.push(UseBinding::Named {
                    name: rename.rename.to_string(),
                    path: path.join("::"),
                });
            }
        },
        UseTree::Group(group) => {
            for item in &group.items {
                collect(item, prefix, bindings);
            }
        },
        UseTree::Glob(_) => bindings.push(UseBinding::Glob {
            path: prefix.join("::"),
        }),
    }
}