diff --git a/libs/extractor/src/extractor/extract_style_from_stylex.rs b/libs/extractor/src/extractor/extract_style_from_stylex.rs new file mode 100644 index 00000000..f922ab84 --- /dev/null +++ b/libs/extractor/src/extractor/extract_style_from_stylex.rs @@ -0,0 +1,412 @@ +use crate::ExtractStyleProp; +use crate::extract_style::extract_dynamic_style::ExtractDynamicStyle; +use crate::extract_style::extract_static_style::ExtractStaticStyle; +use crate::extract_style::extract_style_value::ExtractStyleValue; +use crate::stylex::{ + SelectorPart, StylexIncludeRef, decompose_value_conditions, is_first_that_works_call, + is_include_call_static, is_types_call, normalize_stylex_property, +}; +use crate::utils::get_string_by_literal_expression; +use css::optimize_value::optimize_value; +use css::sheet_to_variable_name; +use oxc_ast::AstBuilder; +use oxc_ast::ast::{BindingPattern, Expression, ObjectPropertyKind, Statement}; +use rustc_hash::FxHashMap; + +use crate::utils::get_string_by_property_key; + +/// Extract styles from a `stylex.create()` call's argument (ObjectExpression). +/// +/// Handles static string/number values (Phase 1) and value-level conditions (Phase 2). +/// +/// Returns a Vec of `(namespace_name, style_props, css_vars, include_refs)` tuples. Each namespace +/// corresponds to a top-level key in the `stylex.create({...})` argument. +#[allow(clippy::type_complexity)] +pub fn extract_stylex_namespace_styles<'a>( + _ast_builder: &AstBuilder<'a>, + expression: &mut Expression<'a>, + keyframe_names: &FxHashMap, +) -> Vec<( + String, + Vec>, + Option>, + Vec, +)> { + let Expression::ObjectExpression(obj) = expression else { + return vec![]; + }; + + let mut result = vec![]; + + for prop in obj.properties.iter() { + let ObjectPropertyKind::ObjectProperty(prop) = prop else { + // Phase 4c: Spread not supported at namespace level + if matches!(prop, ObjectPropertyKind::SpreadProperty(_)) { + eprintln!( + "[stylex] ERROR: Object spread is not allowed at the namespace level of stylex.create()." + ); + } + continue; + }; + + let Some(ns_name) = get_string_by_property_key(&prop.key) else { + // Phase 4c: Computed namespace keys not supported + if prop.computed { + eprintln!( + "[stylex] ERROR: Computed namespace keys are not allowed in stylex.create()." + ); + } + continue; + }; + + // Phase 4b: Arrow function (dynamic namespace) + if let Expression::ArrowFunctionExpression(arrow) = &prop.value { + if let Some((styles, css_vars)) = + extract_stylex_dynamic_namespace(arrow, keyframe_names) + { + result.push((ns_name, styles, Some(css_vars), vec![])); + } else { + result.push((ns_name, vec![], None, vec![])); + } + continue; + } + + let Expression::ObjectExpression(ns_obj) = &prop.value else { + // Non-object namespace value (e.g., null): push empty styles + result.push((ns_name, vec![], None, vec![])); + continue; + }; + + let mut styles = vec![]; + let mut include_refs = vec![]; + + for style_prop in ns_obj.properties.iter() { + let ObjectPropertyKind::ObjectProperty(style_prop) = style_prop else { + // Check for stylex.include() spread + if let ObjectPropertyKind::SpreadProperty(spread) = style_prop + && let Expression::CallExpression(call) = &spread.argument + && is_include_call_static(&call.callee) + && !call.arguments.is_empty() + { + // Parse include(base.member) + if let Expression::StaticMemberExpression(member) = + call.arguments[0].to_expression() + && let Expression::Identifier(ident) = &member.object + { + include_refs.push(StylexIncludeRef { + var_name: ident.name.to_string(), + member_name: member.property.name.to_string(), + }); + } + } else if matches!(style_prop, ObjectPropertyKind::SpreadProperty(_)) { + eprintln!( + "[stylex] ERROR: Object spread is not allowed in stylex.create() namespaces. Define all properties explicitly." + ); + } + continue; + }; + + let Some(prop_name) = get_string_by_property_key(&style_prop.key) else { + // Phase 4c: Computed property keys not supported + if style_prop.computed { + eprintln!( + "[stylex] ERROR: Computed property keys are not allowed in stylex.create(). Use static string keys instead." + ); + } + continue; + }; + + // Phase 2: pseudo-element / pseudo-class top-level keys + if prop_name.starts_with("::") || prop_name.starts_with(':') { + let Expression::ObjectExpression(inner_obj) = &style_prop.value else { + continue; + }; + for inner_prop in inner_obj.properties.iter() { + let ObjectPropertyKind::ObjectProperty(inner_prop) = inner_prop else { + continue; + }; + let Some(inner_name) = get_string_by_property_key(&inner_prop.key) else { + continue; + }; + let inner_css_property = normalize_stylex_property(&inner_name); + let parent_selectors = vec![SelectorPart::Pseudo(prop_name.clone())]; + for decomposed in decompose_value_conditions( + &inner_css_property, + &inner_prop.value, + &parent_selectors, + ) { + if let Some(css_value) = decomposed.value { + styles.push(ExtractStyleProp::Static(ExtractStyleValue::Static( + ExtractStaticStyle { + property: decomposed.property, + value: optimize_value(&css_value), + level: 0, + selector: decomposed.selector, + style_order: None, + layer: None, + }, + ))); + } + } + } + continue; + } + + let css_property = normalize_stylex_property(&prop_name); + + // Phase 4c: Warn about CSS shorthand properties + const SHORTHAND_PROPERTIES: &[&str] = &[ + "margin", + "padding", + "background", + "border", + "font", + "outline", + "overflow", + "flex", + "grid", + "gap", + "border-radius", + "border-color", + "border-style", + "border-width", + "margin-inline", + "margin-block", + "padding-inline", + "padding-block", + ]; + if SHORTHAND_PROPERTIES.contains(&css_property.as_str()) { + eprintln!( + "[stylex] WARNING: Shorthand property '{}' may cause unexpected specificity issues. Consider using longhand properties (e.g., 'marginTop', 'paddingLeft').", + css_property + ); + } + + // Phase 4a: Resolve keyframe variable references (e.g., animationName: fadeIn) + if let Expression::Identifier(ident) = &style_prop.value + && let Some(anim_name) = keyframe_names.get(ident.name.as_str()) + { + styles.push(ExtractStyleProp::Static(ExtractStyleValue::Static( + ExtractStaticStyle { + property: css_property, + value: optimize_value(anim_name), + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ))); + continue; + } + + // Phase 1: static string/number values + let css_value = if let Some(s) = get_string_by_literal_expression(&style_prop.value) { + s + } else if matches!(&style_prop.value, Expression::ObjectExpression(_)) { + // Phase 2: value-level conditions + for decomposed in decompose_value_conditions(&css_property, &style_prop.value, &[]) + { + if let Some(css_value) = decomposed.value { + styles.push(ExtractStyleProp::Static(ExtractStyleValue::Static( + ExtractStaticStyle { + property: decomposed.property, + value: optimize_value(&css_value), + level: 0, + selector: decomposed.selector, + style_order: None, + layer: None, + }, + ))); + } + } + continue; + } else if let Expression::CallExpression(call) = &style_prop.value + && is_first_that_works_call(&call.callee) + { + // firstThatWorks('a', 'b', 'c'): last arg is least preferred, first is most preferred. + // CSS fallback: output in reverse order (least preferred first, most preferred last). + for arg in call.arguments.iter().rev() { + let arg_expr = arg.to_expression(); + if let Some(s) = get_string_by_literal_expression(arg_expr) { + styles.push(ExtractStyleProp::Static(ExtractStyleValue::Static( + ExtractStaticStyle { + property: css_property.clone(), + value: optimize_value(&s), + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ))); + } + } + continue; + } else if let Expression::CallExpression(call) = &style_prop.value + && is_types_call(&call.callee) + && !call.arguments.is_empty() + { + // stylex.types.length('100px') → extract inner value '100px' + let inner = call.arguments[0].to_expression(); + let css_value = if let Some(s) = get_string_by_literal_expression(inner) { + s + } else { + continue; // Can't resolve inner value + }; + styles.push(ExtractStyleProp::Static(ExtractStyleValue::Static( + ExtractStaticStyle { + property: css_property, + value: optimize_value(&css_value), + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ))); + continue; + } else { + // Phase 4c: Non-static values in create() are not supported + if !matches!(&style_prop.value, Expression::NullLiteral(_)) { + eprintln!( + "[stylex] ERROR: Non-static value for property '{}' in stylex.create(). Only string literals, numbers, null, objects (conditions), firstThatWorks(), types.*(), and arrow functions are allowed.", + prop_name + ); + } + continue; + }; + + // Construct directly — bypass convert_value() to avoid devup-ui + // spacing transformations. StyleX values are raw CSS, only optimize_value(). + styles.push(ExtractStyleProp::Static(ExtractStyleValue::Static( + ExtractStaticStyle { + property: css_property, + value: optimize_value(&css_value), + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ))); + } + + result.push((ns_name, styles, None, include_refs)); + } + + result +} + +/// Extract styles from a dynamic StyleX namespace (arrow function). +/// Returns (styles_for_css, css_vars) where css_vars maps param_index to CSS variable name. +#[allow(clippy::type_complexity)] +fn extract_stylex_dynamic_namespace<'a>( + arrow: &oxc_ast::ast::ArrowFunctionExpression<'a>, + keyframe_names: &FxHashMap, +) -> Option<(Vec>, Vec<(usize, String)>)> { + // 1. Extract parameter names + let param_names: Vec = arrow + .params + .items + .iter() + .filter_map(|param| { + if let BindingPattern::BindingIdentifier(ident) = ¶m.pattern { + Some(ident.name.to_string()) + } else { + None + } + }) + .collect(); + + if param_names.is_empty() { + return None; + } + + // 2. Get body ObjectExpression from expression body: (x) => ({ ... }) + if !arrow.expression { + return None; + } + // Expression arrow body: Oxc always wraps in ExpressionStatement. + // Unwrap ParenthesizedExpression since Oxc preserves parens for `(x) => ({...})`. + let body_expr = arrow.body.statements.first().and_then(|stmt| { + if let Statement::ExpressionStatement(e) = stmt { + Some(&e.expression) + } else { + None + } + })?; + let inner = if let Expression::ParenthesizedExpression(paren) = body_expr { + &paren.expression + } else { + body_expr + }; + let Expression::ObjectExpression(body_obj) = inner else { + return None; + }; + + // 3. Process each property + let mut styles = vec![]; + let mut css_vars = vec![]; + + for prop in body_obj.properties.iter() { + let ObjectPropertyKind::ObjectProperty(prop) = prop else { + continue; + }; + + let Some(prop_name) = get_string_by_property_key(&prop.key) else { + continue; + }; + let css_property = normalize_stylex_property(&prop_name); + + // Check if value references a parameter (dynamic) + let is_dynamic = if prop.shorthand { + // Shorthand: { height } is equivalent to { height: height } + param_names.iter().position(|p| p == &prop_name) + } else if let Expression::Identifier(ident) = &prop.value { + param_names.iter().position(|p| p == ident.name.as_str()) + } else { + None + }; + + if let Some(param_idx) = is_dynamic { + // Dynamic property: generate CSS variable + let var_name = sheet_to_variable_name(&css_property, 0, None); + css_vars.push((param_idx, var_name)); + let param_name = ¶m_names[param_idx]; + styles.push(ExtractStyleProp::Static(ExtractStyleValue::Dynamic( + ExtractDynamicStyle::new(&css_property, 0, param_name, None), + ))); + } else { + // Static property: resolve keyframe references or literal values + if let Expression::Identifier(ident) = &prop.value + && let Some(anim_name) = keyframe_names.get(ident.name.as_str()) + { + styles.push(ExtractStyleProp::Static(ExtractStyleValue::Static( + ExtractStaticStyle { + property: css_property, + value: optimize_value(anim_name), + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ))); + continue; + } + let css_value = if let Some(s) = get_string_by_literal_expression(&prop.value) { + s + } else { + continue; + }; + styles.push(ExtractStyleProp::Static(ExtractStyleValue::Static( + ExtractStaticStyle { + property: css_property, + value: optimize_value(&css_value), + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ))); + } + } + + Some((styles, css_vars)) +} diff --git a/libs/extractor/src/extractor/mod.rs b/libs/extractor/src/extractor/mod.rs index e97bb3ab..45e32e19 100644 --- a/libs/extractor/src/extractor/mod.rs +++ b/libs/extractor/src/extractor/mod.rs @@ -8,6 +8,7 @@ pub(super) mod extract_style_from_expression; pub(super) mod extract_style_from_jsx; pub(super) mod extract_style_from_member_expression; pub(super) mod extract_style_from_styled; +pub(super) mod extract_style_from_stylex; /** * type diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 23d8d72b..66a8a8c2 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -7,6 +7,7 @@ mod gen_class_name; mod gen_style; mod import_alias_visit; mod prop_modify_utils; +mod stylex; mod tailwind; mod util_type; mod utils; @@ -178,7 +179,8 @@ pub fn extract( ); // Step 2: Check if code contains the target package (after transformation) - let has_relevant_import = transformed_code.contains(option.package.as_str()); + let has_relevant_import = transformed_code.contains(option.package.as_str()) + || transformed_code.contains("@stylexjs/stylex"); if !has_relevant_import { // skip if not using package @@ -14129,4 +14131,2172 @@ export { c as Lib };"#, let result = combine_conditional_class_name(&builder, make_cond(), None, None); assert!(result.is_none()); } + + // ========================================== + // StyleX Phase 1 tests + // ========================================== + + #[test] + #[serial] + fn test_stylex_import_default() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: { color: 'red' } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_import_namespace() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import * as stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: { color: 'blue' } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_import_named() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { create } from '@stylexjs/stylex'; +const styles = create({ base: { color: 'green' } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_create_single_namespace_static() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + color: 'red', + backgroundColor: 'blue', + padding: '10px', + } +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_create_multiple_namespaces() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + color: 'red', + }, + active: { + color: 'blue', + fontWeight: 'bold', + } +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_create_numeric_values() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + fontSize: 16, + opacity: 0.5, + zIndex: 10, + fontWeight: 700, + padding: 8, + flex: 1, + } +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_create_empty_object() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_create_empty_namespace() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: {} });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_pseudo_class_hover() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + color: { default: 'red', ':hover': 'blue' }, + } +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_pseudo_class_focus() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + color: { default: 'red', ':focus': 'blue' }, + } +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_media_query() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + color: { default: 'red', '@media (max-width: 600px)': 'blue' }, + } +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_supports_query() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + display: { default: 'block', '@supports (display: grid)': 'grid' }, + } +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_nested_hover_in_media() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + color: { ':hover': { default: null, '@media (hover: hover)': 'blue' } }, + } +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_nested_media_then_hover() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + color: { '@media (min-width: 768px)': { default: 'red', ':hover': 'blue' } }, + } +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_pseudo_element_placeholder() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + '::placeholder': { color: '#999', opacity: 1 }, + } +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_pseudo_element_before() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + '::before': { content: '""', display: 'block' }, + } +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_null_value_no_css() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + color: { default: null, ':hover': 'blue' }, + } +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_multiple_conditions() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + backgroundColor: { default: 'white', ':hover': 'gray' }, + color: { default: 'black', ':hover': 'red' }, + } +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + // ── Phase 3: stylex.props() integration tests ────────────────────── + + #[test] + #[serial] + fn test_stylex_props_single_static() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { color: 'red', fontSize: '16px' }, +}); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_props_multiple_static() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { color: 'red' }, + active: { backgroundColor: 'blue' }, +}); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_props_conditional_and() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { color: 'red' }, + active: { backgroundColor: 'blue' }, +}); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_props_conditional_ternary() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + primary: { color: 'red' }, + secondary: { color: 'blue' }, +}); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_props_zero_args() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_props_with_conditions() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { color: { default: 'red', ':hover': 'blue' } }, +}); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_props_named_import() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { create, props } from '@stylexjs/stylex'; +const styles = create({ + base: { color: 'red' }, +}); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_props_falsy_args() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { color: 'red' }, +}); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + // ========================================== + // StyleX Phase 4a tests — keyframes, firstThatWorks, types.* + // ========================================== + + #[test] + #[serial] + fn test_stylex_keyframes_standalone() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const fadeIn = stylex.keyframes({ + from: { opacity: 0 }, + to: { opacity: 1 }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_keyframes_in_create() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const fadeIn = stylex.keyframes({ + from: { opacity: 0 }, + to: { opacity: 1 }, +}); +const styles = stylex.create({ + base: { animationName: fadeIn, animationDuration: '0.5s' }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_first_that_works_basic() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { position: stylex.firstThatWorks('sticky', '-webkit-sticky', 'fixed') }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_first_that_works_two_values() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { display: stylex.firstThatWorks('grid', 'flex') }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_types_length_strip() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { width: stylex.types.length('100px') }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_types_color_strip() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { color: stylex.types.color('red') }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_keyframes_named_import() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { keyframes, create } from '@stylexjs/stylex'; +const fadeIn = keyframes({ + from: { opacity: 0 }, + to: { opacity: 1 }, +}); +const styles = create({ + base: { animationName: fadeIn }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_dynamic_basic() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + bar: (height) => ({ + height, + }), +}); +const result = stylex.props(styles.bar(h));"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_dynamic_mixed() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + bar: (height) => ({ + height, + width: '100%', + }), +}); +const result = stylex.props(styles.bar(h));"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_dynamic_multi_param() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + bar: (h, w) => ({ + height: h, + width: w, + }), +}); +const result = stylex.props(styles.bar(myH, myW));"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + // ========================================== + // StyleX Phase 4c: Diagnostic error/warning tests + // ========================================== + + #[test] + #[serial] + fn test_stylex_error_computed_key() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const key = 'color'; +const styles = stylex.create({ + base: { + [key]: 'red', + }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_error_spread_in_namespace() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const shared = { color: 'red' }; +const styles = stylex.create({ + base: { + ...shared, + fontSize: '16px', + }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_error_non_static_value() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + color: someVariable, + fontSize: '16px', + }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_warn_shorthand_property() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + margin: '10px', + color: 'red', + }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_error_destructuring_create() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const { base } = stylex.create({ + base: { + color: 'red', + }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_coexist_with_box() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +import { Box } from '@devup-ui/react'; +const styles = stylex.create({ + base: { color: 'blue', fontSize: '14px' }, +}); +const el =
+ +
+
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_coexist_with_css() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +import { css } from '@devup-ui/react'; +const stylexStyles = stylex.create({ + base: { color: 'blue' }, +}); +const devupClass = css({ bg: 'red' }); +const el =
+
+
+
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_first_that_works_in_condition() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + color: 'red', + ':hover': { + display: stylex.firstThatWorks('grid', 'flex'), + }, + }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_types_in_condition() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ + base: { + width: { + default: '100%', + '@media (min-width: 768px)': stylex.types.length('50%'), + }, + }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_include_basic() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const base = stylex.create({ + root: { color: 'red', fontSize: '16px' }, +}); +const composed = stylex.create({ + fancy: { ...stylex.include(base.root), backgroundColor: 'blue' }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_include_named_import() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { create, include } from '@stylexjs/stylex'; +const base = create({ + root: { color: 'red' }, +}); +const composed = create({ + fancy: { ...include(base.root), padding: '8px' }, +});"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_include_with_props() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const base = stylex.create({ + root: { color: 'red' }, +}); +const composed = stylex.create({ + fancy: { ...stylex.include(base.root), backgroundColor: 'blue' }, +}); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + // ========================================== + // Coverage integration tests — stylex.rs, extract_style_from_stylex.rs, visit.rs + // ========================================== + + #[test] + #[serial] + fn test_stylex_named_first_that_works() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { create, firstThatWorks } from '@stylexjs/stylex'; +const styles = create({ base: { color: firstThatWorks('red', 'blue') } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_create_numeric_with_unit() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: { fontSize: 16, lineHeight: 1.5 } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_first_that_works_numeric() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: { zIndex: stylex.firstThatWorks(10, 20) } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_types_numeric() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: { fontSize: stylex.types.length(16) } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_types_unresolvable() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: { fontSize: stylex.types.length(someVar) } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_pseudo_non_object_value() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: { ':hover': 'invalid' } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_non_object_namespace_value() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: 'not-an-object', active: { color: 'blue' } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_spread_at_namespace_level() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const other = {}; +const styles = stylex.create({ ...other, base: { color: 'red' } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_computed_namespace_key() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const key = 'base'; +const styles = stylex.create({ [key]: { color: 'red' } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_dynamic_empty_params() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: () => ({ color: 'red' }) });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_dynamic_block_body() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: (x) => { return { color: x }; } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_dynamic_numeric_value() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: (x) => ({ fontSize: 16, height: x }) });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_dynamic_keyframe_ref() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const fadeIn = stylex.keyframes({ from: { opacity: '0' }, to: { opacity: '1' } }); +const styles = stylex.create({ base: (dur) => ({ animationName: fadeIn, animationDuration: dur }) });"#, + ExtractOption { package: "@devup-ui/react".to_string(), css_dir: "@devup-ui/react".to_string(), single_css: true, import_main_css: false, import_aliases: HashMap::new() }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_props_conditional_consequent_only() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ active: { color: 'red' } }); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_props_conditional_alternate_only() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ fallback: { color: 'gray' } }); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_props_conditional_none_none() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: { color: 'red' } }); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_props_falsy_literals() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: { color: 'red' } }); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_props_unresolvable_arg() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: { color: 'red' } }); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_props_dynamic_as_non_call() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: (x) => ({ color: x }) }); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_include_dynamic_target() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const base = stylex.create({ dynamic: (x) => ({ color: x, fontSize: '14px' }) }); +const composed = stylex.create({ fancy: { ...stylex.include(base.dynamic), backgroundColor: 'blue' } });"#, + ExtractOption { package: "@devup-ui/react".to_string(), css_dir: "@devup-ui/react".to_string(), single_css: true, import_main_css: false, import_aliases: HashMap::new() }, + ) + .unwrap() + )); + } + + #[test] + #[serial] + fn test_stylex_import_unknown_named() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { create, unknownFunction } from '@stylexjs/stylex'; +const styles = create({ base: { color: 'red' } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + // ========================================== + // Coverage tests: extract_style_from_stylex.rs + // ========================================== + + /// Line 24: Non-object argument to stylex.create() + #[test] + #[serial] + fn test_stylex_create_non_object_arg() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create("not-object");"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Lines 100, 103: Non-ObjectProperty / no string key inside pseudo inner properties + #[test] + #[serial] + fn test_stylex_pseudo_inner_spread_and_computed() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const other = { fontSize: '14px' }; +const styles = stylex.create({ base: { ':hover': { ...other, [Symbol()]: 'val', color: 'red' } } });"#, + ExtractOption { package: "@devup-ui/react".to_string(), css_dir: "@devup-ui/react".to_string(), single_css: true, import_main_css: false, import_aliases: HashMap::new() }, + ) + .unwrap() + )); + } + + /// Lines 232, 236: Non-ObjectProperty / no key in dynamic body (spread and computed) + #[test] + #[serial] + fn test_stylex_dynamic_body_spread_and_computed() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const other = { fontSize: '14px' }; +const styles = stylex.create({ base: (x) => ({ ...other, [Symbol()]: x, color: 'red' }) });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Line 269: Unresolvable value in dynamic namespace (not a param, not string, not number) + #[test] + #[serial] + fn test_stylex_dynamic_unresolvable_value() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const someVar = getSomething(); +const styles = stylex.create({ base: (x) => ({ color: x, fontSize: someVar }) });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + // ========================================== + // Coverage tests: stylex.rs + // ========================================== + + /// Line 48: false from is_include_call_static — spread with non-include call + #[test] + #[serial] + fn test_stylex_spread_non_include_call() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +function notInclude() { return {}; } +const styles = stylex.create({ base: { ...notInclude(), color: 'red' } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Lines 66-67: Named import types.X() + #[test] + #[serial] + fn test_stylex_named_import_types() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import { create, types } from '@stylexjs/stylex'; +const styles = create({ base: { width: types.length('100px') } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Line 70: false from is_types_call — call expression value that's not types or firstThatWorks + #[test] + #[serial] + fn test_stylex_non_types_non_ftw_call() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +function someFunc() { return 'red'; } +const styles = stylex.create({ base: { color: someFunc() } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Line 183: types with unresolvable inside condition object + #[test] + #[serial] + fn test_stylex_types_unresolvable_in_condition() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const someVar = getSomething(); +const styles = stylex.create({ base: { fontSize: { default: stylex.types.length(someVar) } } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Line 188: Non-object/non-string/non-number/non-null/non-call value in decompose (array expression) + #[test] + #[serial] + fn test_stylex_array_in_condition_object() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: { opacity: { default: [1, 2] } } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Line 195: Non-ObjectProperty inside condition object (spread) + #[test] + #[serial] + fn test_stylex_spread_in_condition_object() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const spreadObj = { ':hover': 'blue' }; +const styles = stylex.create({ base: { color: { ...spreadObj, default: 'red' } } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Line 198: No string key in condition object (computed key) + #[test] + #[serial] + fn test_stylex_computed_key_in_condition_object() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const key = ':hover'; +const styles = stylex.create({ base: { color: { [key]: 'blue', default: 'red' } } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + // ========================================== + // Coverage tests: visit.rs + // ========================================== + + /// Line 188: Empty className in static namespace — styles.empty where empty has no properties + #[test] + #[serial] + fn test_stylex_props_empty_namespace() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ empty: {} }); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Line 198: styles.nonexistent — member not found in namespace map + #[test] + #[serial] + fn test_stylex_props_nonexistent_member() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: { color: 'red' } }); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Line 208: None from LogicalExpression when right side can't resolve + #[test] + #[serial] + fn test_stylex_props_logical_unresolvable_right() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: { color: 'red' } }); +const el =
;"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Line 332: include with empty class_name_str — include a namespace that has no properties + #[test] + #[serial] + fn test_stylex_include_empty_namespace() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const base = stylex.create({ empty: {} }); +const composed = stylex.create({ test: { ...stylex.include(base.empty), color: 'red' } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Dynamic namespace with bare expression body (not object): (x) => x + /// Covers: extract_style_from_stylex.rs ObjectExpression else branch + #[test] + #[serial] + fn test_stylex_dynamic_bare_expression_body() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: (x) => x });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Dynamic namespace with parenthesized non-object body: (x) => (x) + /// Covers: extract_style_from_stylex.rs ParenthesizedExpression unwrap + ObjectExpression else + #[test] + #[serial] + fn test_stylex_dynamic_paren_non_object_body() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const styles = stylex.create({ base: (x) => (x) });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } + + /// Include-only namespace with no own styles — class_name_str starts empty + /// Covers: visit.rs line 332 (class_name_str = included_class when empty) + #[test] + #[serial] + fn test_stylex_include_only_no_own_styles() { + reset_class_map(); + reset_file_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import stylex from '@stylexjs/stylex'; +const base = stylex.create({ root: { color: 'red' } }); +const composed = stylex.create({ combined: { ...stylex.include(base.root) } });"#, + ExtractOption { + package: "@devup-ui/react".to_string(), + css_dir: "@devup-ui/react".to_string(), + single_css: true, + import_main_css: false, + import_aliases: HashMap::new() + }, + ) + .unwrap() + )); + } } diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_array_in_condition_object.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_array_in_condition_object.snap new file mode 100644 index 00000000..5e861527 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_array_in_condition_object.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: { opacity: { default: [1, 2] } } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_coexist_with_box.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_coexist_with_box.snap new file mode 100644 index 00000000..9fc61b61 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_coexist_with_box.snap @@ -0,0 +1,49 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nimport { Box } from '@devup-ui/react';\nconst styles = stylex.create({\n base: { color: 'blue', fontSize: '14px' },\n});\nconst el =
\n \n
\n
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "font-size", + value: "14px", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "padding", + value: "8px", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\nconst el =
\n
\n
\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_coexist_with_css.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_coexist_with_css.snap new file mode 100644 index 00000000..b2adbef8 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_coexist_with_css.snap @@ -0,0 +1,29 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nimport { css } from '@devup-ui/react';\nconst stylexStyles = stylex.create({\n base: { color: 'blue' },\n});\nconst devupClass = css({ bg: 'red' });\nconst el =
\n
\n
\n
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst stylexStyles = { \"base\": \"a\" };\nconst devupClass = \"b\";\nconst el =
\n
\n
\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_computed_key_in_condition_object.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_computed_key_in_condition_object.snap new file mode 100644 index 00000000..ac8c2fb5 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_computed_key_in_condition_object.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst key = ':hover';\nconst styles = stylex.create({ base: { color: { [key]: 'blue', default: 'red' } } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst key = \":hover\";\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_computed_namespace_key.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_computed_namespace_key.snap new file mode 100644 index 00000000..e19776b8 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_computed_namespace_key.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst key = 'base';\nconst styles = stylex.create({ [key]: { color: 'red' } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst key = \"base\";\nconst styles = {};\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_create_empty_namespace.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_create_empty_namespace.snap new file mode 100644 index 00000000..5cd65538 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_create_empty_namespace.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: {} });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_create_empty_object.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_create_empty_object.snap new file mode 100644 index 00000000..a62a3ca1 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_create_empty_object.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst styles = {};\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_create_multiple_namespaces.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_create_multiple_namespaces.snap new file mode 100644 index 00000000..3998d76f --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_create_multiple_namespaces.snap @@ -0,0 +1,39 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n color: 'red',\n },\n active: {\n color: 'blue',\n fontWeight: 'bold',\n }\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "font-weight", + value: "bold", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = {\n\t\"base\": \"a\",\n\t\"active\": \"b c\"\n};\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_create_non_object_arg.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_create_non_object_arg.snap new file mode 100644 index 00000000..42600773 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_create_non_object_arg.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create(\"not-object\");\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst styles = {};\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_create_numeric_values.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_create_numeric_values.snap new file mode 100644 index 00000000..d8a33c39 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_create_numeric_values.snap @@ -0,0 +1,69 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n fontSize: 16,\n opacity: 0.5,\n zIndex: 10,\n fontWeight: 700,\n padding: 8,\n flex: 1,\n }\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "flex", + value: "1", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "font-size", + value: "16", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "font-weight", + value: "700", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "opacity", + value: ".5", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "padding", + value: "8", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "z-index", + value: "10", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b c d e f\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_create_numeric_with_unit.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_create_numeric_with_unit.snap new file mode 100644 index 00000000..4478cd7c --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_create_numeric_with_unit.snap @@ -0,0 +1,29 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: { fontSize: 16, lineHeight: 1.5 } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "font-size", + value: "16", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "line-height", + value: "1.5", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_create_single_namespace_static.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_create_single_namespace_static.snap new file mode 100644 index 00000000..f932baba --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_create_single_namespace_static.snap @@ -0,0 +1,39 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n color: 'red',\n backgroundColor: 'blue',\n padding: '10px',\n }\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background-color", + value: "blue", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "padding", + value: "10px", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b c\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_bare_expression_body.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_bare_expression_body.snap new file mode 100644 index 00000000..5f109c2a --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_bare_expression_body.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: (x) => x });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_basic.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_basic.snap new file mode 100644 index 00000000..0e64214a --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_basic.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n bar: (height) => ({\n height,\n }),\n});\nconst result = stylex.props(styles.bar(h));\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Dynamic( + ExtractDynamicStyle { + property: "height", + level: 0, + identifier: "height", + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"bar\": \"b\" };\nconst result = {\n\tclassName: \"b\",\n\tstyle: { \"--a\": h }\n};\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_block_body.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_block_body.snap new file mode 100644 index 00000000..219ba188 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_block_body.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: (x) => { return { color: x }; } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_body_spread_and_computed.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_body_spread_and_computed.snap new file mode 100644 index 00000000..db0b1bee --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_body_spread_and_computed.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst other = { fontSize: '14px' };\nconst styles = stylex.create({ base: (x) => ({ ...other, [Symbol()]: x, color: 'red' }) });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst other = { fontSize: \"14px\" };\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_empty_params.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_empty_params.snap new file mode 100644 index 00000000..67c9ef33 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_empty_params.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: () => ({ color: 'red' }) });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_keyframe_ref.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_keyframe_ref.snap new file mode 100644 index 00000000..37f84121 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_keyframe_ref.snap @@ -0,0 +1,54 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst fadeIn = stylex.keyframes({ from: { opacity: '0' }, to: { opacity: '1' } });\nconst styles = stylex.create({ base: (dur) => ({ animationName: fadeIn, animationDuration: dur }) });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "animation-name", + value: "a", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Dynamic( + ExtractDynamicStyle { + property: "animation-duration", + level: 0, + identifier: "dur", + selector: None, + style_order: None, + }, + ), + Keyframes( + ExtractKeyframes { + keyframes: { + "from": [ + ExtractStaticStyle { + property: "opacity", + value: "0", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ], + "to": [ + ExtractStaticStyle { + property: "opacity", + value: "1", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ], + }, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst fadeIn = \"a\";\nconst styles = { \"base\": \"c d\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_mixed.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_mixed.snap new file mode 100644 index 00000000..255d23b9 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_mixed.snap @@ -0,0 +1,28 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n bar: (height) => ({\n height,\n width: '100%',\n }),\n});\nconst result = stylex.props(styles.bar(h));\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "width", + value: "100%", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Dynamic( + ExtractDynamicStyle { + property: "height", + level: 0, + identifier: "height", + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"bar\": \"b c\" };\nconst result = {\n\tclassName: \"b c\",\n\tstyle: { \"--a\": h }\n};\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_multi_param.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_multi_param.snap new file mode 100644 index 00000000..af63ba3e --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_multi_param.snap @@ -0,0 +1,27 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n bar: (h, w) => ({\n height: h,\n width: w,\n }),\n});\nconst result = stylex.props(styles.bar(myH, myW));\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Dynamic( + ExtractDynamicStyle { + property: "height", + level: 0, + identifier: "h", + selector: None, + style_order: None, + }, + ), + Dynamic( + ExtractDynamicStyle { + property: "width", + level: 0, + identifier: "w", + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"bar\": \"c d\" };\nconst result = {\n\tclassName: \"c d\",\n\tstyle: {\n\t\t\"--a\": myH,\n\t\t\"--b\": myW\n\t}\n};\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_numeric_value.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_numeric_value.snap new file mode 100644 index 00000000..21272d36 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_numeric_value.snap @@ -0,0 +1,28 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: (x) => ({ fontSize: 16, height: x }) });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "font-size", + value: "16", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Dynamic( + ExtractDynamicStyle { + property: "height", + level: 0, + identifier: "x", + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"b c\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_paren_non_object_body.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_paren_non_object_body.snap new file mode 100644 index 00000000..fbd06013 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_paren_non_object_body.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: (x) => (x) });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_unresolvable_value.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_unresolvable_value.snap new file mode 100644 index 00000000..17c8c8ee --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_dynamic_unresolvable_value.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst someVar = getSomething();\nconst styles = stylex.create({ base: (x) => ({ color: x, fontSize: someVar }) });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Dynamic( + ExtractDynamicStyle { + property: "color", + level: 0, + identifier: "x", + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst someVar = getSomething();\nconst styles = { \"base\": \"b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_error_computed_key.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_error_computed_key.snap new file mode 100644 index 00000000..c6946764 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_error_computed_key.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst key = 'color';\nconst styles = stylex.create({\n base: {\n [key]: 'red',\n },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst key = \"color\";\nconst styles = { \"base\": \"\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_error_destructuring_create.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_error_destructuring_create.snap new file mode 100644 index 00000000..e8384947 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_error_destructuring_create.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst { base } = stylex.create({\n base: {\n color: 'red',\n },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst { base } = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_error_non_static_value.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_error_non_static_value.snap new file mode 100644 index 00000000..de65bdbc --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_error_non_static_value.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n color: someVariable,\n fontSize: '16px',\n },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "font-size", + value: "16px", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_error_spread_in_namespace.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_error_spread_in_namespace.snap new file mode 100644 index 00000000..832b2eb5 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_error_spread_in_namespace.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst shared = { color: 'red' };\nconst styles = stylex.create({\n base: {\n ...shared,\n fontSize: '16px',\n },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "font-size", + value: "16px", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst shared = { color: \"red\" };\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_first_that_works_basic.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_first_that_works_basic.snap new file mode 100644 index 00000000..56da7529 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_first_that_works_basic.snap @@ -0,0 +1,39 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: { position: stylex.firstThatWorks('sticky', '-webkit-sticky', 'fixed') },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "position", + value: "-webkit-sticky", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "position", + value: "fixed", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "position", + value: "sticky", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b c\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_first_that_works_in_condition.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_first_that_works_in_condition.snap new file mode 100644 index 00000000..a080c7b2 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_first_that_works_in_condition.snap @@ -0,0 +1,47 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n color: 'red',\n ':hover': {\n display: stylex.firstThatWorks('grid', 'flex'),\n },\n },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "display", + value: "flex", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "display", + value: "grid", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b c\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_first_that_works_numeric.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_first_that_works_numeric.snap new file mode 100644 index 00000000..1d38276c --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_first_that_works_numeric.snap @@ -0,0 +1,29 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: { zIndex: stylex.firstThatWorks(10, 20) } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "z-index", + value: "10", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "z-index", + value: "20", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_first_that_works_two_values.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_first_that_works_two_values.snap new file mode 100644 index 00000000..b4e4c2ff --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_first_that_works_two_values.snap @@ -0,0 +1,29 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: { display: stylex.firstThatWorks('grid', 'flex') },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "display", + value: "flex", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "display", + value: "grid", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_import_default.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_import_default.snap new file mode 100644 index 00000000..b912fd89 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_import_default.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: { color: 'red' } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_import_named.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_import_named.snap new file mode 100644 index 00000000..435aab66 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_import_named.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { create } from '@stylexjs/stylex';\nconst styles = create({ base: { color: 'green' } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "green", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport { create } from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_import_namespace.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_import_namespace.snap new file mode 100644 index 00000000..776f4254 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_import_namespace.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import * as stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: { color: 'blue' } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport * as stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_import_unknown_named.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_import_unknown_named.snap new file mode 100644 index 00000000..3bee8cc3 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_import_unknown_named.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { create, unknownFunction } from '@stylexjs/stylex';\nconst styles = create({ base: { color: 'red' } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport { create, unknownFunction } from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_include_basic.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_include_basic.snap new file mode 100644 index 00000000..69113352 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_include_basic.snap @@ -0,0 +1,39 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst base = stylex.create({\n root: { color: 'red', fontSize: '16px' },\n});\nconst composed = stylex.create({\n fancy: { ...stylex.include(base.root), backgroundColor: 'blue' },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background-color", + value: "blue", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "font-size", + value: "16px", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst base = { \"root\": \"a b\" };\nconst composed = { \"fancy\": \"a b c\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_include_dynamic_target.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_include_dynamic_target.snap new file mode 100644 index 00000000..920d47c9 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_include_dynamic_target.snap @@ -0,0 +1,38 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst base = stylex.create({ dynamic: (x) => ({ color: x, fontSize: '14px' }) });\nconst composed = stylex.create({ fancy: { ...stylex.include(base.dynamic), backgroundColor: 'blue' } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background-color", + value: "blue", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "font-size", + value: "14px", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Dynamic( + ExtractDynamicStyle { + property: "color", + level: 0, + identifier: "x", + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst base = { \"dynamic\": \"b c\" };\nconst composed = { \"fancy\": \"b c d\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_include_empty_namespace.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_include_empty_namespace.snap new file mode 100644 index 00000000..84272662 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_include_empty_namespace.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst base = stylex.create({ empty: {} });\nconst composed = stylex.create({ test: { ...stylex.include(base.empty), color: 'red' } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst base = { \"empty\": \"\" };\nconst composed = { \"test\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_include_named_import.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_include_named_import.snap new file mode 100644 index 00000000..db0d6dca --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_include_named_import.snap @@ -0,0 +1,29 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { create, include } from '@stylexjs/stylex';\nconst base = create({\n root: { color: 'red' },\n});\nconst composed = create({\n fancy: { ...include(base.root), padding: '8px' },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "padding", + value: "8px", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport { create, include } from \"@stylexjs/stylex\";\nconst base = { \"root\": \"a\" };\nconst composed = { \"fancy\": \"a b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_include_only_no_own_styles.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_include_only_no_own_styles.snap new file mode 100644 index 00000000..f01c7e2f --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_include_only_no_own_styles.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst base = stylex.create({ root: { color: 'red' } });\nconst composed = stylex.create({ combined: { ...stylex.include(base.root) } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst base = { \"root\": \"a\" };\nconst composed = { \"combined\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_include_with_props.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_include_with_props.snap new file mode 100644 index 00000000..b679c4b0 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_include_with_props.snap @@ -0,0 +1,29 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst base = stylex.create({\n root: { color: 'red' },\n});\nconst composed = stylex.create({\n fancy: { ...stylex.include(base.root), backgroundColor: 'blue' },\n});\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background-color", + value: "blue", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst base = { \"root\": \"a\" };\nconst composed = { \"fancy\": \"a b\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_keyframes_in_create.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_keyframes_in_create.snap new file mode 100644 index 00000000..00e679fa --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_keyframes_in_create.snap @@ -0,0 +1,55 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst fadeIn = stylex.keyframes({\n from: { opacity: 0 },\n to: { opacity: 1 },\n});\nconst styles = stylex.create({\n base: { animationName: fadeIn, animationDuration: '0.5s' },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "animation-duration", + value: ".5s", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "animation-name", + value: "a", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Keyframes( + ExtractKeyframes { + keyframes: { + "from": [ + ExtractStaticStyle { + property: "opacity", + value: "0", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ], + "to": [ + ExtractStaticStyle { + property: "opacity", + value: "1", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ], + }, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst fadeIn = \"a\";\nconst styles = { \"base\": \"b c\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_keyframes_named_import.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_keyframes_named_import.snap new file mode 100644 index 00000000..9168fdbd --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_keyframes_named_import.snap @@ -0,0 +1,45 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { keyframes, create } from '@stylexjs/stylex';\nconst fadeIn = keyframes({\n from: { opacity: 0 },\n to: { opacity: 1 },\n});\nconst styles = create({\n base: { animationName: fadeIn },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "animation-name", + value: "a", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Keyframes( + ExtractKeyframes { + keyframes: { + "from": [ + ExtractStaticStyle { + property: "opacity", + value: "0", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ], + "to": [ + ExtractStaticStyle { + property: "opacity", + value: "1", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ], + }, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport { keyframes, create } from \"@stylexjs/stylex\";\nconst fadeIn = \"a\";\nconst styles = { \"base\": \"b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_keyframes_standalone.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_keyframes_standalone.snap new file mode 100644 index 00000000..d183b44c --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_keyframes_standalone.snap @@ -0,0 +1,35 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst fadeIn = stylex.keyframes({\n from: { opacity: 0 },\n to: { opacity: 1 },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Keyframes( + ExtractKeyframes { + keyframes: { + "from": [ + ExtractStaticStyle { + property: "opacity", + value: "0", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ], + "to": [ + ExtractStaticStyle { + property: "opacity", + value: "1", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ], + }, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst fadeIn = \"a\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_media_query.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_media_query.snap new file mode 100644 index 00000000..28cdad89 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_media_query.snap @@ -0,0 +1,35 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n color: { default: 'red', '@media (max-width: 600px)': 'blue' },\n }\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + At { + kind: Media, + query: "(max-width: 600px)", + selector: None, + }, + ), + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_multiple_conditions.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_multiple_conditions.snap new file mode 100644 index 00000000..2d221939 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_multiple_conditions.snap @@ -0,0 +1,57 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n backgroundColor: { default: 'white', ':hover': 'gray' },\n color: { default: 'black', ':hover': 'red' },\n }\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background-color", + value: "gray", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "background-color", + value: "white", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "black", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b c d\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_named_first_that_works.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_named_first_that_works.snap new file mode 100644 index 00000000..0debb57e --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_named_first_that_works.snap @@ -0,0 +1,29 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { create, firstThatWorks } from '@stylexjs/stylex';\nconst styles = create({ base: { color: firstThatWorks('red', 'blue') } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport { create, firstThatWorks } from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_named_import_types.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_named_import_types.snap new file mode 100644 index 00000000..d28534b2 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_named_import_types.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { create, types } from '@stylexjs/stylex';\nconst styles = create({ base: { width: types.length('100px') } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "width", + value: "100px", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport { create, types } from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_nested_hover_in_media.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_nested_hover_in_media.snap new file mode 100644 index 00000000..61381013 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_nested_hover_in_media.snap @@ -0,0 +1,27 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n color: { ':hover': { default: null, '@media (hover: hover)': 'blue' } },\n }\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + At { + kind: Media, + query: "(hover: hover)", + selector: Some( + "&:hover", + ), + }, + ), + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_nested_media_then_hover.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_nested_media_then_hover.snap new file mode 100644 index 00000000..f03c0266 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_nested_media_then_hover.snap @@ -0,0 +1,43 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n color: { '@media (min-width: 768px)': { default: 'red', ':hover': 'blue' } },\n }\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + At { + kind: Media, + query: "(min-width: 768px)", + selector: Some( + "&:hover", + ), + }, + ), + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: Some( + At { + kind: Media, + query: "(min-width: 768px)", + selector: None, + }, + ), + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_non_object_namespace_value.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_non_object_namespace_value.snap new file mode 100644 index 00000000..7243b22d --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_non_object_namespace_value.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: 'not-an-object', active: { color: 'blue' } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = {\n\t\"base\": \"\",\n\t\"active\": \"a\"\n};\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_non_types_non_ftw_call.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_non_types_non_ftw_call.snap new file mode 100644 index 00000000..ee31bcbb --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_non_types_non_ftw_call.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nfunction someFunc() { return 'red'; }\nconst styles = stylex.create({ base: { color: someFunc() } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nfunction someFunc() {\n\treturn \"red\";\n}\nconst styles = { \"base\": \"\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_null_value_no_css.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_null_value_no_css.snap new file mode 100644 index 00000000..98448b34 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_null_value_no_css.snap @@ -0,0 +1,23 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n color: { default: null, ':hover': 'blue' },\n }\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_alternate_only.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_alternate_only.snap new file mode 100644 index 00000000..9c01f583 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_alternate_only.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ fallback: { color: 'gray' } });\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "gray", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"fallback\": \"a\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_and.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_and.snap new file mode 100644 index 00000000..386a2a98 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_and.snap @@ -0,0 +1,29 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: { color: 'red' },\n active: { backgroundColor: 'blue' },\n});\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background-color", + value: "blue", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = {\n\t\"base\": \"a\",\n\t\"active\": \"b\"\n};\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_consequent_only.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_consequent_only.snap new file mode 100644 index 00000000..53956aa0 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_consequent_only.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ active: { color: 'red' } });\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"active\": \"a\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_none_none.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_none_none.snap new file mode 100644 index 00000000..7d8a543f --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_none_none.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: { color: 'red' } });\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_ternary.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_ternary.snap new file mode 100644 index 00000000..306670a6 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_conditional_ternary.snap @@ -0,0 +1,29 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n primary: { color: 'red' },\n secondary: { color: 'blue' },\n});\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = {\n\t\"primary\": \"a\",\n\t\"secondary\": \"b\"\n};\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_dynamic_as_non_call.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_dynamic_as_non_call.snap new file mode 100644 index 00000000..11fd0a8a --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_dynamic_as_non_call.snap @@ -0,0 +1,18 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: (x) => ({ color: x }) });\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Dynamic( + ExtractDynamicStyle { + property: "color", + level: 0, + identifier: "x", + selector: None, + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"b\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_empty_namespace.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_empty_namespace.snap new file mode 100644 index 00000000..212c08d9 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_empty_namespace.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ empty: {} });\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst styles = { \"empty\": \"\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_falsy_args.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_falsy_args.snap new file mode 100644 index 00000000..130cf163 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_falsy_args.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: { color: 'red' },\n});\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_falsy_literals.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_falsy_literals.snap new file mode 100644 index 00000000..1bf3c15b --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_falsy_literals.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: { color: 'red' } });\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_logical_unresolvable_right.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_logical_unresolvable_right.snap new file mode 100644 index 00000000..8c890902 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_logical_unresolvable_right.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: { color: 'red' } });\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_multiple_static.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_multiple_static.snap new file mode 100644 index 00000000..3aba49df --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_multiple_static.snap @@ -0,0 +1,29 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: { color: 'red' },\n active: { backgroundColor: 'blue' },\n});\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background-color", + value: "blue", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = {\n\t\"base\": \"a\",\n\t\"active\": \"b\"\n};\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_named_import.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_named_import.snap new file mode 100644 index 00000000..c859fdf3 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_named_import.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { create, props } from '@stylexjs/stylex';\nconst styles = create({\n base: { color: 'red' },\n});\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport { create, props } from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_nonexistent_member.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_nonexistent_member.snap new file mode 100644 index 00000000..8113e354 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_nonexistent_member.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: { color: 'red' } });\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_single_static.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_single_static.snap new file mode 100644 index 00000000..2336bfb9 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_single_static.snap @@ -0,0 +1,29 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: { color: 'red', fontSize: '16px' },\n});\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "font-size", + value: "16px", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_unresolvable_arg.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_unresolvable_arg.snap new file mode 100644 index 00000000..b9853d31 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_unresolvable_arg.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: { color: 'red' } });\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_with_conditions.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_with_conditions.snap new file mode 100644 index 00000000..b8c016ce --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_with_conditions.snap @@ -0,0 +1,33 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: { color: { default: 'red', ':hover': 'blue' } },\n});\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_props_zero_args.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_props_zero_args.snap new file mode 100644 index 00000000..91e74606 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_props_zero_args.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst el =
;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst el =
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_class_focus.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_class_focus.snap new file mode 100644 index 00000000..61382df7 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_class_focus.snap @@ -0,0 +1,33 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n color: { default: 'red', ':focus': 'blue' },\n }\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Selector( + "&:focus", + ), + ), + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_class_hover.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_class_hover.snap new file mode 100644 index 00000000..069e0f34 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_class_hover.snap @@ -0,0 +1,33 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n color: { default: 'red', ':hover': 'blue' },\n }\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "blue", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_element_before.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_element_before.snap new file mode 100644 index 00000000..1d48815f --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_element_before.snap @@ -0,0 +1,37 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n '::before': { content: '\"\"', display: 'block' },\n }\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "content", + value: "\"\"", + level: 0, + selector: Some( + Selector( + "&::before", + ), + ), + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "display", + value: "block", + level: 0, + selector: Some( + Selector( + "&::before", + ), + ), + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_element_placeholder.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_element_placeholder.snap new file mode 100644 index 00000000..8671a9f3 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_element_placeholder.snap @@ -0,0 +1,37 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n '::placeholder': { color: '#999', opacity: 1 },\n }\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "#999", + level: 0, + selector: Some( + Selector( + "&::placeholder", + ), + ), + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "opacity", + value: "1", + level: 0, + selector: Some( + Selector( + "&::placeholder", + ), + ), + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_inner_spread_and_computed.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_inner_spread_and_computed.snap new file mode 100644 index 00000000..8b63ae83 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_inner_spread_and_computed.snap @@ -0,0 +1,23 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst other = { fontSize: '14px' };\nconst styles = stylex.create({ base: { ':hover': { ...other, [Symbol()]: 'val', color: 'red' } } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst other = { fontSize: \"14px\" };\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_non_object_value.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_non_object_value.snap new file mode 100644 index 00000000..cb12be87 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_pseudo_non_object_value.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: { ':hover': 'invalid' } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_spread_at_namespace_level.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_spread_at_namespace_level.snap new file mode 100644 index 00000000..6757f7d9 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_spread_at_namespace_level.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst other = {};\nconst styles = stylex.create({ ...other, base: { color: 'red' } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst other = {};\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_spread_in_condition_object.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_spread_in_condition_object.snap new file mode 100644 index 00000000..754d3a5d --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_spread_in_condition_object.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst spreadObj = { ':hover': 'blue' };\nconst styles = stylex.create({ base: { color: { ...spreadObj, default: 'red' } } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst spreadObj = { \":hover\": \"blue\" };\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_spread_non_include_call.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_spread_non_include_call.snap new file mode 100644 index 00000000..c0b3b75e --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_spread_non_include_call.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nfunction notInclude() { return {}; }\nconst styles = stylex.create({ base: { ...notInclude(), color: 'red' } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nfunction notInclude() {\n\treturn {};\n}\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_supports_query.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_supports_query.snap new file mode 100644 index 00000000..fcfcdda0 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_supports_query.snap @@ -0,0 +1,35 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n display: { default: 'block', '@supports (display: grid)': 'grid' },\n }\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "display", + value: "block", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "display", + value: "grid", + level: 0, + selector: Some( + At { + kind: Supports, + query: "(display: grid)", + selector: None, + }, + ), + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_types_color_strip.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_types_color_strip.snap new file mode 100644 index 00000000..a72f87db --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_types_color_strip.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: { color: stylex.types.color('red') },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_types_in_condition.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_types_in_condition.snap new file mode 100644 index 00000000..2488f50a --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_types_in_condition.snap @@ -0,0 +1,35 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n width: {\n default: '100%',\n '@media (min-width: 768px)': stylex.types.length('50%'),\n },\n },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "width", + value: "100%", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "width", + value: "50%", + level: 0, + selector: Some( + At { + kind: Media, + query: "(min-width: 768px)", + selector: None, + }, + ), + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_types_length_strip.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_types_length_strip.snap new file mode 100644 index 00000000..766a07de --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_types_length_strip.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: { width: stylex.types.length('100px') },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "width", + value: "100px", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_types_numeric.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_types_numeric.snap new file mode 100644 index 00000000..28c1eda9 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_types_numeric.snap @@ -0,0 +1,19 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: { fontSize: stylex.types.length(16) } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "font-size", + value: "16", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_types_unresolvable.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_types_unresolvable.snap new file mode 100644 index 00000000..e177e7d8 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_types_unresolvable.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({ base: { fontSize: stylex.types.length(someVar) } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_types_unresolvable_in_condition.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_types_unresolvable_in_condition.snap new file mode 100644 index 00000000..01d6a8e9 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_types_unresolvable_in_condition.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst someVar = getSomething();\nconst styles = stylex.create({ base: { fontSize: { default: stylex.types.length(someVar) } } });\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "import stylex from \"@stylexjs/stylex\";\nconst someVar = getSomething();\nconst styles = { \"base\": \"\" };\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__stylex_warn_shorthand_property.snap b/libs/extractor/src/snapshots/extractor__tests__stylex_warn_shorthand_property.snap new file mode 100644 index 00000000..c52623cc --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__stylex_warn_shorthand_property.snap @@ -0,0 +1,29 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import stylex from '@stylexjs/stylex';\nconst styles = stylex.create({\n base: {\n margin: '10px',\n color: 'red',\n },\n});\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false,\n import_aliases: HashMap::new()\n},).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "color", + value: "red", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + Static( + ExtractStaticStyle { + property: "margin", + value: "10px", + level: 0, + selector: None, + style_order: None, + layer: None, + }, + ), + }, + code: "import \"@devup-ui/react/devup-ui.css\";\nimport stylex from \"@stylexjs/stylex\";\nconst styles = { \"base\": \"a b\" };\n", +} diff --git a/libs/extractor/src/stylex.rs b/libs/extractor/src/stylex.rs new file mode 100644 index 00000000..05e1a930 --- /dev/null +++ b/libs/extractor/src/stylex.rs @@ -0,0 +1,292 @@ +use css::style_selector::{AtRuleKind, StyleSelector}; +use oxc_ast::ast::{Expression, ObjectPropertyKind}; + +use crate::utils::{get_string_by_literal_expression, get_string_by_property_key}; + +/// Which StyleX function a named import refers to +#[derive(Debug, Clone, PartialEq)] +pub enum StylexFunction { + Create, + Props, + Keyframes, + FirstThatWorks, + DefineVars, + CreateTheme, + Include, +} + +/// Check if a call expression is stylex.firstThatWorks() or named firstThatWorks(). +pub fn is_first_that_works_call(callee: &Expression) -> bool { + // stylex.firstThatWorks(...) + if let Expression::StaticMemberExpression(member) = callee + && member.property.name.as_str() == "firstThatWorks" + { + return true; + } + // firstThatWorks(...) (named import) + if let Expression::Identifier(ident) = callee + && ident.name.as_str() == "firstThatWorks" + { + return true; + } + false +} + +/// Check if a call expression is stylex.include() or named include(). +/// This is a static check that does NOT require access to the visitor. +pub fn is_include_call_static(callee: &Expression) -> bool { + if let Expression::StaticMemberExpression(member) = callee + && member.property.name.as_str() == "include" + { + return true; + } + if let Expression::Identifier(ident) = callee + && ident.name.as_str() == "include" + { + return true; + } + false +} + +/// A reference to a stylex.include(base.member) call found inside stylex.create(). +#[derive(Debug, Clone)] +pub struct StylexIncludeRef { + pub var_name: String, + pub member_name: String, +} + +/// Check if a call expression is stylex.types.X() or types.X() (type wrapper). +pub fn is_types_call(callee: &Expression) -> bool { + if let Expression::StaticMemberExpression(member) = callee { + // stylex.types.X(...) + if let Expression::StaticMemberExpression(inner) = &member.object { + return inner.property.name.as_str() == "types"; + } + // types.X(...) (named import) + if let Expression::Identifier(ident) = &member.object { + return ident.name.as_str() == "types"; + } + } + false +} + +/// Convert camelCase CSS property name to kebab-case. +/// StyleX uses standard CSS properties only — NO devup-ui shorthand expansion. +pub fn normalize_stylex_property(name: &str) -> String { + css::utils::to_kebab_case(name) +} + +/// Intermediate selector parts collected during recursion. +#[derive(Debug, Clone)] +pub enum SelectorPart { + /// Pseudo-class or pseudo-element, e.g. ":hover", "::placeholder" + Pseudo(String), + /// At-rule condition, e.g. @media (max-width: 600px) + AtRule { kind: AtRuleKind, query: String }, +} + +/// A single decomposed style entry from a value-level condition object. +#[derive(Debug)] +pub struct DecomposedStyle { + pub property: String, + /// `None` means null (no CSS emitted, tracked for atomic override). + pub value: Option, + pub selector: Option, +} + +/// Information about a dynamic StyleX namespace (arrow function in stylex.create()) +#[derive(Debug, Clone)] +pub struct StylexDynamicInfo { + /// Combined class name string for all properties (static + dynamic) + pub class_name: String, + /// Maps (param_index, css_variable_name) for each dynamic property + pub css_vars: Vec<(usize, String)>, +} + +/// A StyleX namespace entry — either static or dynamic (arrow function) +#[derive(Debug, Clone)] +pub enum StylexNamespaceValue { + /// Static namespace: just a className string + Static(String), + /// Dynamic namespace (from arrow function): className + CSS variable mappings + Dynamic(StylexDynamicInfo), +} + +/// Decompose a StyleX value-level condition object into flat (css_value, selector) tuples. +/// +/// StyleX allows values to be objects with condition keys: +/// ```js +/// { color: { default: 'red', ':hover': 'blue', '@media (max-width:600px)': 'green' } } +/// ``` +/// +/// This recursively walks the value tree and returns flat tuples of (value_or_none, selector). +/// `None` value means null/no CSS emitted (but tracked for atomic override). +pub fn decompose_value_conditions( + css_property: &str, + value: &Expression, + parent_selectors: &[SelectorPart], +) -> Vec { + // String literal → leaf + if let Some(s) = get_string_by_literal_expression(value) { + return vec![DecomposedStyle { + property: css_property.to_string(), + value: Some(s), + selector: compose_selectors(parent_selectors), + }]; + } + + // NullLiteral → tracked but no CSS + if matches!(value, Expression::NullLiteral(_)) { + return vec![DecomposedStyle { + property: css_property.to_string(), + value: None, + selector: compose_selectors(parent_selectors), + }]; + } + + // CallExpression: firstThatWorks() → multiple fallback values with current selectors + if let Expression::CallExpression(call) = value + && is_first_that_works_call(&call.callee) + { + let mut results = vec![]; + for arg in call.arguments.iter().rev() { + let arg_expr = arg.to_expression(); + if let Some(s) = get_string_by_literal_expression(arg_expr) { + results.push(DecomposedStyle { + property: css_property.to_string(), + value: Some(s), + selector: compose_selectors(parent_selectors), + }); + } + } + return results; + } + + // CallExpression: types.*() → extract inner value, pass through selectors + if let Expression::CallExpression(call) = value + && is_types_call(&call.callee) + && !call.arguments.is_empty() + { + let inner = call.arguments[0].to_expression(); + if let Some(s) = get_string_by_literal_expression(inner) { + return vec![DecomposedStyle { + property: css_property.to_string(), + value: Some(s), + selector: compose_selectors(parent_selectors), + }]; + } + return vec![]; + } + + // ObjectExpression → recurse into condition keys + let Expression::ObjectExpression(obj) = value else { + return vec![]; + }; + + let mut results = vec![]; + + for prop in obj.properties.iter() { + let ObjectPropertyKind::ObjectProperty(prop) = prop else { + continue; + }; + let Some(key) = get_string_by_property_key(&prop.key) else { + continue; + }; + + if key == "default" { + results.extend(decompose_value_conditions( + css_property, + &prop.value, + parent_selectors, + )); + } else if key.starts_with("::") || key.starts_with(':') { + let mut new_selectors = parent_selectors.to_vec(); + new_selectors.push(SelectorPart::Pseudo(key)); + results.extend(decompose_value_conditions( + css_property, + &prop.value, + &new_selectors, + )); + } else if let Some((kind, query)) = parse_at_rule_key(&key) { + let mut new_selectors = parent_selectors.to_vec(); + new_selectors.push(SelectorPart::AtRule { kind, query }); + results.extend(decompose_value_conditions( + css_property, + &prop.value, + &new_selectors, + )); + } + } + + results +} + +/// Compose a list of selector parts into a single `StyleSelector`. +fn compose_selectors(parts: &[SelectorPart]) -> Option { + if parts.is_empty() { + return None; + } + + let pseudos: Vec<&str> = parts + .iter() + .filter_map(|p| match p { + SelectorPart::Pseudo(s) => Some(s.as_str()), + SelectorPart::AtRule { .. } => None, + }) + .collect(); + + let at_rules: Vec<(AtRuleKind, &str)> = parts + .iter() + .filter_map(|p| match p { + SelectorPart::AtRule { kind, query } => Some((*kind, query.as_str())), + SelectorPart::Pseudo(_) => None, + }) + .collect(); + + let pseudo_str = if pseudos.is_empty() { + None + } else { + Some(format!("&{}", pseudos.join(""))) + }; + + if at_rules.is_empty() { + pseudo_str.map(StyleSelector::Selector) + } else { + let (kind, query) = at_rules.last().expect("at_rules is non-empty"); + Some(StyleSelector::At { + kind: *kind, + query: query.to_string(), + selector: pseudo_str, + }) + } +} + +/// Parse an at-rule key like `"@media (max-width: 600px)"` into kind + query. +fn parse_at_rule_key(key: &str) -> Option<(AtRuleKind, String)> { + key.strip_prefix("@media") + .map(|q| (AtRuleKind::Media, q.trim().to_string())) + .or_else(|| { + key.strip_prefix("@supports") + .map(|q| (AtRuleKind::Supports, q.trim().to_string())) + }) + .or_else(|| { + key.strip_prefix("@container") + .map(|q| (AtRuleKind::Container, q.trim().to_string())) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_normalize_stylex_property() { + assert_eq!( + normalize_stylex_property("backgroundColor"), + "background-color" + ); + assert_eq!(normalize_stylex_property("fontSize"), "font-size"); + assert_eq!(normalize_stylex_property("color"), "color"); + assert_eq!(normalize_stylex_property("zIndex"), "z-index"); + } +} diff --git a/libs/extractor/src/visit.rs b/libs/extractor/src/visit.rs index 7d3bfe64..d6e0dab3 100644 --- a/libs/extractor/src/visit.rs +++ b/libs/extractor/src/visit.rs @@ -6,6 +6,7 @@ use crate::extract_style::extract_css::ExtractCss; use crate::extract_style::extract_keyframes::ExtractKeyframes; use crate::extractor::KeyframesExtractResult; use crate::extractor::extract_keyframes_from_expression::extract_keyframes_from_expression; +use crate::extractor::extract_style_from_stylex::extract_stylex_namespace_styles; use crate::extractor::{ ExtractResult, GlobalExtractResult, extract_global_style_from_expression::extract_global_style_from_expression, @@ -13,8 +14,9 @@ use crate::extractor::{ extract_style_from_jsx::extract_style_from_jsx, extract_style_from_styled::extract_style_from_styled, }; -use crate::gen_class_name::gen_class_names; +use crate::gen_class_name::{gen_class_names, merge_expression_for_class_name}; use crate::prop_modify_utils::{modify_prop_object, modify_props}; +use crate::stylex::{StylexDynamicInfo, StylexFunction, StylexNamespaceValue}; use crate::util_type::UtilType; use crate::{ExtractStyleProp, ExtractStyleValue}; use css::disassemble_property; @@ -26,7 +28,7 @@ use oxc_ast::ast::JSXAttributeName::Identifier; use oxc_ast::ast::{ Argument, BindingPattern, CallExpression, Expression, ImportDeclaration, ImportOrExportKind, JSXAttributeItem, JSXAttributeValue, JSXChild, JSXElement, ObjectPropertyKind, Program, - Statement, VariableDeclarator, WithClause, + PropertyKey, PropertyKind, Statement, VariableDeclarator, WithClause, }; use oxc_ast_visit::VisitMut; use oxc_ast_visit::walk_mut::{ @@ -57,6 +59,21 @@ pub struct DevupVisitor<'a> { pub css_files: Vec, pub styles: FxHashSet, styled_import: Option, + /// Tracked StyleX default/namespace import name (e.g., `stylex` from `import stylex from '...'`) + stylex_import: Option, + /// Tracked StyleX named imports (e.g., `create` from `import { create } from '...'`) + stylex_named_imports: FxHashMap, + /// Pending StyleX namespace map from the most recent stylex.create() call. + /// Set in visit_expression, consumed in visit_variable_declarator. + stylex_pending_create: Option>, + /// Maps variable names to their namespace→className mappings from stylex.create(). + /// e.g., "styles" → { "base" → "a b", "active" → "c" } + stylex_namespaces: FxHashMap>, + /// Pending keyframe animation name from most recent stylex.keyframes() call. + stylex_pending_keyframe_name: Option, + /// Maps variable names to their keyframe animation names. + /// e.g., "fadeIn" → "a-a" + stylex_keyframe_names: FxHashMap, } impl<'a> DevupVisitor<'a> { @@ -80,6 +97,222 @@ impl<'a> DevupVisitor<'a> { util_imports: FxHashMap::default(), split_filename, styled_import: None, + stylex_import: None, + stylex_named_imports: FxHashMap::default(), + stylex_pending_create: None, + stylex_namespaces: FxHashMap::default(), + stylex_pending_keyframe_name: None, + stylex_keyframe_names: FxHashMap::default(), + } + } +} + +impl<'a> DevupVisitor<'a> { + /// Check if a callee expression is a `stylex.create(...)` or named `create(...)` call. + fn is_stylex_create_call(&self, callee: &Expression) -> bool { + // Check namespace/default call: stylex.create(...) + if let Some(stylex_name) = &self.stylex_import + && let Expression::StaticMemberExpression(member) = callee + && let Expression::Identifier(ident) = &member.object + && ident.name.as_str() == stylex_name.as_str() + && member.property.name.as_str() == "create" + { + return true; + } + // Check named import call: create(...) + if let Expression::Identifier(ident) = callee + && let Some(StylexFunction::Create) = self.stylex_named_imports.get(ident.name.as_str()) + { + return true; + } + false + } + + /// Check if a callee expression is a `stylex.props(...)` or named `props(...)` call. + fn is_stylex_props_call(&self, callee: &Expression) -> bool { + // Check namespace/default call: stylex.props(...) + if let Some(stylex_name) = &self.stylex_import + && let Expression::StaticMemberExpression(member) = callee + && let Expression::Identifier(ident) = &member.object + && ident.name.as_str() == stylex_name.as_str() + && member.property.name.as_str() == "props" + { + return true; + } + // Check named import call: props(...) + if let Expression::Identifier(ident) = callee + && let Some(StylexFunction::Props) = self.stylex_named_imports.get(ident.name.as_str()) + { + return true; + } + false + } + + /// Check if a callee is stylex.keyframes() or named keyframes() call. + fn is_stylex_keyframes_call(&self, callee: &Expression) -> bool { + if let Some(stylex_name) = &self.stylex_import + && let Expression::StaticMemberExpression(member) = callee + && let Expression::Identifier(ident) = &member.object + && ident.name.as_str() == stylex_name.as_str() + && member.property.name.as_str() == "keyframes" + { + return true; + } + if let Expression::Identifier(ident) = callee + && let Some(StylexFunction::Keyframes) = + self.stylex_named_imports.get(ident.name.as_str()) + { + return true; + } + false + } + + /// Resolve stylex.props() arguments to className expressions and style properties. + /// Returns (class_exprs, style_props) where style_props are CSS variable assignments + /// from dynamic namespace calls like `styles.bar(h)`. + fn resolve_stylex_props_args( + &self, + arguments: &mut oxc_allocator::Vec<'a, Argument<'a>>, + ) -> (Vec>, Vec>) { + let mut class_exprs: Vec> = vec![]; + let mut style_props: Vec> = vec![]; + + for arg in arguments.iter() { + let expr = arg.to_expression(); + // Check for dynamic namespace call first: styles.bar(h) + if let Expression::CallExpression(call) = expr + && let Some((class_expr, props)) = self.resolve_stylex_dynamic_call(call) + { + class_exprs.push(class_expr); + style_props.extend(props); + continue; + } + if let Some(class_expr) = self.resolve_stylex_arg(expr) { + class_exprs.push(class_expr); + } + } + + (class_exprs, style_props) + } + + /// Resolve a dynamic namespace call like `styles.bar(h)` to (className, style_props). + fn resolve_stylex_dynamic_call( + &self, + call: &CallExpression<'a>, + ) -> Option<(Expression<'a>, Vec>)> { + if let Expression::StaticMemberExpression(member) = &call.callee + && let Expression::Identifier(obj) = &member.object + && let Some(ns_map) = self.stylex_namespaces.get(obj.name.as_str()) + && let Some(StylexNamespaceValue::Dynamic(info)) = + ns_map.get(member.property.name.as_str()) + { + let class_expr = + self.ast + .expression_string_literal(SPAN, self.ast.atom(&info.class_name), None); + + let mut props = vec![]; + for (param_idx, var_name) in &info.css_vars { + if let Some(arg) = call.arguments.get(*param_idx) { + let arg_expr = arg.to_expression().clone_in(self.ast.allocator); + props.push(self.ast.object_property_kind_object_property( + SPAN, + PropertyKind::Init, + PropertyKey::StringLiteral(self.ast.alloc_string_literal( + SPAN, + self.ast.atom(var_name), + None, + )), + arg_expr, + false, + false, + false, + )); + } + } + + Some((class_expr, props)) + } else { + None + } + } + + /// Resolve a single stylex.props() argument to a className expression. + fn resolve_stylex_arg(&self, expr: &Expression<'a>) -> Option> { + match expr { + // styles.base → StaticMemberExpression + Expression::StaticMemberExpression(member) => { + if let Expression::Identifier(obj) = &member.object + && let Some(ns_map) = self.stylex_namespaces.get(obj.name.as_str()) + && let Some(StylexNamespaceValue::Static(cn)) = + ns_map.get(member.property.name.as_str()) + && !cn.is_empty() + { + return Some( + self.ast + .expression_string_literal(SPAN, self.ast.atom(cn), None), + ); + } + None + } + // isActive && styles.active → LogicalExpression(And) + Expression::LogicalExpression(logical) + if logical.operator == oxc_ast::ast::LogicalOperator::And => + { + // The right side should be the namespace reference + if let Some(class_expr) = self.resolve_stylex_arg(&logical.right) { + // Build: condition ? " className" : "" + let condition = logical.left.clone_in(self.ast.allocator); + Some(self.ast.expression_conditional( + SPAN, + condition, + class_expr, + self.ast.expression_string_literal(SPAN, "", None), + )) + } else { + None + } + } + // cond ? styles.a : styles.b → ConditionalExpression + Expression::ConditionalExpression(cond) => { + let consequent = self.resolve_stylex_arg(&cond.consequent); + let alternate = self.resolve_stylex_arg(&cond.alternate); + match (consequent, alternate) { + (Some(cons), Some(alt)) => { + let test = cond.test.clone_in(self.ast.allocator); + Some(self.ast.expression_conditional(SPAN, test, cons, alt)) + } + (Some(cons), None) => { + let test = cond.test.clone_in(self.ast.allocator); + Some(self.ast.expression_conditional( + SPAN, + test, + cons, + self.ast.expression_string_literal(SPAN, "", None), + )) + } + (None, Some(alt)) => { + let test = cond.test.clone_in(self.ast.allocator); + Some(self.ast.expression_conditional( + SPAN, + self.ast.expression_unary( + SPAN, + oxc_ast::ast::UnaryOperator::LogicalNot, + test, + ), + alt, + self.ast.expression_string_literal(SPAN, "", None), + )) + } + (None, None) => None, + } + } + // false, null, undefined, 0, "" → falsy, skip + Expression::BooleanLiteral(b) if !b.value => None, + Expression::NullLiteral(_) => None, + Expression::NumericLiteral(n) if n.value == 0.0 => None, + Expression::StringLiteral(s) if s.value.is_empty() => None, + // Anything else we can't resolve → skip + _ => None, } } } @@ -179,6 +412,151 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { } } + // Handle StyleX: stylex.create({...}) calls + if let Expression::CallExpression(call) = it + && self.is_stylex_create_call(&call.callee) + && call.arguments.len() == 1 + { + let arg = call.arguments[0].to_expression_mut(); + let namespaces = + extract_stylex_namespace_styles(&self.ast, arg, &self.stylex_keyframe_names); + + let mut namespace_map: FxHashMap = FxHashMap::default(); + let mut properties = oxc_allocator::Vec::new_in(self.ast.allocator); + for (ns_name, mut styles, css_vars, include_refs) in namespaces { + let class_name = + gen_class_names(&self.ast, &mut styles, None, self.split_filename.as_deref()); + self.styles + .extend(styles.into_iter().flat_map(|ex| ex.extract())); + + // Extract className string for props() resolution + let mut class_name_str = class_name.as_ref().map_or(String::new(), |expr| { + if let Expression::StringLiteral(s) = expr { + s.value.to_string() + } else { + String::new() + } + }); + + // Resolve include() references — prepend included classNames + for inc_ref in &include_refs { + if let Some(ns) = self.stylex_namespaces.get(&inc_ref.var_name) + && let Some(ns_value) = ns.get(&inc_ref.member_name) + { + let included_class = match ns_value { + StylexNamespaceValue::Static(s) => s.clone(), + StylexNamespaceValue::Dynamic(info) => info.class_name.clone(), + }; + if !included_class.is_empty() { + if class_name_str.is_empty() { + class_name_str = included_class; + } else { + class_name_str = format!("{} {}", included_class, class_name_str); + } + } + } + } + + let ns_value = if let Some(vars) = css_vars { + StylexNamespaceValue::Dynamic(StylexDynamicInfo { + class_name: class_name_str.clone(), + css_vars: vars, + }) + } else { + StylexNamespaceValue::Static(class_name_str.clone()) + }; + namespace_map.insert(ns_name.clone(), ns_value); + + // If include refs changed the className, use the combined string + let value = if !include_refs.is_empty() && !class_name_str.is_empty() { + self.ast + .expression_string_literal(SPAN, self.ast.atom(&class_name_str), None) + } else { + class_name.unwrap_or_else(|| { + self.ast + .expression_string_literal(SPAN, self.ast.atom(""), None) + }) + }; + + properties.push(self.ast.object_property_kind_object_property( + SPAN, + PropertyKind::Init, + PropertyKey::StringLiteral(self.ast.alloc_string_literal( + SPAN, + self.ast.atom(&ns_name), + None, + )), + value, + false, + false, + false, + )); + } + + self.stylex_pending_create = Some(namespace_map); + *it = self.ast.expression_object(SPAN, properties); + } + + // Handle StyleX: stylex.keyframes({...}) calls + if let Expression::CallExpression(call) = it + && self.is_stylex_keyframes_call(&call.callee) + && call.arguments.len() == 1 + { + let arg = call.arguments[0].to_expression_mut(); + let KeyframesExtractResult { keyframes } = + extract_keyframes_from_expression(&self.ast, arg); + let name = keyframes + .extract(self.split_filename.as_deref()) + .to_string(); + self.styles.insert(ExtractStyleValue::Keyframes(keyframes)); + self.stylex_pending_keyframe_name = Some(name.clone()); + *it = self + .ast + .expression_string_literal(SPAN, self.ast.atom(&name), None); + } + + // Handle StyleX: stylex.props(...) calls + if let Expression::CallExpression(call) = it + && self.is_stylex_props_call(&call.callee) + { + let (class_exprs, style_props) = self.resolve_stylex_props_args(&mut call.arguments); + + // Build className expression using existing merge utility + let class_name_expr = merge_expression_for_class_name(&self.ast, class_exprs) + .unwrap_or_else(|| self.ast.expression_string_literal(SPAN, "", None)); + + // Build replacement: { className: , style?: { ... } } + let mut props = oxc_allocator::Vec::new_in(self.ast.allocator); + props.push(self.ast.object_property_kind_object_property( + SPAN, + PropertyKind::Init, + PropertyKey::StaticIdentifier(self.ast.alloc_identifier_name(SPAN, "className")), + class_name_expr, + false, + false, + false, + )); + + // Add style property for dynamic CSS variables + if !style_props.is_empty() { + let style_obj = self.ast.expression_object( + SPAN, + oxc_allocator::Vec::from_iter_in(style_props, self.ast.allocator), + ); + props.push(self.ast.object_property_kind_object_property( + SPAN, + PropertyKind::Init, + PropertyKey::StaticIdentifier(self.ast.alloc_identifier_name(SPAN, "style")), + style_obj, + false, + false, + false, + )); + } + + *it = self.ast.expression_object(SPAN, props); + } + if let Expression::CallExpression(call) = it { let util_import_key = if let Expression::Identifier(ident) = &call.callee { Some(ident.name.to_string()) @@ -553,6 +931,30 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { } walk_variable_declarator(self, it); + + // Phase 4c: Check for destructuring of stylex.create() + if self.stylex_pending_create.is_some() && it.id.get_binding_identifier().is_none() { + eprintln!( + "[stylex] ERROR: Destructuring stylex.create() is not supported. Assign the result to a single variable (e.g., `const styles = stylex.create({{...}})`)." + ); + self.stylex_pending_create.take(); + } + + // After walking, capture stylex.create() variable binding + if let Some(pending) = self.stylex_pending_create.take() + && let Some(ident) = it.id.get_binding_identifier() + { + self.stylex_namespaces + .insert(ident.name.to_string(), pending); + } + + // Capture stylex.keyframes() variable binding + if let Some(name) = self.stylex_pending_keyframe_name.take() + && let Some(ident) = it.id.get_binding_identifier() + { + self.stylex_keyframe_names + .insert(ident.name.to_string(), name); + } } fn visit_import_declaration(&mut self, it: &mut ImportDeclaration<'a>) { if it.source.value != self.package @@ -623,6 +1025,36 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { } } } + } else if it.source.value == "@stylexjs/stylex" { + if let Some(specifiers) = &it.specifiers { + for specifier in specifiers { + match specifier { + ImportDeclarationSpecifier::ImportDefaultSpecifier(default_spec) => { + self.stylex_import = Some(default_spec.local.name.to_string()); + } + ImportDeclarationSpecifier::ImportNamespaceSpecifier(ns_spec) => { + self.stylex_import = Some(ns_spec.local.name.to_string()); + } + ImportSpecifier(named_spec) => { + let imported = named_spec.imported.to_string(); + let local = named_spec.local.name.to_string(); + let func = match imported.as_str() { + "create" => Some(StylexFunction::Create), + "props" => Some(StylexFunction::Props), + "keyframes" => Some(StylexFunction::Keyframes), + "firstThatWorks" => Some(StylexFunction::FirstThatWorks), + "defineVars" => Some(StylexFunction::DefineVars), + "createTheme" => Some(StylexFunction::CreateTheme), + "include" => Some(StylexFunction::Include), + _ => None, + }; + if let Some(func) = func { + self.stylex_named_imports.insert(local, func); + } + } + } + } + } } else { walk_import_declaration(self, it); } diff --git a/packages/react/src/__tests__/index.test.ts b/packages/react/src/__tests__/index.test.ts index a9eee4e6..0f5f5214 100644 --- a/packages/react/src/__tests__/index.test.ts +++ b/packages/react/src/__tests__/index.test.ts @@ -18,6 +18,7 @@ describe('export', () => { globalCss: expect.any(Function), keyframes: expect.any(Function), styled: expect.any(Object), + stylex: expect.any(Object), ThemeScript: expect.any(Function), diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index c979536d..a84fd621 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -22,4 +22,5 @@ export { initTheme } from './utils/init-theme' export { keyframes } from './utils/keyframes' export { setTheme } from './utils/set-theme' export { styled } from './utils/styled' +export * as stylex from './utils/stylex' export type { CustomColors } from 'csstype-extra' diff --git a/packages/react/src/utils/__tests__/stylex.test.ts b/packages/react/src/utils/__tests__/stylex.test.ts new file mode 100644 index 00000000..d2dbb5cb --- /dev/null +++ b/packages/react/src/utils/__tests__/stylex.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, it } from 'bun:test' + +import { + create, + createTheme, + defineVars, + firstThatWorks, + include, + keyframes, + props, + types, +} from '../stylex' + +describe('stylex', () => { + it('create should throw at runtime', () => { + expect(() => create({ base: { color: 'red' } })).toThrowError( + 'Cannot run on the runtime', + ) + }) + + it('props should throw at runtime', () => { + expect(() => props()).toThrowError('Cannot run on the runtime') + }) + + it('keyframes should throw at runtime', () => { + expect(() => + keyframes({ from: { opacity: '0' }, to: { opacity: '1' } }), + ).toThrowError('Cannot run on the runtime') + }) + + it('firstThatWorks should throw at runtime', () => { + expect(() => firstThatWorks('red', 'blue')).toThrowError( + 'Cannot run on the runtime', + ) + }) + + it('types.length should throw at runtime', () => { + expect(() => types.length('10px')).toThrowError('Cannot run on the runtime') + }) + + it('types.color should throw at runtime', () => { + expect(() => types.color('red')).toThrowError('Cannot run on the runtime') + }) + + it('include should throw at runtime', () => { + expect(() => include({ color: 'red' })).toThrowError( + 'Cannot run on the runtime', + ) + }) + + it('defineVars should throw at runtime', () => { + expect(() => defineVars({ primary: 'red' })).toThrowError( + 'Cannot run on the runtime', + ) + }) + + it('createTheme should throw at runtime', () => { + expect(() => + createTheme({ primary: '--x' }, { primary: 'blue' }), + ).toThrowError('Cannot run on the runtime') + }) +}) diff --git a/packages/react/src/utils/stylex.ts b/packages/react/src/utils/stylex.ts new file mode 100644 index 00000000..7824d78e --- /dev/null +++ b/packages/react/src/utils/stylex.ts @@ -0,0 +1,78 @@ +type StyleValue = string | number | null | undefined + +type StyleProperties = Record> + +interface StyleXTypes { + angle(value: T): T + color(value: T): T + image(value: T): T + integer(value: T): T + length(value: T): T + lengthPercentage(value: T): T + number(value: T): T + percentage(value: T): T + resolution(value: T): T + time(value: T): T + transformFunction(value: T): T + transformList(value: T): T + url(value: T): T +} + +export function create>( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + styles: S, +): { readonly [K in keyof S]: S[K] } { + throw new Error('Cannot run on the runtime') +} + +export function props( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ...styles: ReadonlyArray +): { className?: string; style?: Record } { + throw new Error('Cannot run on the runtime') +} + +export function keyframes( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + frames: Record, +): string { + throw new Error('Cannot run on the runtime') +} + +export function firstThatWorks( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ...values: T[] +): T { + throw new Error('Cannot run on the runtime') +} + +export function include( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + style: S, +): S { + throw new Error('Cannot run on the runtime') +} + +export function defineVars>( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + vars: V, +): { readonly [K in keyof V]: string } { + throw new Error('Cannot run on the runtime') +} + +export function createTheme>( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + vars: V, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + overrides: { readonly [K in keyof V]: StyleValue }, +): Record { + throw new Error('Cannot run on the runtime') +} + +export const types: StyleXTypes = new Proxy({} as StyleXTypes, { + get() { + return () => { + throw new Error('Cannot run on the runtime') + } + }, +})