1//! Analyze modules
  2use std::path::Path;
  3
  4use anyhow::Result;
  5use syn::parse_file;
  6
  7use crate::data_model::{Enum, Function, Module, Struct};
  8
  9use super::docstring_from_attrs;
 10
 11impl Module {
 12    /// Fully qualified name of the variant
 13    pub fn path_str(&self) -> String {
 14        self.path.join("::")
 15    }
 16    /// Extract the relevant information from the AST
 17    pub fn parse(
 18        file: Option<&Path>,
 19        path: &[&str],
 20        content: &str,
 21    ) -> Result<(Self, Vec<Struct>, Vec<Enum>, Vec<Function>)> {
 22        let syntax = parse_file(content)?;
 23        let mut mod_ = Self {
 24            file: file.map(|f| f.to_string_lossy().to_string()), // TODO better way to serialize the path, also ?
 25            path: path.iter().map(|s| s.to_string()).collect(),
 26            docstring: docstring_from_attrs(&syntax.attrs),
 27            declarations: vec![],
 28        };
 29
 30        let mut structs = vec![];
 31        let mut enums = vec![];
 32        let mut functions = vec![];
 33
 34        for item in syntax.items {
 35            // TODO traits, functions, impls, et
 36            match &item {
 37                syn::Item::Mod(mod_item) => {
 38                    if let syn::Visibility::Public(_) = mod_item.vis {
 39                        // TODO handle modules that are not just declarations
 40                        mod_.declarations.push(mod_item.ident.to_string());
 41                    }
 42                }
 43                syn::Item::Struct(struct_item) => {
 44                    if let syn::Visibility::Public(_) = struct_item.vis {
 45                        let struct_ = Struct::parse(path, struct_item);
 46                        structs.push(struct_);
 47                    }
 48                }
 49                syn::Item::Enum(enum_item) => {
 50                    if let syn::Visibility::Public(_) = enum_item.vis {
 51                        let enum_ = Enum::parse(path, enum_item);
 52                        enums.push(enum_);
 53                    }
 54                }
 55                syn::Item::Fn(fn_item) => {
 56                    if let syn::Visibility::Public(_) = fn_item.vis {
 57                        let function = Function::parse(path, fn_item);
 58                        functions.push(function);
 59                    }
 60                }
 61                _ => {}
 62            }
 63        }
 64
 65        Ok((mod_, structs, enums, functions))
 66    }
 67
 68    pub fn to_json(&self) -> String {
 69        serde_json::to_string(&self).unwrap()
 70    }
 71}
 72
 73#[cfg(test)]
 74mod tests {
 75    use super::*;
 76    use insta::assert_yaml_snapshot;
 77
 78    #[test]
 79    fn test_parse_module() {
 80        let content = r###"
 81//! Multi-line
 82//! docstring
 83
 84pub enum MyEnum {
 85    MyVariant1,
 86}
 87"###;
 88        let mod_ = Module::parse(None, &["test"], content).unwrap();
 89        assert_yaml_snapshot!(mod_, @r###"
 90        ---
 91        - file: ~
 92          path:
 93            - test
 94          docstring: "Multi-line\ndocstring"
 95          declarations: []
 96        - []
 97        - - path:
 98              - test
 99              - MyEnum
100            docstring: ""
101            variants:
102              - path:
103                  - test
104                  - MyEnum
105                  - MyVariant1
106                docstring: ""
107                discriminant: ~
108                fields: []
109        - []
110        "###);
111    }
112}