use-js-import 0.0.1

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

use use_js_module::JsModuleSpecifier;

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

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

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

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

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

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

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

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

/// Lightweight import statement metadata.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct JsImportStatementParts {
    kind: JsImportKind,
    source: JsModuleSpecifier,
    specifiers: Vec<JsImportSpecifier>,
}

impl JsImportStatementParts {
    /// Creates import statement metadata.
    #[must_use]
    pub const fn new(kind: JsImportKind, source: JsModuleSpecifier) -> Self {
        Self {
            kind,
            source,
            specifiers: Vec::new(),
        }
    }

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

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

    /// Returns the import source specifier.
    #[must_use]
    pub const fn source(&self) -> &JsModuleSpecifier {
        &self.source
    }

    /// Returns imported binding metadata.
    #[must_use]
    pub fn specifiers(&self) -> &[JsImportSpecifier] {
        &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::{JsImportKind, JsImportSpecifier, JsImportStatementParts};
    use use_js_module::{JsModuleSpecifier, JsModuleSpecifierError};

    #[test]
    fn models_named_import_metadata() -> Result<(), JsModuleSpecifierError> {
        let source = JsModuleSpecifier::new("react")?;
        let parts = JsImportStatementParts::new(JsImportKind::Named, source)
            .with_specifier(JsImportSpecifier::named("useState", Some("state")));

        assert_eq!(parts.kind(), JsImportKind::Named);
        assert_eq!(parts.source().as_str(), "react");
        assert_eq!(parts.specifiers()[0].imported(), Some("useState"));
        assert_eq!(parts.specifiers()[0].local(), Some("state"));
        Ok(())
    }
}