use std::collections::HashMap;
use rustdoc_types::{Crate, Id, Item, ItemEnum, Visibility};
pub struct CrateModel {
pub krate: Crate,
pub module_index: HashMap<String, Id>,
#[allow(dead_code)]
pub item_module_path: HashMap<Id, String>,
}
impl CrateModel {
pub fn from_crate(krate: Crate) -> Self {
let mut module_index = HashMap::new();
let mut item_module_path = HashMap::new();
let crate_name = krate
.index
.get(&krate.root)
.and_then(|item| item.name.as_deref())
.unwrap_or("unknown")
.to_string();
if let Some(root_item) = krate.index.get(&krate.root) {
Self::walk_modules(
&krate,
root_item,
&krate.root,
&crate_name,
&mut module_index,
&mut item_module_path,
);
}
Self {
krate,
module_index,
item_module_path,
}
}
fn walk_modules(
krate: &Crate,
item: &Item,
item_id: &Id,
current_path: &str,
module_index: &mut HashMap<String, Id>,
item_module_path: &mut HashMap<Id, String>,
) {
module_index.insert(current_path.to_string(), item_id.clone());
item_module_path.insert(item_id.clone(), current_path.to_string());
if let ItemEnum::Module(module) = &item.inner {
for child_id in &module.items {
if let Some(child_item) = krate.index.get(child_id) {
let child_path = if let Some(name) = &child_item.name {
format!("{current_path}::{name}")
} else {
continue;
};
item_module_path.insert(child_id.clone(), current_path.to_string());
if matches!(child_item.inner, ItemEnum::Module(_)) {
Self::walk_modules(
krate,
child_item,
child_id,
&child_path,
module_index,
item_module_path,
);
}
}
}
}
}
pub fn crate_name(&self) -> &str {
self.krate
.index
.get(&self.krate.root)
.and_then(|item| item.name.as_deref())
.unwrap_or("unknown")
}
pub fn find_module(&self, module_path: &str) -> Option<&Item> {
let full_path = format!("{}::{}", self.crate_name(), module_path);
let id = self
.module_index
.get(&full_path)
.or_else(|| self.module_index.get(module_path))?;
self.krate.index.get(id)
}
pub fn root_module(&self) -> Option<&Item> {
self.krate.index.get(&self.krate.root)
}
pub fn module_children<'a>(&'a self, module_item: &'a Item) -> Vec<(&'a Id, &'a Item)> {
match &module_item.inner {
ItemEnum::Module(module) => module
.items
.iter()
.filter_map(|id| self.krate.index.get(id).map(|item| (id, item)))
.collect(),
_ => vec![],
}
}
#[allow(dead_code)]
pub fn containing_module_path(&self, item_id: &Id) -> Option<&str> {
self.item_module_path.get(item_id).map(|s| s.as_str())
}
pub fn module_path(&self, module_id: &Id) -> Option<&str> {
for (path, id) in &self.module_index {
if id == module_id {
return Some(path.as_str());
}
}
None
}
pub fn is_ancestor_or_equal(ancestor_path: &str, descendant_path: &str) -> bool {
if ancestor_path == descendant_path {
return true;
}
descendant_path.starts_with(ancestor_path)
&& descendant_path.as_bytes().get(ancestor_path.len()) == Some(&b':')
}
}
pub fn is_visible_from(
model: &CrateModel,
item: &Item,
_item_id: &Id,
observer_module_path: &str,
same_crate: bool,
) -> bool {
match &item.visibility {
Visibility::Public => true,
Visibility::Crate => same_crate,
Visibility::Restricted { parent, path: _ } => {
if !same_crate {
return false;
}
if let Some(restricted_path) = model.module_path(parent) {
CrateModel::is_ancestor_or_equal(restricted_path, observer_module_path)
} else {
false
}
}
Visibility::Default => {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_ancestor_or_equal() {
assert!(CrateModel::is_ancestor_or_equal("foo", "foo"));
assert!(CrateModel::is_ancestor_or_equal("foo", "foo::bar"));
assert!(CrateModel::is_ancestor_or_equal("foo", "foo::bar::baz"));
assert!(!CrateModel::is_ancestor_or_equal("foo", "foobar"));
assert!(!CrateModel::is_ancestor_or_equal("foo::bar", "foo"));
assert!(!CrateModel::is_ancestor_or_equal("foo::bar", "foo::baz"));
}
}