knave-expand 0.14.22

The reading, scoping and expansion stages of knave
Documentation
use super::bindings::Bindings;
use super::docstring_notes::DocstringNotes;
use crate::core::{ModuleId, ModulePath, Namespace};
use crate::modules::{ModuleRegistry, ModuleSources};

pub(super) struct NotesModule<'t> {
    modules: &'t ModuleRegistry,
    bindings: Bindings<'t>,
}

impl<'t> NotesModule<'t> {
    pub(super) fn new(modules: &'t ModuleRegistry, bindings: Bindings<'t>) -> Self {
        Self { modules, bindings }
    }

    pub(super) fn find(&self) -> Option<ModuleId> {
        let prelude = self
            .modules
            .find(&ModulePath::new(ModuleSources::PRELUDE))?;
        let page_macro = self
            .modules
            .get(prelude)
            .exported(DocstringNotes::PAGE_MACRO, Namespace::Value)?;
        let page_module = self.bindings.definition_module(page_macro)?;
        let key = self
            .modules
            .get(page_module)
            .exported(DocstringNotes::DOCSTRINGS_KEY, Namespace::Value)?;
        self.bindings.definition_module(key)
    }
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;

    use super::*;
    use crate::expand::Expander;
    use crate::source::SourceMap;

    struct NotesModuleFixture {
        sources: SourceMap,
    }

    impl NotesModuleFixture {
        const MODULE: &str = "notes";

        fn new() -> Self {
            Self {
                sources: SourceMap::default(),
            }
        }

        fn defines_the_docstrings_key(&mut self) -> bool {
            let entry = self.sources.add(Self::MODULE, "");
            let mut expander = Expander::new(
                &mut self.sources,
                ModuleSources::new(PathBuf::new(), PathBuf::new()),
            );
            expander.load_source_module(ModulePath::new(Self::MODULE), &[entry]);
            let bindings = expander.bindings();
            let notes = NotesModule::new(&expander.modules, bindings).find();
            notes.is_some_and(|module| {
                let key = expander
                    .modules
                    .get(module)
                    .exported(DocstringNotes::DOCSTRINGS_KEY, Namespace::Value);
                key.and_then(|key| bindings.definition_module(key)) == Some(module)
            })
        }

        fn found_without_a_prelude(&mut self) -> Option<ModuleId> {
            let expander = Expander::new(
                &mut self.sources,
                ModuleSources::new(PathBuf::new(), PathBuf::new()),
            );
            let empty = ModuleRegistry::default();
            NotesModule::new(&empty, expander.bindings()).find()
        }
    }

    #[test]
    fn the_notes_module_is_the_prelude_module_that_defines_the_docstrings_key() {
        assert!(NotesModuleFixture::new().defines_the_docstrings_key());
    }

    #[test]
    fn a_registry_without_the_prelude_has_no_notes_module() {
        assert_eq!(NotesModuleFixture::new().found_without_a_prelude(), None);
    }
}