public-api 0.27.1

List and diff the public API of Rust library crates. Relies on rustdoc JSON output from the nightly toolchain.
Documentation
use rustdoc_types::{Item, ItemEnum};

use crate::{public_item::PublicItemPath, render::RenderingContext, tokens::Token};

/// Wraps an [`Item`] and allows us to override its name.
#[derive(Clone, Debug)]
pub struct NameableItem<'c> {
    /// The item we are effectively wrapping.
    pub item: &'c Item,

    /// If `Some`, this overrides [Item::name], which happens in the case of
    /// renamed imports (`pub use other::Item as Foo;`).
    ///
    /// We can't calculate this on-demand, because we can't know the final name
    /// until we have checked if we need to break import recursion.
    pub overridden_name: Option<String>,

    /// See [`crate::item_processor::sorting_prefix()`] docs for an explanation why we have this.
    pub sorting_prefix: u8,
}

impl<'c> NameableItem<'c> {
    /// The regular name of the item. Shown to users.
    pub fn name(&self) -> Option<&str> {
        self.overridden_name
            .as_deref()
            .or(self.item.name.as_deref())
    }

    /// The name that, when sorted on, will group items nicely. Is never shown
    /// to a user.
    pub fn sortable_name(&self, context: &RenderingContext) -> String {
        let mut sortable_name = format!("{:0>3}-", self.sorting_prefix);

        // Note that in order for the prefix to sort properly lexicographically,
        // we need to pad it with leading zeroes.
        if let Some(name) = self.name() {
            sortable_name.push_str(name);
        } else if let ItemEnum::Impl(impl_) = &self.item.inner {
            // In order for items of impls to be grouped together with its
            // impl, add the "name" of the impl to the sorting prefix.
            sortable_name.push_str(&crate::tokens::tokens_to_string(
                &context.render_impl(impl_, &[]),
            ));

            // If this is an inherent impl, additionally add the concatenated
            // names of all associated items to the "name" of the impl. This makes
            // multiple inherent impls group together, even if they have the
            // same "name".
            //
            // For example, consider this code:
            //
            //   pub struct MultipleInherentImpls;
            //
            //   impl MultipleInherentImpls {
            //       pub fn impl_one() {}
            //   }
            //
            //   impl MultipleInherentImpls {
            //       pub fn impl_two() {}
            //   }
            //
            // In this case, we want to group the two impls together. So
            // the name of the first impl should be
            //
            //   impl MultipleInherentImpls-impl_one
            //
            // and the second one
            //
            //   impl MultipleInherentImpls-impl_two
            //
            if impl_.trait_.is_none() {
                let mut assoc_item_names: Vec<&str> = impl_
                    .items
                    .iter()
                    .filter_map(|id| context.crate_.index.get(id))
                    .filter_map(|item| item.name.as_ref())
                    .map(String::as_str)
                    .collect();
                assoc_item_names.sort_unstable();

                sortable_name.push('-');
                sortable_name.push_str(&assoc_item_names.join("-"));
            }
        }

        sortable_name
    }
}

/// This struct represents one public item of a crate, but in intermediate form.
/// Conceptually it wraps a single [`Item`] even though the path to the item
/// consists of many [`Item`]s. Later, one [`Self`] will be converted to exactly
/// one [`crate::PublicItem`].
#[derive(Clone, Debug)]
pub struct IntermediatePublicItem<'c> {
    path: Vec<NameableItem<'c>>,
}

impl<'c> IntermediatePublicItem<'c> {
    pub fn new(path: Vec<NameableItem<'c>>) -> Self {
        Self { path }
    }

    #[must_use]
    pub fn item(&self) -> &'c Item {
        self.path().last().expect("path must not be empty").item
    }

    #[must_use]
    pub fn path(&self) -> &[NameableItem<'c>] {
        &self.path
    }

    /// See [`crate::item_processor::sorting_prefix()`] docs for an explanation why we have this.
    #[must_use]
    pub fn sortable_path(&self, context: &RenderingContext) -> PublicItemPath {
        self.path()
            .iter()
            .map(|p| NameableItem::sortable_name(p, context))
            .collect()
    }

    #[must_use]
    pub fn path_contains_renamed_item(&self) -> bool {
        self.path().iter().any(|m| m.overridden_name.is_some())
    }

    pub fn render_token_stream(&self, context: &RenderingContext) -> Vec<Token> {
        context.token_stream(self)
    }
}