Skip to main content

stylex_transform/shared/transformers/
stylex_create_theme.rs

1use 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  // Sort @-rules to get a consistent unique hash value
121  // But also put "default" first
122  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  // Create a class name hash
148  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}