M src/html.rs => src/html.rs +91 -1
@@ 44,7 44,7 @@ mod util;
use std::{convert::TryFrom, error, fmt, fs, io, path};
-use crate::{Error, Fqn, Ident, Item};
+use crate::{Error, Fqn, FullItem, Ident, Item, ItemType, Members};
/// A parser for HTML documentation generated by rustdoc.
///
@@ 133,6 133,26 @@ impl Crate {
path: path.into(),
}
}
+
+ fn item_path(&self, fqn: &Fqn, ty: ItemType) -> Result<path::PathBuf, ParseError> {
+ if fqn[0] != self.ident {
+ return Err(ParseError::ItemNotFound(fqn.clone()));
+ }
+ let mut path = self.path.clone();
+ if ty == ItemType::Module {
+ for ident in fqn.iter().skip(1) {
+ path.push(ident.as_str());
+ }
+ path.push("index.html");
+ } else {
+ unimplemented!();
+ }
+ if path.exists() {
+ Ok(path)
+ } else {
+ Err(ParseError::ItemNotFound(fqn.clone()))
+ }
+ }
}
impl crate::Crate for Crate {
@@ 151,6 171,20 @@ impl crate::Crate for Crate {
modules.push(root);
Ok(modules)
}
+
+ fn open_item(&self, fqn: Fqn, ty: ItemType) -> Result<FullItem, Error> {
+ let path = self.item_path(&fqn, ty)?;
+ let item = Document::from_file(path)?.parse_item(ty)?;
+ if item.fqn == fqn {
+ Ok(item)
+ } else {
+ Err(ParseError::UnexpectedItem {
+ expected: fqn,
+ actual: item.fqn,
+ }
+ .into())
+ }
+ }
}
fn extend_modules(modules: &mut Vec<Fqn>, parent: &Fqn, path: &path::Path) -> Result<(), Error> {
@@ 249,6 283,45 @@ impl Document {
}
Ok(items)
}
+
+ /// Parses the documentation for an item with the given type.
+ ///
+ /// In the standard rustdoc output, the item documentation is placed in the `index.html` for
+ /// modules and in the `type.name.html` file for other items with the type `type` and the name
+ /// `name`.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// let document = rustdoc_parser::html::Document::from_file("target/doc/log/index.html").unwrap();
+ /// let module = document.parse_item(rustdoc_parser::ItemType::Module).unwrap();
+ /// ```
+ pub fn parse_item(&self, ty: ItemType) -> Result<FullItem, Error> {
+ let main = self.main()?;
+ let fqn = Fqn::try_from(main.fqn()?)?;
+ let members = match ty {
+ ItemType::Module => {
+ let mut reexports = Vec::new();
+ let mut items = Vec::new();
+ for group in main.module_items()? {
+ let ty = group.ty()?;
+ if ty == ItemType::Import {
+ for item in group.items()? {
+ reexports.push(item.import()?);
+ }
+ } else {
+ for item in group.items()? {
+ let fqn = fqn.clone().child(item.ident()?);
+ items.push(Item { fqn, ty });
+ }
+ }
+ }
+ Some(Members::Module { reexports, items })
+ }
+ _ => None,
+ };
+ Ok(FullItem { fqn, ty, members })
+ }
}
impl From<dom::Document> for Document {
@@ 270,6 343,15 @@ impl TryFrom<kuchiki::NodeRef> for Document {
pub enum ParseError {
/// The HTML document has an invalid structure.
InvalidStructure(String),
+ /// The item with the given name was not found.
+ ItemNotFound(Fqn),
+ /// The documentation for an unexpected item was read.
+ UnexpectedItem {
+ /// The name of the expected item.
+ expected: Fqn,
+ /// The name of the actual item.
+ actual: Fqn,
+ },
}
impl error::Error for ParseError {}
@@ 280,6 362,14 @@ impl fmt::Display for ParseError {
Self::InvalidStructure(info) => {
write!(f, "the HTML document has an invalid structure: {}", info)
}
+ Self::ItemNotFound(fqn) => write!(f, "the item with the name {} was not found", fqn),
+ Self::UnexpectedItem { expected, actual } => {
+ write!(
+ f,
+ "expected to read documentation for {}, found {}",
+ expected, actual
+ )
+ }
}
}
}
M src/html/dom.rs => src/html/dom.rs +21 -0
@@ 87,6 87,15 @@ impl Element {
self.0.as_node().descendants().elements().map(From::from)
}
+ pub fn next_sibling(&self) -> Option<Element> {
+ self.0
+ .as_node()
+ .following_siblings()
+ .elements()
+ .next()
+ .map(From::from)
+ }
+
pub fn text_contents(&self) -> String {
self.0.text_contents()
}
@@ 128,6 137,18 @@ impl Element {
)))
}
}
+
+ pub fn ensure_class(&self, class: &str) -> Result<(), ParseError> {
+ if self.has_class(class) {
+ Ok(())
+ } else {
+ Err(err(format!(
+ "expected element with class {}, got {:?}",
+ class,
+ self.classes()
+ )))
+ }
+ }
}
impl From<kuchiki::NodeDataRef<kuchiki::ElementData>> for Element {
M src/html/elements.rs => src/html/elements.rs +160 -3
@@ 7,8 7,9 @@ use super::{
dom::{Document, Element},
util, ParseError,
};
-use crate::{Fqn, Ident, ItemType, ParseIdentError};
+use crate::{Error, Ident, Import, ItemType, ParseIdentError};
+#[derive(Debug)]
pub struct Html(Element);
impl Html {
@@ 41,6 42,7 @@ impl TryFrom<Element> for Html {
}
}
+#[derive(Debug)]
pub struct Main(Element);
impl Main {
@@ 51,6 53,22 @@ impl Main {
.map(IndexGroup::try_from)
.collect()
}
+
+ pub fn module_items(&self) -> Result<Vec<ModuleItemsHeading>, ParseError> {
+ self.0
+ .children()
+ .filter(|element| element.name() == "h2")
+ .map(ModuleItemsHeading::try_from)
+ .collect()
+ }
+
+ pub fn fqn(&self) -> Result<Fqn, ParseError> {
+ self.0
+ .children()
+ .find(|element| element.has_class("fqn"))
+ .ok_or_else(|| ParseError::InvalidStructure("missing fqn for main".to_owned()))
+ .and_then(Fqn::try_from)
+ }
}
impl TryFrom<Element> for Main {
@@ 63,6 81,144 @@ impl TryFrom<Element> for Main {
}
}
+#[derive(Debug)]
+pub struct Fqn(Element);
+
+impl TryFrom<Element> for Fqn {
+ type Error = ParseError;
+
+ fn try_from(element: Element) -> Result<Self, Self::Error> {
+ element.ensure_name("h1")?;
+ element.ensure_class("fqn")?;
+ Ok(Self(element))
+ }
+}
+
+impl TryFrom<Fqn> for crate::Fqn {
+ type Error = Error;
+
+ fn try_from(fqn: Fqn) -> Result<Self, Self::Error> {
+ let in_band =
+ fqn.0.children().next().ok_or_else(|| {
+ ParseError::InvalidStructure("missing in-band for fqn".to_owned())
+ })?;
+ let in_band = FqnInBand::try_from(in_band)?;
+ in_band.fqn().map_err(From::from)
+ }
+}
+
+#[derive(Debug)]
+pub struct FqnInBand(Element);
+
+impl FqnInBand {
+ fn fqn(&self) -> Result<crate::Fqn, ParseIdentError> {
+ let fqn: Vec<Ident> = self
+ .0
+ .children()
+ .filter(|element| element.name() == "a")
+ .map(|element| element.text_contents().parse())
+ .collect::<Result<_, _>>()?;
+ crate::Fqn::try_from(fqn)
+ }
+}
+
+impl TryFrom<Element> for FqnInBand {
+ type Error = ParseError;
+
+ fn try_from(element: Element) -> Result<Self, Self::Error> {
+ element.ensure_name("span")?;
+ element.ensure_class("in-band")?;
+ Ok(Self(element))
+ }
+}
+
+#[derive(Debug)]
+pub struct ModuleItemsHeading(Element);
+
+impl ModuleItemsHeading {
+ pub fn ty(&self) -> Result<ItemType, ParseError> {
+ let id = self.0.id().ok_or_else(|| {
+ ParseError::InvalidStructure("no id for module items heading".to_owned())
+ })?;
+ util::parse_item_type_from_module_group_id(&id)
+ }
+
+ pub fn items(&self) -> Result<Vec<ModuleItem>, ParseError> {
+ self.0
+ .next_sibling()
+ .ok_or_else(|| {
+ ParseError::InvalidStructure(
+ "module items heading does not have a sibling element".to_owned(),
+ )
+ })
+ .and_then(ModuleItemsTable::try_from)?
+ .items()
+ }
+}
+
+impl TryFrom<Element> for ModuleItemsHeading {
+ type Error = ParseError;
+
+ fn try_from(element: Element) -> Result<Self, Self::Error> {
+ element.ensure_name("h2")?;
+ element.ensure_class("section-header")?;
+ Ok(Self(element))
+ }
+}
+
+#[derive(Debug)]
+pub struct ModuleItemsTable(Element);
+
+impl ModuleItemsTable {
+ fn items(&self) -> Result<Vec<ModuleItem>, ParseError> {
+ self.0
+ .children()
+ .filter(|element| element.has_class("item-left"))
+ .map(ModuleItem::try_from)
+ .collect()
+ }
+}
+
+impl TryFrom<Element> for ModuleItemsTable {
+ type Error = ParseError;
+
+ fn try_from(element: Element) -> Result<Self, Self::Error> {
+ element.ensure_name("div")?;
+ element.ensure_class("item-table")?;
+ Ok(Self(element))
+ }
+}
+
+#[derive(Debug)]
+pub struct ModuleItem(Element);
+
+impl ModuleItem {
+ pub fn ident(&self) -> Result<Ident, ParseIdentError> {
+ self.0
+ .children()
+ .filter(|element| element.name() == "a")
+ .map(|element| element.text_contents())
+ .next()
+ .unwrap_or_default()
+ .parse()
+ }
+
+ pub fn import(&self) -> Result<Import, Error> {
+ util::parse_import(&self.0.text_contents())
+ }
+}
+
+impl TryFrom<Element> for ModuleItem {
+ type Error = ParseError;
+
+ fn try_from(element: Element) -> Result<Self, Self::Error> {
+ element.ensure_name("div")?;
+ element.ensure_class("item-left")?;
+ Ok(Self(element))
+ }
+}
+
+#[derive(Debug)]
pub struct IndexGroup(Element);
impl IndexGroup {
@@ 101,16 257,17 @@ impl TryFrom<Element> for IndexGroup {
}
}
+#[derive(Debug)]
pub struct IndexItem(Element);
impl IndexItem {
- pub fn fqn(&self, krate: Ident) -> Result<Fqn, ParseIdentError> {
+ pub fn fqn(&self, krate: Ident) -> Result<crate::Fqn, ParseIdentError> {
let mut fqn = vec1::Vec1::new(krate);
// TODO: check if empty?
for part in self.0.text_contents().split("::") {
fqn.push(Ident::try_from(part)?);
}
- Ok(Fqn::from(fqn))
+ Ok(crate::Fqn::from(fqn))
}
}
M src/html/util.rs => src/html/util.rs +128 -4
@@ 2,11 2,11 @@
// SPDX-License-Identifier: Apache-2.0 or MIT
use super::ParseError;
-use crate::ItemType;
+use crate::{Error, Import, ItemType};
-pub fn parse_item_type_from_index_group_class(id: &str) -> Result<ItemType, ParseError> {
+pub fn parse_item_type_from_index_group_class(class: &str) -> Result<ItemType, ParseError> {
// https://github.com/rust-lang/rust/blob/3e3890c9d4064253aaa8c51f5d5458d2dc6dab77/src/librustdoc/html/render/mod.rs#L329
- match id {
+ match class {
"structs" => Ok(ItemType::Struct),
"enums" => Ok(ItemType::Enum),
"unions" => Ok(ItemType::Union),
@@ 22,8 22,132 @@ pub fn parse_item_type_from_index_group_class(id: &str) -> Result<ItemType, Pars
"statics" => Ok(ItemType::Static),
"constants" => Ok(ItemType::Constant),
_ => Err(ParseError::InvalidStructure(format!(
- "unexpected item group id {}",
+ "unexpected item group class {}",
+ class
+ ))),
+ }
+}
+
+pub fn parse_item_type_from_module_group_id(id: &str) -> Result<ItemType, ParseError> {
+ // https://github.com/rust-lang/rust/blob/3e3890c9d4064253aaa8c51f5d5458d2dc6dab77/src/librustdoc/html/render/mod.rs#L2426
+ match id {
+ "reexports" => Ok(ItemType::Import),
+ "modules" => Ok(ItemType::Module),
+ "structs" => Ok(ItemType::Struct),
+ "unions" => Ok(ItemType::Union),
+ "enums" => Ok(ItemType::Enum),
+ "functions" => Ok(ItemType::Function),
+ "types" => Ok(ItemType::Typedef),
+ "statics" => Ok(ItemType::Static),
+ "constants" => Ok(ItemType::Constant),
+ "traits" => Ok(ItemType::Trait),
+ "impls" => Ok(ItemType::Impl),
+ "tymethods" => Ok(ItemType::TyMethod),
+ "methods" => Ok(ItemType::Method),
+ "fields" => Ok(ItemType::StructField),
+ "variants" => Ok(ItemType::Variant),
+ "macros" => Ok(ItemType::Macro),
+ "primitives" => Ok(ItemType::Primitive),
+ "associated-types" => Ok(ItemType::AssocType),
+ "associated-consts" => Ok(ItemType::AssocConst),
+ "foreign-types" => Ok(ItemType::ForeignType),
+ "keywords" => Ok(ItemType::Keyword),
+ "opaque-types" => Ok(ItemType::OpaqueTy),
+ "attributes" => Ok(ItemType::ProcAttribute),
+ "derives" => Ok(ItemType::ProcDerive),
+ "trait-aliases" => Ok(ItemType::TraitAlias),
+ _ => Err(ParseError::InvalidStructure(format!(
+ "unexpected module group id {}",
id
))),
}
}
+
+pub fn parse_import(s: &str) -> Result<Import, Error> {
+ const AS: &str = " as ";
+ let s = s.strip_prefix("pub use ").ok_or_else(|| {
+ ParseError::InvalidStructure("import does not start with pub use".to_owned())
+ })?;
+ let s = s
+ .strip_suffix(';')
+ .ok_or_else(|| ParseError::InvalidStructure("import does not end with ;".to_owned()))?;
+ if let Some(n) = s.find(AS) {
+ // "pub use source as name;"
+ let fqn = &s[..n];
+ let name = &s[n + AS.len()..];
+ Ok(Import {
+ source: fqn.parse()?,
+ name: name.parse()?,
+ glob: false,
+ })
+ } else {
+ let (source, glob) = if let Some(s) = s.strip_suffix("::*") {
+ // "pub use source::*;"
+ (s, true)
+ } else {
+ // "pub use source;"
+ (s, false)
+ };
+ let source: crate::Fqn = source.parse()?;
+ let name = source.ident().clone();
+ Ok(Import { source, name, glob })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::parse_import;
+ use crate::Import;
+
+ #[test]
+ fn test_parse_import() {
+ assert_eq!(
+ parse_import("pub use source as name;").unwrap(),
+ Import {
+ source: "source".parse().unwrap(),
+ name: "name".parse().unwrap(),
+ glob: false,
+ },
+ );
+ assert_eq!(
+ parse_import("pub use multi::part::path as name;").unwrap(),
+ Import {
+ source: "multi::part::path".parse().unwrap(),
+ name: "name".parse().unwrap(),
+ glob: false,
+ },
+ );
+ assert_eq!(
+ parse_import("pub use source;").unwrap(),
+ Import {
+ source: "source".parse().unwrap(),
+ name: "source".parse().unwrap(),
+ glob: false,
+ },
+ );
+ assert_eq!(
+ parse_import("pub use multi::part::path;").unwrap(),
+ Import {
+ source: "multi::part::path".parse().unwrap(),
+ name: "path".parse().unwrap(),
+ glob: false,
+ },
+ );
+ assert_eq!(
+ parse_import("pub use source::*;").unwrap(),
+ Import {
+ source: "source".parse().unwrap(),
+ name: "source".parse().unwrap(),
+ glob: true,
+ },
+ );
+ assert_eq!(
+ parse_import("pub use multi::part::path::*;").unwrap(),
+ Import {
+ source: "multi::part::path".parse().unwrap(),
+ name: "path".parse().unwrap(),
+ glob: true,
+ },
+ );
+ }
+}
M src/json.rs => src/json.rs +81 -1
@@ 52,7 52,7 @@ mod util;
use std::{collections, convert::TryFrom, error, fmt, fs, io, path};
-use crate::{Error, Fqn, Ident, Item, ItemType};
+use crate::{Error, Fqn, FullItem, Ident, Import, Item, ItemType, Members};
use util::{CrateExt, ItemExt};
/// A parser for JSON documentation generated by rustdoc.
@@ 186,6 186,70 @@ impl Crate {
})
.transpose()
}
+
+ fn find_item(&self, fqn: &Fqn, ty: ItemType) -> Result<&model::Item, ParseError> {
+ let root = self.krate.get(&self.krate.root)?;
+ if root.name.as_deref() == Some(fqn.krate().as_str()) {
+ self.find_child(root, &fqn[1..], ty)
+ .ok_or_else(|| ParseError::ItemNotFound(fqn.clone()))
+ } else {
+ Err(ParseError::ItemNotFound(fqn.clone()))
+ }
+ }
+
+ fn find_child<'a>(
+ &'a self,
+ parent: &'a model::Item,
+ name: &[Ident],
+ ty: ItemType,
+ ) -> Option<&'a model::Item> {
+ if let Some(first) = name.first() {
+ let matches: Vec<_> = parent
+ .members()
+ .flat_map(|id| self.krate.get(id))
+ .filter(|item| item.name.as_deref() == Some(first.as_str()))
+ .filter(|item| {
+ if name.len() == 1 {
+ item.ty() == ty
+ } else {
+ item.ty() == ItemType::Module
+ }
+ })
+ .collect();
+ if matches.len() == 1 {
+ self.find_child(matches[0], &name[1..], ty)
+ } else {
+ None
+ }
+ } else {
+ Some(parent)
+ }
+ }
+
+ fn reexports(&self, item: &model::Item) -> Result<Vec<Import>, Error> {
+ item.members()
+ .flat_map(|id| self.krate.get(id))
+ .flat_map(|item| match &item.inner {
+ model::ItemEnum::Import(import) => Some(import),
+ _ => None,
+ })
+ .map(|import| {
+ Ok(Import {
+ source: import.source.parse()?,
+ name: import.name.parse()?,
+ glob: import.glob,
+ })
+ })
+ .collect()
+ }
+
+ fn module_items(&self, item: &model::Item) -> Result<Vec<Item>, Error> {
+ item.members()
+ .flat_map(|id| self.krate.get(id))
+ .filter(|item| item.name.is_some())
+ .map(|item| self.item(&item.id))
+ .collect()
+ }
}
impl crate::Crate for Crate {
@@ 223,6 287,18 @@ impl crate::Crate for Crate {
.map(|item| self.fqn(item))
.collect()
}
+
+ fn open_item(&self, fqn: Fqn, ty: ItemType) -> Result<FullItem, Error> {
+ let item = self.find_item(&fqn, ItemType::Module)?;
+ let members = match ty {
+ ItemType::Module => Some(Members::Module {
+ reexports: self.reexports(item)?,
+ items: self.module_items(item)?,
+ }),
+ _ => None,
+ };
+ Ok(FullItem { fqn, ty, members })
+ }
}
impl TryFrom<model::Crate> for Crate {
@@ 255,6 331,8 @@ impl TryFrom<model::Crate> for Crate {
pub enum ParseError {
/// The JSON deserialization failed.
DeserializationError(serde_json::Error),
+ /// The item with the given name could not be found.
+ ItemNotFound(Fqn),
/// The item with this ID is missing.
MissingItem(String),
/// The item with this ID has multiple parents.
@@ 275,6 353,7 @@ impl error::Error for ParseError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Self::DeserializationError(error) => Some(error),
+ Self::ItemNotFound(_) => None,
Self::MissingItem(_) => None,
Self::MultipleParents(_) => None,
Self::UnnamedItem(_) => None,
@@ 287,6 366,7 @@ impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DeserializationError(_) => "failed to deserialize the JSON crate data".fmt(f),
+ Self::ItemNotFound(fqn) => write!(f, "item {} not found", fqn),
Self::MissingItem(item) => write!(f, "missing item {} in crate", item),
Self::MultipleParents(item) => write!(f, "item {} has multiple parents", item),
Self::UnnamedItem(item) => write!(f, "item {} does not have a name", item),
M src/model.rs => src/model.rs +54 -0
@@ 81,6 81,23 @@ pub trait Crate: fmt::Debug {
/// }
/// ```
fn modules(&self) -> Result<Vec<Fqn>, Error>;
+
+ /// Opens the documentation for the given item with the given item type in this crate.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// let dir = rustdoc_parser::Dir::open("target/doc");
+ /// for krate in dir.list_crates().expect("failed to list crates") {
+ /// let krate = dir.open_crate(krate).expect("failed to open crate documentation");
+ /// for module in krate.modules().unwrap() {
+ /// println!("{}", module);
+ /// let item = krate.open_item(module, rustdoc_parser::ItemType::Module).unwrap();
+ /// println!("{:?}", item.members);
+ /// }
+ /// }
+ /// ```
+ fn open_item(&self, fqn: Fqn, ty: ItemType) -> Result<FullItem, Error>;
}
/// A Rust item.
@@ 92,3 109,40 @@ pub struct Item {
/// The type of this item.
pub ty: ItemType,
}
+
+/// A Rust import.
+#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+#[non_exhaustive]
+pub struct Import {
+ /// The item being imported.
+ pub source: Fqn,
+ /// The name of the imported item.
+ pub name: Ident,
+ /// Whether this import uses a glob.
+ pub glob: bool,
+}
+
+/// The full documentation for a Rust item.
+#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+#[non_exhaustive]
+pub struct FullItem {
+ /// The name of this item.
+ pub fqn: Fqn,
+ /// The type of this item.
+ pub ty: ItemType,
+ /// The members of this item.
+ pub members: Option<Members>,
+}
+
+/// The members of a Rust item.
+#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+#[non_exhaustive]
+pub enum Members {
+ /// The members of a module.
+ Module {
+ /// The re-exports in this module.
+ reexports: Vec<Import>,
+ /// The items in this module.
+ items: Vec<Item>,
+ },
+}
M src/types.rs => src/types.rs +11 -3
@@ 331,6 331,16 @@ impl<'a> TryFrom<&'a str> for Fqn {
}
}
+impl TryFrom<Vec<Ident>> for Fqn {
+ type Error = ParseIdentError;
+
+ fn try_from(vec: Vec<Ident>) -> Result<Self, Self::Error> {
+ vec1::Vec1::try_from_vec(vec)
+ .map_err(|_| ParseIdentError::Empty)
+ .map(Self)
+ }
+}
+
impl IntoIterator for Fqn {
type IntoIter = std::vec::IntoIter<Self::Item>;
type Item = Ident;
@@ 377,9 387,7 @@ impl str::FromStr for Fqn {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let result: Result<Vec<_>, _> = s.split("::").map(Ident::from_str).collect();
- result
- .and_then(|vec| vec1::Vec1::try_from_vec(vec).map_err(|_| ParseIdentError::Empty))
- .map(Self)
+ result.and_then(Self::try_from)
}
}
M tests/parser.rs => tests/parser.rs +17 -0
@@ 66,3 66,20 @@ fn test_modules() {
insta::assert_debug_snapshot!(format!("modules__{}", krate.ident()), modules);
});
}
+
+#[test]
+fn test_module() {
+ test_with_crates(&["anyhow", "log", "rand_core"], |krate| {
+ for fqn in krate.modules().unwrap() {
+ let mut module = krate
+ .open_item(fqn.clone(), rustdoc_parser::ItemType::Module)
+ .unwrap();
+ if let Some(rustdoc_parser::Members::Module { reexports, items }) = &mut module.members
+ {
+ reexports.sort();
+ items.sort();
+ }
+ insta::assert_debug_snapshot!(format!("module__{}", fqn), module);
+ }
+ });
+}
A tests/snapshots/parser__module__anyhow.snap => tests/snapshots/parser__module__anyhow.snap +52 -0
@@ 0,0 1,52 @@
+---
+source: tests/parser.rs
+expression: module
+
+---
+FullItem {
+ fqn: Fqn("anyhow"),
+ ty: Module,
+ members: Some(
+ Module {
+ reexports: [
+ Import {
+ source: Fqn("anyhow"),
+ name: Ident(
+ "format_err",
+ ),
+ glob: false,
+ },
+ ],
+ items: [
+ Item {
+ fqn: Fqn("anyhow::Chain"),
+ ty: Struct,
+ },
+ Item {
+ fqn: Fqn("anyhow::Context"),
+ ty: Trait,
+ },
+ Item {
+ fqn: Fqn("anyhow::Error"),
+ ty: Struct,
+ },
+ Item {
+ fqn: Fqn("anyhow::Result"),
+ ty: Typedef,
+ },
+ Item {
+ fqn: Fqn("anyhow::anyhow"),
+ ty: Macro,
+ },
+ Item {
+ fqn: Fqn("anyhow::bail"),
+ ty: Macro,
+ },
+ Item {
+ fqn: Fqn("anyhow::ensure"),
+ ty: Macro,
+ },
+ ],
+ },
+ ),
+}
A tests/snapshots/parser__module__log.snap => tests/snapshots/parser__module__log.snap +104 -0
@@ 0,0 1,104 @@
+---
+source: tests/parser.rs
+expression: module
+
+---
+FullItem {
+ fqn: Fqn("log"),
+ ty: Module,
+ members: Some(
+ Module {
+ reexports: [],
+ items: [
+ Item {
+ fqn: Fqn("log::Level"),
+ ty: Enum,
+ },
+ Item {
+ fqn: Fqn("log::LevelFilter"),
+ ty: Enum,
+ },
+ Item {
+ fqn: Fqn("log::Log"),
+ ty: Trait,
+ },
+ Item {
+ fqn: Fqn("log::Metadata"),
+ ty: Struct,
+ },
+ Item {
+ fqn: Fqn("log::MetadataBuilder"),
+ ty: Struct,
+ },
+ Item {
+ fqn: Fqn("log::ParseLevelError"),
+ ty: Struct,
+ },
+ Item {
+ fqn: Fqn("log::Record"),
+ ty: Struct,
+ },
+ Item {
+ fqn: Fqn("log::RecordBuilder"),
+ ty: Struct,
+ },
+ Item {
+ fqn: Fqn("log::STATIC_MAX_LEVEL"),
+ ty: Constant,
+ },
+ Item {
+ fqn: Fqn("log::SetLoggerError"),
+ ty: Struct,
+ },
+ Item {
+ fqn: Fqn("log::debug"),
+ ty: Macro,
+ },
+ Item {
+ fqn: Fqn("log::error"),
+ ty: Macro,
+ },
+ Item {
+ fqn: Fqn("log::info"),
+ ty: Macro,
+ },
+ Item {
+ fqn: Fqn("log::log"),
+ ty: Macro,
+ },
+ Item {
+ fqn: Fqn("log::log_enabled"),
+ ty: Macro,
+ },
+ Item {
+ fqn: Fqn("log::logger"),
+ ty: Function,
+ },
+ Item {
+ fqn: Fqn("log::max_level"),
+ ty: Function,
+ },
+ Item {
+ fqn: Fqn("log::set_logger"),
+ ty: Function,
+ },
+ Item {
+ fqn: Fqn("log::set_logger_racy"),
+ ty: Function,
+ },
+ Item {
+ fqn: Fqn("log::set_max_level"),
+ ty: Function,
+ },
+ Item {
+ fqn: Fqn("log::trace"),
+ ty: Macro,
+ },
+ Item {
+ fqn: Fqn("log::warn"),
+ ty: Macro,
+ },
+ ],
+ },
+ ),
+}
A tests/snapshots/parser__module__rand_core.snap => tests/snapshots/parser__module__rand_core.snap +44 -0
@@ 0,0 1,44 @@
+---
+source: tests/parser.rs
+expression: module
+
+---
+FullItem {
+ fqn: Fqn("rand_core"),
+ ty: Module,
+ members: Some(
+ Module {
+ reexports: [],
+ items: [
+ Item {
+ fqn: Fqn("rand_core::CryptoRng"),
+ ty: Trait,
+ },
+ Item {
+ fqn: Fqn("rand_core::Error"),
+ ty: Struct,
+ },
+ Item {
+ fqn: Fqn("rand_core::RngCore"),
+ ty: Trait,
+ },
+ Item {
+ fqn: Fqn("rand_core::SeedableRng"),
+ ty: Trait,
+ },
+ Item {
+ fqn: Fqn("rand_core::block"),
+ ty: Module,
+ },
+ Item {
+ fqn: Fqn("rand_core::impls"),
+ ty: Module,
+ },
+ Item {
+ fqn: Fqn("rand_core::le"),
+ ty: Module,
+ },
+ ],
+ },
+ ),
+}
A tests/snapshots/parser__module__rand_core::block.snap => tests/snapshots/parser__module__rand_core::block.snap +28 -0
@@ 0,0 1,28 @@
+---
+source: tests/parser.rs
+expression: module
+
+---
+FullItem {
+ fqn: Fqn("rand_core::block"),
+ ty: Module,
+ members: Some(
+ Module {
+ reexports: [],
+ items: [
+ Item {
+ fqn: Fqn("rand_core::block::BlockRng"),
+ ty: Struct,
+ },
+ Item {
+ fqn: Fqn("rand_core::block::BlockRng64"),
+ ty: Struct,
+ },
+ Item {
+ fqn: Fqn("rand_core::block::BlockRngCore"),
+ ty: Trait,
+ },
+ ],
+ },
+ ),
+}
A tests/snapshots/parser__module__rand_core::impls.snap => tests/snapshots/parser__module__rand_core::impls.snap +40 -0
@@ 0,0 1,40 @@
+---
+source: tests/parser.rs
+expression: module
+
+---
+FullItem {
+ fqn: Fqn("rand_core::impls"),
+ ty: Module,
+ members: Some(
+ Module {
+ reexports: [],
+ items: [
+ Item {
+ fqn: Fqn("rand_core::impls::fill_bytes_via_next"),
+ ty: Function,
+ },
+ Item {
+ fqn: Fqn("rand_core::impls::fill_via_u32_chunks"),
+ ty: Function,
+ },
+ Item {
+ fqn: Fqn("rand_core::impls::fill_via_u64_chunks"),
+ ty: Function,
+ },
+ Item {
+ fqn: Fqn("rand_core::impls::next_u32_via_fill"),
+ ty: Function,
+ },
+ Item {
+ fqn: Fqn("rand_core::impls::next_u64_via_fill"),
+ ty: Function,
+ },
+ Item {
+ fqn: Fqn("rand_core::impls::next_u64_via_u32"),
+ ty: Function,
+ },
+ ],
+ },
+ ),
+}
A tests/snapshots/parser__module__rand_core::le.snap => tests/snapshots/parser__module__rand_core::le.snap +24 -0
@@ 0,0 1,24 @@
+---
+source: tests/parser.rs
+expression: module
+
+---
+FullItem {
+ fqn: Fqn("rand_core::le"),
+ ty: Module,
+ members: Some(
+ Module {
+ reexports: [],
+ items: [
+ Item {
+ fqn: Fqn("rand_core::le::read_u32_into"),
+ ty: Function,
+ },
+ Item {
+ fqn: Fqn("rand_core::le::read_u64_into"),
+ ty: Function,
+ },
+ ],
+ },
+ ),
+}