Skip to main content

use_js_import/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use use_js_module::JsModuleSpecifier;
5
6/// JavaScript import metadata kind.
7#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
8pub enum JsImportKind {
9    Default,
10    Named,
11    Namespace,
12    SideEffect,
13    TypeOnly,
14}
15
16/// Imported binding metadata.
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct JsImportSpecifier {
19    imported: Option<String>,
20    local: Option<String>,
21}
22
23impl JsImportSpecifier {
24    /// Creates import specifier metadata.
25    #[must_use]
26    pub fn new(imported: Option<&str>, local: Option<&str>) -> Self {
27        Self {
28            imported: clean_optional(imported),
29            local: clean_optional(local),
30        }
31    }
32
33    /// Creates named import metadata.
34    #[must_use]
35    pub fn named(imported: &str, local: Option<&str>) -> Self {
36        Self::new(Some(imported), local)
37    }
38
39    /// Creates default import metadata.
40    #[must_use]
41    pub fn default(local: &str) -> Self {
42        Self::new(None, Some(local))
43    }
44
45    /// Creates namespace import metadata.
46    #[must_use]
47    pub fn namespace(local: &str) -> Self {
48        Self::new(Some("*"), Some(local))
49    }
50
51    /// Returns the imported name when one is present.
52    #[must_use]
53    pub fn imported(&self) -> Option<&str> {
54        self.imported.as_deref()
55    }
56
57    /// Returns the local binding name when one is present.
58    #[must_use]
59    pub fn local(&self) -> Option<&str> {
60        self.local.as_deref()
61    }
62}
63
64/// Lightweight import statement metadata.
65#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct JsImportStatementParts {
67    kind: JsImportKind,
68    source: JsModuleSpecifier,
69    specifiers: Vec<JsImportSpecifier>,
70}
71
72impl JsImportStatementParts {
73    /// Creates import statement metadata.
74    #[must_use]
75    pub const fn new(kind: JsImportKind, source: JsModuleSpecifier) -> Self {
76        Self {
77            kind,
78            source,
79            specifiers: Vec::new(),
80        }
81    }
82
83    /// Adds a specifier and returns the updated metadata.
84    #[must_use]
85    pub fn with_specifier(mut self, specifier: JsImportSpecifier) -> Self {
86        self.specifiers.push(specifier);
87        self
88    }
89
90    /// Returns the import kind.
91    #[must_use]
92    pub const fn kind(&self) -> JsImportKind {
93        self.kind
94    }
95
96    /// Returns the import source specifier.
97    #[must_use]
98    pub const fn source(&self) -> &JsModuleSpecifier {
99        &self.source
100    }
101
102    /// Returns imported binding metadata.
103    #[must_use]
104    pub fn specifiers(&self) -> &[JsImportSpecifier] {
105        &self.specifiers
106    }
107}
108
109fn clean_optional(value: Option<&str>) -> Option<String> {
110    value.and_then(|text| {
111        let trimmed = text.trim();
112        (!trimmed.is_empty()).then(|| trimmed.to_string())
113    })
114}
115
116#[cfg(test)]
117mod tests {
118    use super::{JsImportKind, JsImportSpecifier, JsImportStatementParts};
119    use use_js_module::{JsModuleSpecifier, JsModuleSpecifierError};
120
121    #[test]
122    fn models_named_import_metadata() -> Result<(), JsModuleSpecifierError> {
123        let source = JsModuleSpecifier::new("react")?;
124        let parts = JsImportStatementParts::new(JsImportKind::Named, source)
125            .with_specifier(JsImportSpecifier::named("useState", Some("state")));
126
127        assert_eq!(parts.kind(), JsImportKind::Named);
128        assert_eq!(parts.source().as_str(), "react");
129        assert_eq!(parts.specifiers()[0].imported(), Some("useState"));
130        assert_eq!(parts.specifiers()[0].local(), Some("state"));
131        Ok(())
132    }
133}