stylex_compiler_rs/enums/
mod.rs1use fancy_regex::Regex;
2use log::warn;
3use napi::JsValue;
4use napi::{
5 Env, Error, NapiRaw, Unknown,
6 bindgen_prelude::{FromNapiValue, ToNapiValue},
7 sys::{napi_env, napi_value},
8};
9use napi_derive::napi;
10use stylex_regex::regex::NPM_NAME_REGEX;
11use stylex_structures::named_import_source::NamedImportSource;
12
13#[napi(object)]
14#[derive(Debug, Clone)]
15pub struct StyleXModuleResolution {
16 pub r#type: String,
17 pub root_dir: Option<String>,
18 pub theme_file_extension: Option<String>,
19}
20
21#[napi(string_enum)]
22#[derive(Debug)]
23pub enum SourceMaps {
24 True,
25 False,
26 Inline,
27}
28
29#[napi(object)]
30#[derive(Debug, serde::Serialize, serde::Deserialize)]
31pub struct ImportSourceInput {
32 #[serde(rename = "as")]
33 pub as_: String,
34 pub from: String,
35}
36
37#[derive(Debug, Clone)]
38pub enum ImportSourceUnion {
39 Regular(String),
40 Named(NamedImportSource),
41}
42
43#[derive(Debug, Clone)]
44pub enum RuntimeInjectionUnion {
45 Boolean(bool),
46 Regular(String),
47}
48
49impl FromNapiValue for RuntimeInjectionUnion {
50 unsafe fn from_napi_value(env: napi_env, value: napi::sys::napi_value) -> Result<Self, Error> {
51 if let Ok(bool_value) = unsafe { bool::from_napi_value(env, value) } {
53 return Ok(RuntimeInjectionUnion::Boolean(bool_value));
54 }
55
56 let js_unknown = unsafe { Unknown::from_napi_value(env, value) }?;
58 let js_str = unsafe { js_unknown.cast::<napi::JsString>() }?;
59 let string_value = js_str.into_utf8()?.as_str()?.to_owned();
60
61 Ok(RuntimeInjectionUnion::Regular(string_value))
62 }
63}
64
65impl ToNapiValue for RuntimeInjectionUnion {
66 unsafe fn to_napi_value(env: napi_env, value: Self) -> Result<napi_value, Error> {
67 match value {
68 RuntimeInjectionUnion::Boolean(b) => unsafe { bool::to_napi_value(env, b) },
69 RuntimeInjectionUnion::Regular(s) => {
70 let env = Env::from_raw(env);
71 let js_str = env.create_string(&s)?;
72 Ok(js_str.raw())
73 },
74 }
75 }
76}
77
78static MAX_IMPORT_PATH_LENGTH: usize = 214;
79
80fn validate_import_path(path: &str) -> Result<(), String> {
81 if path.len() > MAX_IMPORT_PATH_LENGTH {
82 return Err(format!(
83 "Import path is too long (max {} characters)",
84 MAX_IMPORT_PATH_LENGTH
85 ));
86 }
87
88 if !NPM_NAME_REGEX.is_match(path).unwrap_or_else(|err| {
89 warn!(
90 "Error matching NPM_NAME_REGEX for '{}': {}. Skipping pattern match.",
91 path, err
92 );
93
94 false
95 }) {
96 return Err("Import path does not match required pattern".to_string());
97 }
98 Ok(())
99}
100
101impl FromNapiValue for ImportSourceUnion {
102 unsafe fn from_napi_value(env: napi_env, value: napi::sys::napi_value) -> Result<Self, Error> {
103 let js_unknown = unsafe { Unknown::from_napi_value(env, value) }?;
104 let js_obj = unsafe { js_unknown.cast::<napi::JsObject>() };
107
108 match js_obj {
109 Ok(obj) => match unsafe { ImportSourceInput::from_napi_value(env, obj.raw()) } {
110 Ok(value) => {
111 validate_import_path(&value.from).map_err(Error::from_reason)?;
112 Ok(ImportSourceUnion::Named(NamedImportSource {
113 r#as: value.as_,
114 from: value.from,
115 }))
116 },
117 Err(_) => {
118 let js_unknown = unsafe { Unknown::from_napi_value(env, value) }?;
119 let js_str = unsafe { js_unknown.cast::<napi::JsString>() }?;
120 let import_path = js_str.into_utf8()?.as_str()?.to_owned();
121
122 validate_import_path(&import_path).map_err(Error::from_reason)?;
123 Ok(ImportSourceUnion::Regular(import_path))
124 },
125 },
126 Err(_) => {
127 let js_unknown = unsafe { Unknown::from_napi_value(env, value) }?;
128 let js_str = unsafe { js_unknown.cast::<napi::JsString>() }?;
129 let import_path = js_str.into_utf8()?.as_str()?.to_owned();
130
131 validate_import_path(&import_path).map_err(Error::from_reason)?;
132 Ok(ImportSourceUnion::Regular(import_path))
133 },
134 }
135 }
136}
137
138impl ToNapiValue for ImportSourceUnion {
139 unsafe fn to_napi_value(env: napi_env, value: Self) -> Result<napi_value, Error> {
140 match value {
141 ImportSourceUnion::Regular(s) => {
142 let env = Env::from_raw(env);
143 let js_str = env.create_string(&s)?;
144 Ok(js_str.raw())
145 },
146 ImportSourceUnion::Named(named) => {
147 let env = Env::from_raw(env);
148 let mut js_obj = env.create_object()?;
149
150 let as_str = env.create_string(&named.r#as)?;
151 let from_str = env.create_string(&named.from)?;
152
153 js_obj.set_named_property("as", as_str)?;
154 js_obj.set_named_property("from", from_str)?;
155
156 Ok(unsafe { js_obj.raw() })
157 },
158 }
159 }
160}
161
162#[derive(Debug, Clone)]
163pub enum PathFilterUnion {
164 Glob(String),
165 Regex(String),
166}
167
168impl PathFilterUnion {
169 pub fn from_string(pattern: &str) -> Self {
170 if pattern.starts_with('/') && pattern.len() > 2 {
171 let mut last_slash_pos = None;
173 let chars: Vec<char> = pattern.chars().collect();
174
175 for i in (1..chars.len()).rev() {
176 if chars[i] == '/' {
177 let mut backslash_count = 0;
179 let mut j = i;
180 while j > 0 && chars[j - 1] == '\\' {
181 backslash_count += 1;
182 j -= 1;
183 }
184
185 if backslash_count % 2 == 0 {
187 last_slash_pos = Some(i);
188 break;
189 }
190 }
191 }
192
193 if let Some(last_slash) = last_slash_pos {
194 let regex_pattern = &pattern[1..last_slash];
196 let flags = &pattern[last_slash + 1..];
197
198 if flags
200 .chars()
201 .all(|c| matches!(c, 'g' | 'i' | 'm' | 's' | 'u' | 'y'))
202 {
203 if Regex::new(regex_pattern).is_ok() {
205 let mut inline_flags = String::new();
207 if flags.contains('i') {
208 inline_flags.push('i');
209 }
210 if flags.contains('m') {
211 inline_flags.push('m');
212 }
213 if flags.contains('s') {
214 inline_flags.push('s');
215 }
216
217 let final_pattern = if !inline_flags.is_empty() {
218 format!("(?{}){}", inline_flags, regex_pattern)
219 } else {
220 regex_pattern.to_string()
221 };
222
223 return PathFilterUnion::Regex(final_pattern);
224 }
225 }
226 }
227 }
228
229 PathFilterUnion::Glob(pattern.to_string())
231 }
232}
233
234#[napi(string_enum)]
235#[derive(Debug)]
236pub enum PropertyValidationMode {
237 #[napi(value = "throw")]
238 Throw,
239 #[napi(value = "warn")]
240 Warn,
241 #[napi(value = "silent")]
242 Silent,
243}
244
245#[derive(Debug, Clone)]
247pub enum SxPropNameUnion {
248 Disabled,
250 Name(String),
252}
253
254impl FromNapiValue for SxPropNameUnion {
255 unsafe fn from_napi_value(env: napi_env, value: napi::sys::napi_value) -> Result<Self, Error> {
256 if let Ok(bool_value) = unsafe { bool::from_napi_value(env, value) } {
258 if bool_value {
260 return Err(Error::from_reason(
261 "sxPropName does not accept `true` - use `false` to disable or provide a string prop name",
262 ));
263 }
264 return Ok(SxPropNameUnion::Disabled);
265 }
266
267 let js_unknown = unsafe { Unknown::from_napi_value(env, value) }?;
269 let js_str = unsafe { js_unknown.cast::<napi::JsString>() }?;
270 let string_value = js_str.into_utf8()?.as_str()?.to_owned();
271
272 Ok(SxPropNameUnion::Name(string_value))
273 }
274}
275
276impl ToNapiValue for SxPropNameUnion {
277 unsafe fn to_napi_value(env: napi_env, value: Self) -> Result<napi_value, Error> {
278 match value {
279 SxPropNameUnion::Disabled => unsafe { bool::to_napi_value(env, false) },
280 SxPropNameUnion::Name(s) => {
281 let env = Env::from_raw(env);
282 let js_str = env.create_string(&s)?;
283 Ok(js_str.raw())
284 },
285 }
286 }
287}