stylex_transform/shared/transformers/
stylex_create_theme.rs1use std::{cmp::Ordering, rc::Rc};
2
3use indexmap::IndexMap;
4use stylex_macros::{stylex_panic, stylex_unimplemented};
5use swc_core::ecma::ast::KeyValueProp;
6
7use crate::shared::enums::data_structures::evaluate_result_value::EvaluateResultValue;
8use crate::shared::enums::data_structures::flat_compiled_styles_value::FlatCompiledStylesValue;
9use crate::shared::structures::functions::FunctionMap;
10use crate::shared::structures::state_manager::StateManager;
11use crate::shared::structures::types::{FlatCompiledStyles, InjectableStylesMap};
12use crate::shared::utils::ast::convertors::{convert_expr_to_str, convert_key_value_to_str};
13use crate::shared::utils::common::{
14 create_hash, find_and_swap_remove, get_css_value, get_key_values_from_object,
15 round_to_decimal_places,
16};
17use crate::shared::utils::core::define_vars_utils::{
18 collect_vars_by_at_rules, priority_for_at_rule, wrap_with_at_rules,
19};
20use crate::shared::utils::validators::validate_theme_variables;
21use stylex_constants::constants::common::{COMPILED_KEY, VAR_GROUP_HASH_KEY};
22use stylex_constants::constants::messages::{
23 AT_RULE_NOT_FOUND, EXPECTED_CSS_VAR, EXPRESSION_IS_NOT_A_STRING, THEME_VARS_MUST_BE_OBJECT,
24};
25use stylex_types::structures::injectable_style::InjectableStyle;
26
27pub(crate) fn stylex_create_theme(
28 theme_vars: &mut EvaluateResultValue,
29 variables: &EvaluateResultValue,
30 state: &mut StateManager,
31 typed_variables: &mut FlatCompiledStyles,
32) -> (FlatCompiledStyles, InjectableStylesMap) {
33 let theme_name_key_value = validate_theme_variables(theme_vars, state);
34
35 let mut rules_by_at_rule = IndexMap::new();
36
37 let variables_obj = match variables.as_expr().and_then(|expr| expr.as_object()) {
38 Some(obj) => obj,
39 None => stylex_panic!("{}", THEME_VARS_MUST_BE_OBJECT),
40 };
41 let mut variables_key_values = Box::new(get_key_values_from_object(variables_obj));
42
43 variables_key_values.sort_by_key(convert_key_value_to_str);
44
45 #[allow(unused_assignments)]
46 let mut var_group_hash: String = String::new();
47 let mut theme_vars_key_values: Vec<KeyValueProp> = Vec::new();
48
49 match theme_vars {
50 EvaluateResultValue::Expr(expr) => {
51 let theme_obj = match expr.as_object() {
52 Some(obj) => obj,
53 None => stylex_panic!("{}", THEME_VARS_MUST_BE_OBJECT),
54 };
55 theme_vars_key_values = get_key_values_from_object(theme_obj);
56
57 var_group_hash = theme_vars_key_values
58 .iter()
59 .find(|key_value| convert_key_value_to_str(key_value) == VAR_GROUP_HASH_KEY)
60 .map(|key_value| {
61 match convert_expr_to_str(&key_value.value, state, &FunctionMap::default()) {
62 Some(s) => s,
63 None => stylex_panic!("{}", EXPRESSION_IS_NOT_A_STRING),
64 }
65 })
66 .unwrap_or_default();
67 },
68 EvaluateResultValue::ThemeRef(theme_ref) => {
69 var_group_hash = match theme_ref.get(VAR_GROUP_HASH_KEY, state).as_css_var() {
70 Some(v) => v.to_owned(),
71 None => stylex_panic!("{}", EXPECTED_CSS_VAR),
72 };
73 },
74 _ => {
75 stylex_unimplemented!("Unsupported theme vars type {:?}", theme_vars)
76 },
77 }
78
79 for key_value in variables_key_values.into_iter() {
80 let key = convert_key_value_to_str(&key_value);
81
82 let theme_vars_str_value = match theme_vars {
83 EvaluateResultValue::Expr(_) => {
84 let theme_vars_item = match find_and_swap_remove(&mut theme_vars_key_values, |key_value| {
85 convert_key_value_to_str(key_value) == key
86 }) {
87 Some(item) => item,
88 None => stylex_panic!(
89 "The referenced theme variable was not found. Ensure it was declared in defineVars()."
90 ),
91 };
92
93 match convert_expr_to_str(
94 theme_vars_item.value.as_ref(),
95 state,
96 &FunctionMap::default(),
97 ) {
98 Some(s) => s,
99 None => stylex_panic!("{}", EXPRESSION_IS_NOT_A_STRING),
100 }
101 },
102 EvaluateResultValue::ThemeRef(theme_ref) => {
103 match theme_ref.get(key.as_str(), state).as_css_var() {
104 Some(v) => v.clone(),
105 None => stylex_panic!("{}", EXPECTED_CSS_VAR),
106 }
107 },
108 _ => stylex_unimplemented!("Unsupported theme vars type"),
109 };
110
111 let name_hash = theme_vars_str_value[6..theme_vars_str_value.len() - 1].to_string();
112
113 let css_value = get_css_value(key_value);
114
115 let value = FlatCompiledStylesValue::Tuple(name_hash, css_value.0, css_value.1);
116
117 collect_vars_by_at_rules(&key, &value, &mut rules_by_at_rule, &[], typed_variables);
118 }
119
120 let mut sorted_at_rules = rules_by_at_rule.keys().collect::<Vec<&String>>();
123
124 sorted_at_rules.sort_by(|a, b| {
125 if a.as_str() == "default" {
126 Ordering::Less
127 } else if b.as_str() == "default" {
128 Ordering::Greater
129 } else {
130 a.cmp(b)
131 }
132 });
133
134 let at_rules_string_for_hash = sorted_at_rules
135 .iter()
136 .map(|at_rule| {
137 let rule_by_at_rule = match rules_by_at_rule.get(*at_rule) {
138 Some(v) => v.join(""),
139 None => stylex_panic!("{}", AT_RULE_NOT_FOUND),
140 };
141
142 wrap_with_at_rules(rule_by_at_rule.as_str(), at_rule)
143 })
144 .collect::<Vec<String>>()
145 .join("");
146
147 let override_class_name = format!(
149 "{}{}",
150 state.options.class_name_prefix,
151 create_hash(at_rules_string_for_hash.as_str())
152 );
153
154 let mut resolved_theme_vars = IndexMap::new();
155 let mut styles_to_inject = IndexMap::new();
156
157 for at_rule in sorted_at_rules.into_iter() {
158 let decls = match rules_by_at_rule.get(at_rule) {
159 Some(v) => v.join(""),
160 None => stylex_panic!("{}", AT_RULE_NOT_FOUND),
161 };
162 let rule = format!(".{override_class_name}, .{override_class_name}:root{{{decls}}}");
163
164 let priority = round_to_decimal_places(0.4 + priority_for_at_rule(at_rule) / 10.0, 1);
165
166 let (suffix, ltr) = if at_rule == "default" {
167 (String::new(), rule)
168 } else {
169 (
170 format!("-{}", create_hash(at_rule)),
171 wrap_with_at_rules(&rule, at_rule),
172 )
173 };
174
175 styles_to_inject.insert(
176 format!("{}{}", override_class_name, suffix),
177 InjectableStyle::regular(ltr, Some(priority)),
178 );
179 }
180
181 let theme_name_str_value = match theme_vars {
182 EvaluateResultValue::Expr(_) => {
183 match convert_expr_to_str(
184 theme_name_key_value.value.as_ref(),
185 state,
186 &FunctionMap::default(),
187 ) {
188 Some(s) => s,
189 None => stylex_panic!("{}", EXPRESSION_IS_NOT_A_STRING),
190 }
191 },
192 EvaluateResultValue::ThemeRef(theme_ref) => {
193 match theme_ref.get(VAR_GROUP_HASH_KEY, state).as_css_var() {
194 Some(v) => v.to_owned(),
195 None => stylex_panic!("{}", EXPECTED_CSS_VAR),
196 }
197 },
198 _ => stylex_unimplemented!("Unsupported theme vars type"),
199 };
200
201 let theme_class = format!("{override_class_name} {var_group_hash}");
202
203 resolved_theme_vars.insert(
204 theme_name_str_value,
205 Rc::new(FlatCompiledStylesValue::String(theme_class)),
206 );
207
208 resolved_theme_vars.insert(
209 COMPILED_KEY.to_string(),
210 Rc::new(FlatCompiledStylesValue::Bool(true)),
211 );
212
213 (resolved_theme_vars, styles_to_inject)
214}