1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use use_js_module::JsModuleSpecifier;
5
6#[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#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct JsImportSpecifier {
19 imported: Option<String>,
20 local: Option<String>,
21}
22
23impl JsImportSpecifier {
24 #[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 #[must_use]
35 pub fn named(imported: &str, local: Option<&str>) -> Self {
36 Self::new(Some(imported), local)
37 }
38
39 #[must_use]
41 pub fn default(local: &str) -> Self {
42 Self::new(None, Some(local))
43 }
44
45 #[must_use]
47 pub fn namespace(local: &str) -> Self {
48 Self::new(Some("*"), Some(local))
49 }
50
51 #[must_use]
53 pub fn imported(&self) -> Option<&str> {
54 self.imported.as_deref()
55 }
56
57 #[must_use]
59 pub fn local(&self) -> Option<&str> {
60 self.local.as_deref()
61 }
62}
63
64#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct JsImportStatementParts {
67 kind: JsImportKind,
68 source: JsModuleSpecifier,
69 specifiers: Vec<JsImportSpecifier>,
70}
71
72impl JsImportStatementParts {
73 #[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 #[must_use]
85 pub fn with_specifier(mut self, specifier: JsImportSpecifier) -> Self {
86 self.specifiers.push(specifier);
87 self
88 }
89
90 #[must_use]
92 pub const fn kind(&self) -> JsImportKind {
93 self.kind
94 }
95
96 #[must_use]
98 pub const fn source(&self) -> &JsModuleSpecifier {
99 &self.source
100 }
101
102 #[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}