use-js-export 0.0.1

JavaScript export metadata primitives for RustUse
Documentation
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]

use use_js_module::JsModuleSpecifier;

/// JavaScript export metadata kind.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum JsExportKind {
    Default,
    Named,
    Namespace,
    TypeOnly,
}

/// Exported binding metadata.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct JsExportSpecifier {
    local: Option<String>,
    exported: Option<String>,
}

impl JsExportSpecifier {
    /// Creates export specifier metadata.
    #[must_use]
    pub fn new(local: Option<&str>, exported: Option<&str>) -> Self {
        Self {
            local: clean_optional(local),
            exported: clean_optional(exported),
        }
    }

    /// Creates named export metadata.
    #[must_use]
    pub fn named(local: &str, exported: Option<&str>) -> Self {
        Self::new(Some(local), exported)
    }

    /// Creates default export metadata.
    #[must_use]
    pub fn default(local: &str) -> Self {
        Self::new(Some(local), Some("default"))
    }

    /// Returns the local binding name when one is present.
    #[must_use]
    pub fn local(&self) -> Option<&str> {
        self.local.as_deref()
    }

    /// Returns the exported binding name when one is present.
    #[must_use]
    pub fn exported(&self) -> Option<&str> {
        self.exported.as_deref()
    }
}

/// Lightweight export statement metadata.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct JsExportStatementParts {
    kind: JsExportKind,
    source: Option<JsModuleSpecifier>,
    specifiers: Vec<JsExportSpecifier>,
}

impl JsExportStatementParts {
    /// Creates export statement metadata without a re-export source.
    #[must_use]
    pub const fn new(kind: JsExportKind) -> Self {
        Self {
            kind,
            source: None,
            specifiers: Vec::new(),
        }
    }

    /// Adds a re-export source specifier.
    #[must_use]
    pub fn with_source(mut self, source: JsModuleSpecifier) -> Self {
        self.source = Some(source);
        self
    }

    /// Adds a specifier and returns the updated metadata.
    #[must_use]
    pub fn with_specifier(mut self, specifier: JsExportSpecifier) -> Self {
        self.specifiers.push(specifier);
        self
    }

    /// Returns the export kind.
    #[must_use]
    pub const fn kind(&self) -> JsExportKind {
        self.kind
    }

    /// Returns the optional re-export source specifier.
    #[must_use]
    pub const fn source(&self) -> Option<&JsModuleSpecifier> {
        self.source.as_ref()
    }

    /// Returns exported binding metadata.
    #[must_use]
    pub fn specifiers(&self) -> &[JsExportSpecifier] {
        &self.specifiers
    }
}

fn clean_optional(value: Option<&str>) -> Option<String> {
    value.and_then(|text| {
        let trimmed = text.trim();
        (!trimmed.is_empty()).then(|| trimmed.to_string())
    })
}

#[cfg(test)]
mod tests {
    use super::{JsExportKind, JsExportSpecifier, JsExportStatementParts};
    use use_js_module::{JsModuleSpecifier, JsModuleSpecifierError};

    #[test]
    fn models_re_export_metadata() -> Result<(), JsModuleSpecifierError> {
        let source = JsModuleSpecifier::new("./button.js")?;
        let parts = JsExportStatementParts::new(JsExportKind::Named)
            .with_source(source)
            .with_specifier(JsExportSpecifier::named("Button", Some("Button")));

        assert_eq!(parts.kind(), JsExportKind::Named);
        assert_eq!(
            parts.source().map(JsModuleSpecifier::as_str),
            Some("./button.js")
        );
        assert_eq!(parts.specifiers()[0].local(), Some("Button"));
        Ok(())
    }
}