diff --git a/.changepacks/changepack_log_cT_D5XrKS3_JJG5rRlqgb.json b/.changepacks/changepack_log_cT_D5XrKS3_JJG5rRlqgb.json
new file mode 100644
index 00000000..a5471fc8
--- /dev/null
+++ b/.changepacks/changepack_log_cT_D5XrKS3_JJG5rRlqgb.json
@@ -0,0 +1,8 @@
+{
+ "changes": {
+ "bindings/devup-ui-wasm/package.json": "Patch",
+ "packages/react/package.json": "Patch"
+ },
+ "note": "Implement styled",
+ "date": "2025-12-02T15:06:28.741744300Z"
+}
diff --git a/Cargo.lock b/Cargo.lock
index c2ee70f6..8af2efc8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -696,9 +696,9 @@ dependencies = [
[[package]]
name = "oxc_allocator"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dd67b29aaa88b44af4d7d7b4752cf1dee9b6bc8b447015aad3470f818c3a24f"
+checksum = "5b360908629f56d1b18f60e0aa5a70122fb61e33543078fafbe565edb759d77f"
dependencies = [
"allocator-api2",
"bumpalo",
@@ -709,9 +709,9 @@ dependencies = [
[[package]]
name = "oxc_ast"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "508b3bd51231d6ea54f210ddf66d7859998e444deee1aa11490d7af76a8fcc85"
+checksum = "89e58ea2b49f8940307bb2d012ca0d2a744f6d98e9f96fc87b0a89c8578d57bf"
dependencies = [
"bitflags",
"oxc_allocator",
@@ -726,9 +726,9 @@ dependencies = [
[[package]]
name = "oxc_ast_macros"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5175058e196ee9bf6f00bf15f0da5e6ad05b2c649f13f8d231def53eaca4ed7"
+checksum = "91c6039e721360dd47a101f7ae1e183f90b3c812cd4ed52e0221d791f70d184a"
dependencies = [
"phf",
"proc-macro2",
@@ -738,9 +738,9 @@ dependencies = [
[[package]]
name = "oxc_ast_visit"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3021dc3864afe1e2ec8da4696ea181549dd23e420c54eec8234b8063f96b9711"
+checksum = "11ecdea97ef3f0e7ee7d9b0d29327040acfe30c8c4593bdf4e0bc8fea0d75899"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -750,9 +750,9 @@ dependencies = [
[[package]]
name = "oxc_codegen"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "427fe5a089efc28ec4f0ff0ed226447abe3e8a993f5e611b568a5c5f8bb24010"
+checksum = "0de87539d889ed530f5811c5c17af31b3346e3b709c1fd6b9e2a5e00c4bac934"
dependencies = [
"bitflags",
"cow-utils",
@@ -771,15 +771,15 @@ dependencies = [
[[package]]
name = "oxc_data_structures"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a6c288cce396a89d45998049658911f832c164f5653cf151c17d5d69c8baa15"
+checksum = "e0e95d9a0caba623cc004b9d420951a7d490a0cd172912ac776df12a51063353"
[[package]]
name = "oxc_diagnostics"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0c0e9fd547155f3118dc1747389a38ffa4daff2218d229730175c44be66584d"
+checksum = "cbf468b479ee17919e8bc11c31c405f059762abb78c90cb0931f5e94d7eac30b"
dependencies = [
"cow-utils",
"oxc-miette",
@@ -788,9 +788,9 @@ dependencies = [
[[package]]
name = "oxc_ecmascript"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb1b0ea5a50805d783611082c381f0b2703ab9eebce080269342720cc923178f"
+checksum = "c5af7a036c4e13de3f0b6bfa7bcf22326c3e1da32210b65ec114c96e17e8d77d"
dependencies = [
"cow-utils",
"num-bigint",
@@ -803,9 +803,9 @@ dependencies = [
[[package]]
name = "oxc_estree"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ea2e2422277b5177b6e96effc77e9842c074a7d4e27582ae66db121b05a63dc"
+checksum = "bb9bac2f3fd66cdb7b538e551d9d72b01ceb9009244c25d370684dce01301114"
[[package]]
name = "oxc_index"
@@ -819,9 +819,9 @@ dependencies = [
[[package]]
name = "oxc_parser"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "653f0530c06a2deff988cecc378c53c2d3e39a50d48bb3c34170feae6f9340d0"
+checksum = "12077275a0b65791602bbd398bc83253328e8ab656a2218a7d6bb571787551f9"
dependencies = [
"bitflags",
"cow-utils",
@@ -842,9 +842,9 @@ dependencies = [
[[package]]
name = "oxc_regular_expression"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5374512dcbcc68ec232add5fb5386827bd1c3f8472ca4905fff7758d6f4c6b94"
+checksum = "b02e8106836d7128a3fac86db05409910c3d96fe46d465cd071877a12802d5b3"
dependencies = [
"bitflags",
"oxc_allocator",
@@ -858,9 +858,9 @@ dependencies = [
[[package]]
name = "oxc_semantic"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa86149b02f40f2597b370720ac2396895401e902b204ef8d78b15f7c4f9120e"
+checksum = "f474c2a181362369f44a0ffe3bc780b286602c920a8ec166f12d7818664b391a"
dependencies = [
"itertools 0.14.0",
"oxc_allocator",
@@ -892,9 +892,9 @@ dependencies = [
[[package]]
name = "oxc_span"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b553fc7cfece123ec1460a063a3bdcc8d39f6f8f358af07f5524227e41d5f63f"
+checksum = "4106c63cc7e72fc8b34943b2b85ce1f5350cdd5c7ad70757d1691ac0ebded943"
dependencies = [
"compact_str",
"oxc-miette",
@@ -905,9 +905,9 @@ dependencies = [
[[package]]
name = "oxc_syntax"
-version = "0.99.0"
+version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "becb84744ca2cb19373ea654cc8532cd8623d6aa06cad9164cfc7646b294b0fd"
+checksum = "25aa1d1b60990a801ec16f7f984ea148bfbcb330590aeabf3cf639537401ebca"
dependencies = [
"bitflags",
"cow-utils",
diff --git a/apps/next/src/app/page.tsx b/apps/next/src/app/page.tsx
index b667af0f..f129aeb0 100644
--- a/apps/next/src/app/page.tsx
+++ b/apps/next/src/app/page.tsx
@@ -1,7 +1,13 @@
'use client'
-import { Box, css, Text } from '@devup-ui/react'
+import { Box, css, styled, Text } from '@devup-ui/react'
import { useState } from 'react'
+const color = 'yellow'
+
+const StyledFooter = styled.footer<{ type: '1' | '2' }>`
+ background-color: ${color};
+ color: ${(props) => (props.type === '1' ? 'red' : 'white')};
+`
export default function HomePage() {
const [color, setColor] = useState('yellow')
@@ -9,6 +15,7 @@ export default function HomePage() {
return (
+
IMPLEMENTATION~
Result<&str, &str> {
+ pub fn to_tag(&self) -> &str {
match self {
ExportVariableKind::Center
| ExportVariableKind::VStack
| ExportVariableKind::Grid
| ExportVariableKind::Flex
- | ExportVariableKind::Box => Ok("div"),
- ExportVariableKind::Text => Ok("span"),
- ExportVariableKind::Image => Ok("img"),
- ExportVariableKind::Button => Ok("button"),
- ExportVariableKind::Input => Ok("input"),
+ | ExportVariableKind::Box => "div",
+ ExportVariableKind::Text => "span",
+ ExportVariableKind::Image => "img",
+ ExportVariableKind::Button => "button",
+ ExportVariableKind::Input => "input",
}
}
}
@@ -151,15 +151,15 @@ mod tests {
#[test]
fn test_to_tag() {
- assert_eq!(ExportVariableKind::Box.to_tag(), Ok("div"));
- assert_eq!(ExportVariableKind::Text.to_tag(), Ok("span"));
- assert_eq!(ExportVariableKind::Image.to_tag(), Ok("img"));
- assert_eq!(ExportVariableKind::Button.to_tag(), Ok("button"));
- assert_eq!(ExportVariableKind::Input.to_tag(), Ok("input"));
- assert_eq!(ExportVariableKind::Flex.to_tag(), Ok("div"));
- assert_eq!(ExportVariableKind::VStack.to_tag(), Ok("div"));
- assert_eq!(ExportVariableKind::Center.to_tag(), Ok("div"));
- assert_eq!(ExportVariableKind::Grid.to_tag(), Ok("div"));
+ assert_eq!(ExportVariableKind::Box.to_tag(), "div");
+ assert_eq!(ExportVariableKind::Text.to_tag(), "span");
+ assert_eq!(ExportVariableKind::Image.to_tag(), "img");
+ assert_eq!(ExportVariableKind::Button.to_tag(), "button");
+ assert_eq!(ExportVariableKind::Input.to_tag(), "input");
+ assert_eq!(ExportVariableKind::Flex.to_tag(), "div");
+ assert_eq!(ExportVariableKind::VStack.to_tag(), "div");
+ assert_eq!(ExportVariableKind::Center.to_tag(), "div");
+ assert_eq!(ExportVariableKind::Grid.to_tag(), "div");
}
#[test]
diff --git a/libs/extractor/src/css_utils.rs b/libs/extractor/src/css_utils.rs
index 1ffe19ae..b1f87f15 100644
--- a/libs/extractor/src/css_utils.rs
+++ b/libs/extractor/src/css_utils.rs
@@ -1,12 +1,242 @@
use std::collections::BTreeMap;
+use crate::utils::{get_string_by_literal_expression, wrap_direct_call};
use css::{
optimize_multi_css_value::{check_multi_css_optimize, optimize_mutli_css_value},
rm_css_comment::rm_css_comment,
style_selector::StyleSelector,
};
+use oxc_allocator::Allocator;
+use oxc_span::SPAN;
-use crate::extract_style::extract_static_style::ExtractStaticStyle;
+use crate::utils::expression_to_code;
+use oxc_ast::ast::TemplateLiteral;
+
+use crate::extract_style::{
+ extract_dynamic_style::ExtractDynamicStyle, extract_static_style::ExtractStaticStyle,
+ extract_style_value::ExtractStyleValue,
+};
+
+#[derive(Debug, PartialEq, Clone, Eq, Hash, Ord, PartialOrd)]
+pub enum CssToStyleResult {
+ Static(ExtractStaticStyle),
+ Dynamic(ExtractDynamicStyle),
+}
+
+impl From for ExtractStyleValue {
+ fn from(value: CssToStyleResult) -> Self {
+ match value {
+ CssToStyleResult::Static(style) => ExtractStyleValue::Static(style),
+ CssToStyleResult::Dynamic(style) => ExtractStyleValue::Dynamic(style),
+ }
+ }
+}
+
+pub fn rm_last_semi_colon(code: &str) -> &str {
+ code.trim_end_matches(';')
+}
+
+pub fn css_to_style_literal<'a>(
+ css: &TemplateLiteral<'a>,
+ level: u8,
+ selector: &Option,
+) -> Vec {
+ let mut styles = vec![];
+
+ // If there are no expressions, just process quasis as static CSS
+ if css.expressions.is_empty() {
+ for quasi in css.quasis.iter() {
+ styles.extend(
+ css_to_style(&quasi.value.raw, level, selector)
+ .into_iter()
+ .map(CssToStyleResult::Static),
+ );
+ }
+ return styles;
+ }
+
+ // Process template literal with expressions
+ // Template literal format: `text ${expr1} text ${expr2} text`
+ // We need to parse CSS and identify where expressions are used
+
+ // Build a combined CSS string with unique placeholders for expressions
+ // Use a format that won't conflict with actual CSS values
+ let mut css_parts = Vec::new();
+ let mut expression_map = std::collections::HashMap::new();
+
+ for (i, quasi) in css.quasis.iter().enumerate() {
+ css_parts.push(quasi.value.raw.to_string());
+
+ // Add expression placeholder if not the last quasi
+ if i < css.expressions.len() {
+ // Use a unique placeholder format that CSS parser won't modify
+ let placeholder = format!("__EXPR_{}__", i);
+ expression_map.insert(placeholder.clone(), i);
+ css_parts.push(placeholder);
+ }
+ }
+
+ let combined_css = css_parts.join("");
+
+ // Parse CSS to extract static styles
+ let static_styles = css_to_style(&combined_css, level, selector);
+
+ // Process each static style and check if it contains expression placeholders
+ for style in static_styles {
+ let value = style.value();
+
+ // Find all placeholders in this value
+ let mut found_placeholders = Vec::new();
+ for (placeholder, &idx) in expression_map.iter() {
+ if value.contains(placeholder) {
+ found_placeholders.push((placeholder.clone(), idx));
+ }
+ }
+
+ if !found_placeholders.is_empty() {
+ // Check if all expressions are literals that can be statically evaluated
+
+ let mut all_literals = true;
+ let mut literal_values = Vec::new();
+
+ let mut iter = found_placeholders.iter();
+ while all_literals && let Some((_, idx)) = iter.next() {
+ if *idx < css.expressions.len()
+ && let Some(literal_value) =
+ get_string_by_literal_expression(&css.expressions[*idx])
+ {
+ literal_values.push((*idx, literal_value));
+ } else {
+ all_literals = false;
+ }
+ }
+
+ if all_literals {
+ // All expressions are literals - replace placeholders with literal values to create static style
+ let mut static_value = value.to_string();
+ for (placeholder, idx) in &found_placeholders {
+ if let Some((_, literal_value)) = literal_values.iter().find(|(i, _)| i == idx)
+ {
+ static_value =
+ static_value.replace(placeholder.as_str(), literal_value.as_str());
+ }
+ }
+ // Create a new static style with the evaluated value
+ styles.push(CssToStyleResult::Static(ExtractStaticStyle::new(
+ style.property(),
+ &static_value,
+ style.level(),
+ style.selector().cloned(),
+ )));
+ } else {
+ // Not all expressions are literals - need to create dynamic style
+ // Check if value is just a placeholder (no surrounding text)
+ if found_placeholders.len() == 1
+ && let (placeholder, idx) = &found_placeholders[0]
+ && value.trim() == placeholder.as_str()
+ && *idx < css.expressions.len()
+ {
+ // Value is just the expression - use expression code directly
+ let expr = &css.expressions[*idx];
+
+ // Check if expression is a function (arrow function or function expression)
+ let is_function = matches!(
+ expr,
+ oxc_ast::ast::Expression::ArrowFunctionExpression(_)
+ | oxc_ast::ast::Expression::FunctionExpression(_)
+ );
+
+ let allocator = Allocator::default();
+ let ast_builder = oxc_ast::AstBuilder::new(&allocator);
+ let identifier = if is_function {
+ expression_to_code(&wrap_direct_call(
+ &ast_builder,
+ expr,
+ &[ast_builder.expression_identifier(SPAN, ast_builder.atom("rest"))],
+ ))
+ } else {
+ expression_to_code(expr)
+ };
+ let identifier = rm_last_semi_colon(&identifier);
+
+ styles.push(CssToStyleResult::Dynamic(ExtractDynamicStyle::new(
+ style.property(),
+ style.level(),
+ identifier,
+ style.selector().cloned(),
+ )));
+ } else {
+ // Value has surrounding text - need to create template literal
+ // Reconstruct the template literal by replacing placeholders with ${expr} syntax
+ // The value contains placeholders like "__EXPR_0__px", we need to convert to `${expr}px`
+
+ let mut template_literal = value.to_string();
+
+ // Sort placeholders by their position in reverse order to avoid index shifting
+ found_placeholders.sort_by(|(a_placeholder, _), (b_placeholder, _)| {
+ template_literal
+ .rfind(a_placeholder)
+ .cmp(&template_literal.rfind(b_placeholder))
+ });
+
+ // Replace each placeholder with the actual expression in template literal format
+ for (placeholder, idx) in &found_placeholders {
+ if *idx < css.expressions.len() {
+ let expr = &css.expressions[*idx];
+
+ // Check if expression is a function (arrow function or function expression)
+ let is_function = matches!(
+ expr,
+ oxc_ast::ast::Expression::ArrowFunctionExpression(_)
+ | oxc_ast::ast::Expression::FunctionExpression(_)
+ );
+
+ let allocator = Allocator::default();
+ let ast_builder = oxc_ast::AstBuilder::new(&allocator);
+ let expr_code = if is_function {
+ expression_to_code(&wrap_direct_call(
+ &ast_builder,
+ expr,
+ &[ast_builder
+ .expression_identifier(SPAN, ast_builder.atom("rest"))],
+ ))
+ } else {
+ expression_to_code(expr)
+ };
+
+ let expr_code = rm_last_semi_colon(&expr_code);
+ // Replace placeholder with ${expr} syntax
+ template_literal = template_literal
+ .replace(placeholder.as_str(), &format!("${{{}}}", expr_code));
+ }
+ }
+
+ // Wrap in template literal backticks
+ let final_identifier = format!("`{}`", template_literal);
+
+ styles.push(CssToStyleResult::Dynamic(ExtractDynamicStyle::new(
+ style.property(),
+ style.level(),
+ &final_identifier,
+ style.selector().cloned(),
+ )));
+ }
+ }
+ } else {
+ // Check if property name contains a dynamic expression placeholder
+ let property = style.property();
+
+ if !expression_map.keys().any(|p| property.contains(p)) {
+ // Static style
+ styles.push(CssToStyleResult::Static(style));
+ }
+
+ // Property name is dynamic - skip for now as it's more complex
+ }
+ }
+
+ styles
+}
pub fn css_to_style(
css: &str,
@@ -22,9 +252,10 @@ pub fn css_to_style(
.flat_map(|s| {
let s = s.trim();
if s.is_empty() {
- return None;
+ None
+ } else {
+ Some(format!("@media{s}"))
}
- Some(format!("@media{s}"))
})
.collect::>();
if media_inputs.len() > 1 {
@@ -37,16 +268,62 @@ pub fn css_to_style(
if input.contains('{') {
while let Some(start) = input.find('{') {
- let rest = &input[start + 1..];
+ // Check if there are properties before the selector
+ let before_brace = &input[..start].trim();
+
+ // Split by semicolon to find the last part which should be the selector
+ let parts: Vec<&str> = before_brace.split(';').map(|s| s.trim()).collect();
- let end = if selector.is_none() {
- rest.rfind('}').unwrap()
+ // Find the selector part (the last part that doesn't contain ':')
+ // or if all parts contain ':', then the last part is the selector
+ let (plain_props, selector_part) = if parts.len() > 1 {
+ // Check if any part doesn't contain ':' (which would be a selector)
+ let mut selector_idx = parts.len();
+ for (i, part) in parts.iter().enumerate().rev() {
+ if !part.contains(':') || part.starts_with('&') || part.starts_with('@') {
+ selector_idx = i;
+ break;
+ }
+ }
+
+ // Math.min
+ let (props, sel) = parts.split_at(parts.len().min(selector_idx));
+ (props.join(";"), sel.join(";"))
} else {
- rest.find('}').unwrap()
+ ("".to_string(), before_brace.to_string())
};
+
+ // Process plain properties if any
+ if !plain_props.is_empty() {
+ styles.extend(css_to_style_block(&plain_props, level, selector));
+ }
+
+ let rest = &input[start + 1..];
+
+ // Find the matching closing brace by counting braces
+ let mut brace_count = 1;
+ let mut end = 0;
+ for (i, ch) in rest.char_indices() {
+ match ch {
+ '{' => brace_count += 1,
+ '}' => {
+ brace_count -= 1;
+ if brace_count == 0 {
+ end = i;
+ break;
+ }
+ }
+ _ => {}
+ }
+ }
+
+ // If we didn't find a matching brace, use the first '}' as fallback
+ if brace_count > 0 {
+ end = rest.find('}').unwrap_or(rest.len());
+ }
let block = &rest[..end];
let sel = &if let Some(StyleSelector::Media { query, .. }) = selector {
- let local_sel = input[..start].trim().to_string();
+ let local_sel = selector_part.trim().to_string();
Some(StyleSelector::Media {
query: query.clone(),
selector: if local_sel == "&" {
@@ -56,13 +333,15 @@ pub fn css_to_style(
},
})
} else {
- let sel = input[..start].trim().to_string();
+ let sel = selector_part.trim().to_string();
if sel.starts_with("@media") {
Some(StyleSelector::Media {
query: sel.replace(" ", "").replace("and(", "and (")["@media".len()..]
.to_string(),
selector: None,
})
+ } else if sel.is_empty() {
+ selector.clone()
} else {
Some(StyleSelector::Selector(sel))
}
@@ -72,10 +351,34 @@ pub fn css_to_style(
} else {
css_to_style_block(block, level, sel)
};
- let input_end = input.rfind('}').unwrap() + 1;
- input = &input[start + end + 2..input_end];
+ // Find the matching closing brace
+ let closing_brace_pos = start + 1 + end;
+
+ // Process the block
styles.extend(block);
+
+ // Update input to continue processing after the closing brace
+ // Check if there's more content after the closing brace
+ if closing_brace_pos + 1 < input.len() {
+ let remaining = &input[closing_brace_pos + 1..].trim();
+ if !remaining.is_empty() {
+ // If there's remaining text after the closing brace, process it
+ // This handles cases like "} color: blue;"
+ if remaining.contains('{') {
+ // If it contains '{', continue the loop
+ input = remaining;
+ } else {
+ // If it doesn't contain '{', process it as a block and break
+ styles.extend(css_to_style_block(remaining, level, selector));
+ break;
+ }
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
}
} else {
styles.extend(css_to_style_block(input, level, selector));
@@ -182,8 +485,362 @@ pub fn optimize_css_block(css: &str) -> String {
mod tests {
use super::*;
+ use oxc_allocator::Allocator;
+ use oxc_ast::ast::{Expression, Statement};
+ use oxc_parser::Parser;
+ use oxc_span::SourceType;
use rstest::rstest;
+ #[rstest]
+ #[case("`background-color: red;`", vec![("background-color", "red", None)])]
+ #[case("`background-color: ${color};`", vec![("background-color", "color", None)])]
+ #[case("`background-color: ${color}`", vec![("background-color", "color", None)])]
+ #[case("`background-color: ${color};color: blue;`", vec![("background-color", "color", None), ("color", "blue", None)])]
+ #[case("`background-color: ${()=>\"arrow dynamic\"}`", vec![("background-color", "(()=>`arrow dynamic`)(rest)", None)])]
+ #[case("`background-color: ${()=>\"arrow dynamic\"};color: blue;`", vec![("background-color", "(()=>`arrow dynamic`)(rest)", None), ("color", "blue", None)])]
+ #[case("`color: blue;background-color: ${()=>\"arrow dynamic\"};`", vec![("color", "blue", None),("background-color", "(()=>`arrow dynamic`)(rest)", None)])]
+ #[case("`background-color: ${function(){ return \"arrow dynamic\"}}`", vec![("background-color", "(function(){return`arrow dynamic`})(rest)", None)])]
+ #[case("`background-color: ${function () { return \"arrow dynamic\"} }`", vec![("background-color", "(function(){return`arrow dynamic`})(rest)", None)])]
+ #[case("`background-color: ${object.color}`", vec![("background-color", "object.color", None)])]
+ #[case("`background-color: ${object['color']}`", vec![("background-color", "object[`color`]", None)])]
+ #[case("`background-color: ${func()}`", vec![("background-color", "func()", None)])]
+ #[case("`background-color: ${(props)=>props.b ? 'a' : 'b'}`", vec![("background-color", "(props=>props.b?`a`:`b`)(rest)", None)])]
+ #[case("`background-color: ${(props)=>props.b ? null : undefined}`", vec![("background-color", "(props=>props.b?null:undefined)(rest)", None)])]
+ #[case(
+ "`color: red; background: blue;`",
+ vec![
+ ("color", "red", None),
+ ("background", "blue", None),
+ ]
+ )]
+ #[case(
+ "`margin:0;padding:0;`",
+ vec![
+ ("margin", "0", None),
+ ("padding", "0", None),
+ ]
+ )]
+ #[case(
+ "`font-size: 16px;`",
+ vec![
+ ("font-size", "16px", None),
+ ]
+ )]
+ #[case(
+ "`border: 1px solid #000; color: #fff;`",
+ vec![
+ ("border", "1px solid #000", None),
+ ("color", "#FFF", None),
+ ]
+ )]
+ #[case(
+ "``",
+ vec![]
+ )]
+ #[case(
+ "`@media (min-width: 768px) {
+ border: 1px solid #000;
+ color: #fff;
+ }`",
+ vec![
+ ("border", "1px solid #000", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: None,
+ })),
+ ("color", "#FFF", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: None,
+ })),
+ ]
+ )]
+ #[case(
+ "`@media (min-width: 768px) and (max-width: 1024px) {
+ border: 1px solid #000;
+ color: #fff;
+ }
+
+ @media (min-width: 768px) {
+ border: 1px solid #000;
+ color: #fff;
+ }`",
+ vec![
+ ("border", "1px solid #000", Some(StyleSelector::Media {
+ query: "(min-width:768px)and (max-width:1024px)".to_string(),
+ selector: None,
+ })),
+ ("color", "#FFF", Some(StyleSelector::Media {
+ query: "(min-width:768px)and (max-width:1024px)".to_string(),
+ selector: None,
+ })),
+ ("border", "1px solid #000", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: None,
+ })),
+ ("color", "#FFF", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: None,
+ })),
+ ]
+ )]
+ #[case(
+ "`@media (min-width: 768px) {
+ & {
+ border: 1px solid #fff;
+ color: #fff;
+ }
+ &:hover, &:active, &:nth-child(2) {
+ border: 1px solid #000;
+ color: #000;
+ }
+ }`",
+ vec![
+ ("border", "1px solid #FFF", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: None,
+ })),
+ ("color", "#FFF", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: None,
+ })),
+ ("border", "1px solid #000", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: Some("&:hover,&:active,&:nth-child(2)".to_string()),
+ })),
+ ("color", "#000", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: Some("&:hover,&:active,&:nth-child(2)".to_string()),
+ })),
+ ]
+ )]
+ #[case(
+ "`@media (min-width: 768px) {
+ & {
+ border: 1px solid #fff;
+ color: #fff;
+ }
+ &:hover {
+ border: 1px solid #000;
+ color: #000;
+ }
+ }`",
+ vec![
+ ("border", "1px solid #FFF", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: None,
+ })),
+ ("color", "#FFF", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: None,
+ })),
+ ("border", "1px solid #000", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: Some("&:hover".to_string()),
+ })),
+ ("color", "#000", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: Some("&:hover".to_string()),
+ })),
+ ]
+ )]
+ #[case(
+ "`@media (min-width: 768px) {
+ & {
+ border: 1px solid #fff;
+ color: #fff;
+ }
+ &:hover {
+ border: 1px solid #000;
+ color: #000;
+ }
+ }
+ @media (max-width: 768px) and (min-width: 480px) {
+ & {
+ border: 1px solid #fff;
+ color: #fff;
+ }
+ &:hover {
+ border: 1px solid #000;
+ color: #000;
+ }
+ }`",
+ vec![
+ ("border", "1px solid #FFF", Some(StyleSelector::Media {
+ query: "(max-width:768px)and (min-width:480px)".to_string(),
+ selector: None,
+ })),
+ ("color", "#FFF", Some(StyleSelector::Media {
+ query: "(max-width:768px)and (min-width:480px)".to_string(),
+ selector: None,
+ })),
+ ("border", "1px solid #000", Some(StyleSelector::Media {
+ query: "(max-width:768px)and (min-width:480px)".to_string(),
+ selector: Some("&:hover".to_string()),
+ })),
+ ("color", "#000", Some(StyleSelector::Media {
+ query: "(max-width:768px)and (min-width:480px)".to_string(),
+ selector: Some("&:hover".to_string()),
+ })),
+ ("border", "1px solid #FFF", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: None,
+ })),
+ ("color", "#FFF", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: None,
+ })),
+ ("border", "1px solid #000", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: Some("&:hover".to_string()),
+ })),
+ ("color", "#000", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: Some("&:hover".to_string()),
+ })),
+ ]
+ )]
+ #[case(
+ "`@media (min-width: 768px) {
+ & {
+ border: 1px solid #fff;
+ color: #fff;
+ }
+ }
+ @media (max-width: 768px) and (min-width: 480px) {
+ border: 1px solid #000;
+ color: #000;
+ }`",
+ vec![
+ ("border", "1px solid #FFF", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: None,
+ })),
+ ("color", "#FFF", Some(StyleSelector::Media {
+ query: "(min-width:768px)".to_string(),
+ selector: None,
+ })),
+ ("border", "1px solid #000", Some(StyleSelector::Media {
+ query: "(max-width:768px)and (min-width:480px)".to_string(),
+ selector: None,
+ })),
+ ("color", "#000", Some(StyleSelector::Media {
+ query: "(max-width:768px)and (min-width:480px)".to_string(),
+ selector: None,
+ })),
+ ]
+ )]
+ #[case(
+ "`@media (min-width: 768px) {
+ & {
+ }
+ }
+ @media (max-width: 768px) and (min-width: 480px) {
+ }`",
+ vec![]
+ )]
+ #[case(
+ "`ul { font-family: 'Roboto Hello', sans-serif; }`",
+ vec![
+ ("font-family", "\"Roboto Hello\",sans-serif", Some(StyleSelector::Selector("ul".to_string()))),
+ ]
+ )]
+ #[case(
+ "`&:hover { background-color: red; }`",
+ vec![
+ ("background-color", "red", Some(StyleSelector::Selector("&:hover".to_string()))),
+ ]
+ )]
+ #[case(
+ "`background-color: red; &:hover { background-color: red; }`",
+ vec![
+ ("background-color", "red", None),
+ ("background-color", "red", Some(StyleSelector::Selector("&:hover".to_string()))),
+ ]
+ )]
+ #[case(
+ "`background-color: red; &:hover { background-color: red; } color: blue;`",
+ vec![
+ ("background-color", "red", None),
+ ("background-color", "red", Some(StyleSelector::Selector("&:hover".to_string()))),
+ ("color", "blue", None),
+ ]
+ )]
+ #[case(
+ "`background-color: red; &:hover { background-color: red; } color: blue; &:active { background-color: blue; }`",
+ vec![
+ ("background-color", "red", None),
+ ("background-color", "red", Some(StyleSelector::Selector("&:hover".to_string()))),
+ ("color", "blue", None),
+ ("background-color", "blue", Some(StyleSelector::Selector("&:active".to_string()))),
+ ]
+ )]
+ #[case(
+ "`background-color: red; &:hover { background-color: red; } color: blue; &:active { background-color: blue; } transform: rotate(90deg);`",
+ vec![
+ ("background-color", "red", None),
+ ("background-color", "red", Some(StyleSelector::Selector("&:hover".to_string()))),
+ ("color", "blue", None),
+ ("background-color", "blue", Some(StyleSelector::Selector("&:active".to_string()))),
+ ("transform", "rotate(90deg)", None),
+ ]
+ )]
+ #[case("`width: ${1}px;`", vec![("width", "1px", None)])]
+ #[case("`width: ${\"1\"}px;`", vec![("width", "1px", None)])]
+ #[case("`width: ${'1'}px;`", vec![("width", "1px", None)])]
+ #[case("`width: ${`1`}px;`", vec![("width", "1px", None)])]
+ #[case("`width: ${\"1px\"};`", vec![("width", "1px", None)])]
+ #[case("`width: ${'1px'};`", vec![("width", "1px", None)])]
+ #[case("`width: ${`1px`};`", vec![("width", "1px", None)])]
+ #[case("`width: ${1 + 1}px;`", vec![("width", "`${1+1}px`", None)])]
+ #[case("`width: ${func(1)}px;`", vec![("width", "`${func(1)}px`", None)])]
+ #[case("`width: ${func(1)}${2}px;`", vec![("width", "`${func(1)}${2}px`", None)])]
+ #[case("`width: ${1}${2}px;`", vec![("width", "12px", None)])]
+ #[case("`width: ${func(\n\t 1 , \n\t2\n)}px;`", vec![("width", "`${func(1,2)}px`", None)])]
+ #[case("`width: ${func(\" wow \")}px;`", vec![("width", "`${func(` wow `)}px`", None)])]
+ #[case("`width: ${func(\"hello\\nworld\")}px;`", vec![("width", "`${func(`hello\nworld`)}px`", None)])]
+ #[case("`width: ${func('test\\'quote')}px;`", vec![("width", "`${func(`test'quote`)}px`", None)])]
+ #[case("`width: ${(props)=>props.b ? \"hello\\\"world\" : \"test\"}px;`", vec![("width", "`${(props=>props.b?`hello\"world`:`test`)(rest)}px`", None)])]
+ #[case("`width: ${(props)=>props.b ? \"hello\\\"world\\\"more\" : \"test\"}px;`", vec![("width", "`${(props=>props.b?`hello\"world\"more`:`test`)(rest)}px`", None)])]
+ #[case("`width: ${(props)=>props.b ? \"hello\" + \"world\" : \"test\"}px;`", vec![("width", "`${(props=>props.b?`hello`+`world`:`test`)(rest)}px`", None)])]
+ // wrong cases
+ #[case(
+ "`@media (min-width: 768px) {
+ & {
+ `",
+ vec![]
+ )]
+ fn test_css_to_style_literal(
+ #[case] input: &str,
+ #[case] expected: Vec<(&str, &str, Option)>,
+ ) {
+ // parse template literal code
+ let allocator = Allocator::default();
+ let css = Parser::new(&allocator, input, SourceType::ts()).parse();
+ if let Statement::ExpressionStatement(expr) = &css.program.body[0]
+ && let Expression::TemplateLiteral(tmp) = &expr.expression
+ {
+ let styles = css_to_style_literal(tmp, 0, &None);
+ let mut result: Vec<(&str, &str, Option)> = styles
+ .iter()
+ .map(|prop| match prop {
+ CssToStyleResult::Static(style) => {
+ (style.property(), style.value(), style.selector().cloned())
+ }
+ CssToStyleResult::Dynamic(dynamic) => (
+ dynamic.property(),
+ dynamic.identifier(),
+ dynamic.selector().cloned(),
+ ),
+ })
+ .collect();
+ result.sort();
+ let mut expected_sorted = expected.clone();
+ expected_sorted.sort();
+ assert_eq!(result, expected_sorted);
+ } else {
+ panic!("not a template literal");
+ }
+ }
+
#[rstest]
#[case(
"div{
@@ -482,6 +1139,13 @@ mod tests {
("font-family", "\"Roboto Hello\",sans-serif", Some(StyleSelector::Selector("ul".to_string()))),
]
)]
+ #[case(
+ "div { color: red; ; { background: blue; } }",
+ vec![
+ ("color", "red", Some(StyleSelector::Selector("div".to_string()))),
+ ("background", "blue", Some(StyleSelector::Selector("div".to_string()))),
+ ]
+ )]
fn test_css_to_style(
#[case] input: &str,
#[case] expected: Vec<(&str, &str, Option)>,
diff --git a/libs/extractor/src/extractor/extract_global_style_from_expression.rs b/libs/extractor/src/extractor/extract_global_style_from_expression.rs
index 70d8498e..6c577aa7 100644
--- a/libs/extractor/src/extractor/extract_global_style_from_expression.rs
+++ b/libs/extractor/src/extractor/extract_global_style_from_expression.rs
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use crate::{
ExtractStyleProp,
- css_utils::css_to_style,
+ css_utils::{CssToStyleResult, css_to_style_literal},
extract_style::{
extract_font_face::ExtractFontFace, extract_import::ExtractImport,
extract_style_value::ExtractStyleValue,
@@ -116,18 +116,16 @@ pub fn extract_global_style_from_expression<'a>(
file: file.to_string(),
})));
} else if let ArrayExpressionElement::TemplateLiteral(t) = p {
- let css_styles = css_to_style(
- t.quasis
- .iter()
- .map(|q| q.value.raw.as_str())
- .collect::()
- .trim(),
- 0,
- &None,
- )
- .into_iter()
- .map(ExtractStyleValue::Static)
- .collect::>();
+ let css_styles = css_to_style_literal(t, 0, &None)
+ .into_iter()
+ .filter_map(|ex| {
+ if let CssToStyleResult::Static(st) = ex {
+ Some(ExtractStyleValue::Static(st))
+ } else {
+ None
+ }
+ })
+ .collect::>();
styles.push(ExtractStyleProp::Static(
ExtractStyleValue::FontFace(ExtractFontFace {
properties: BTreeMap::from_iter(
diff --git a/libs/extractor/src/extractor/extract_style_from_expression.rs b/libs/extractor/src/extractor/extract_style_from_expression.rs
index c78973d8..3c878909 100644
--- a/libs/extractor/src/extractor/extract_style_from_expression.rs
+++ b/libs/extractor/src/extractor/extract_style_from_expression.rs
@@ -1,6 +1,6 @@
use crate::{
ExtractStyleProp,
- css_utils::css_to_style,
+ css_utils::{css_to_style, css_to_style_literal},
extract_style::{
extract_dynamic_style::ExtractDynamicStyle, extract_static_style::ExtractStaticStyle,
extract_style_value::ExtractStyleValue,
@@ -145,17 +145,10 @@ pub fn extract_style_from_expression<'a>(
&None,
),
Expression::TemplateLiteral(tmp) => ExtractResult {
- styles: css_to_style(
- &tmp.quasis
- .iter()
- .map(|q| q.value.raw.as_str())
- .collect::(),
- level,
- selector,
- )
- .into_iter()
- .map(|ex| ExtractStyleProp::Static(ExtractStyleValue::Static(ex)))
- .collect(),
+ styles: css_to_style_literal(tmp, level, selector)
+ .into_iter()
+ .map(|ex| ExtractStyleProp::Static(ex.into()))
+ .collect(),
..ExtractResult::default()
},
_ => ExtractResult::default(),
diff --git a/libs/extractor/src/extractor/extract_style_from_styled.rs b/libs/extractor/src/extractor/extract_style_from_styled.rs
new file mode 100644
index 00000000..7872abfa
--- /dev/null
+++ b/libs/extractor/src/extractor/extract_style_from_styled.rs
@@ -0,0 +1,349 @@
+use std::collections::HashMap;
+
+use crate::{
+ ExtractStyleProp,
+ component::ExportVariableKind,
+ css_utils::css_to_style_literal,
+ extract_style::extract_style_value::ExtractStyleValue,
+ extractor::{ExtractResult, extract_style_from_expression::extract_style_from_expression},
+ gen_class_name::gen_class_names,
+ gen_style::gen_styles,
+ utils::{merge_object_expressions, wrap_array_filter},
+};
+use oxc_allocator::CloneIn;
+use oxc_ast::{
+ AstBuilder,
+ ast::{Argument, Expression, FormalParameterKind},
+};
+use oxc_span::SPAN;
+
+fn extract_base_tag_and_class_name<'a>(
+ input: &Expression<'a>,
+ imports: &HashMap,
+) -> (Option, Option>) {
+ if let Expression::StaticMemberExpression(member) = input {
+ (Some(member.property.name.to_string()), None)
+ } else if let Expression::CallExpression(call) = input
+ && call.arguments.len() == 1
+ {
+ // styled("div") or styled(Component)
+ if let Argument::StringLiteral(lit) = &call.arguments[0] {
+ (Some(lit.value.to_string()), None)
+ } else if let Argument::Identifier(ident) = &call.arguments[0] {
+ if let Some(export_variable_kind) = imports.get(ident.name.as_str()) {
+ (
+ Some(export_variable_kind.to_tag().to_string()),
+ Some(export_variable_kind.extract()),
+ )
+ } else {
+ (Some(ident.name.to_string()), None)
+ }
+ } else {
+ // Component reference - we'll handle this later
+ (None, None)
+ }
+ } else {
+ (None, None)
+ }
+}
+
+/// Extract styles from styled function calls
+/// Handles patterns like:
+/// - styled.div`css`
+/// - styled("div")`css`
+/// - styled("div")({ bg: "red" })
+/// - styled.div({ bg: "red" })
+/// - styled(Component)({ bg: "red" })
+pub fn extract_style_from_styled<'a>(
+ ast_builder: &AstBuilder<'a>,
+ expression: &mut Expression<'a>,
+ split_filename: Option<&str>,
+ imports: &HashMap,
+) -> (ExtractResult<'a>, Expression<'a>) {
+ let (result, new_expr) = if let Expression::TaggedTemplateExpression(tag) = expression
+ && let (Some(tag_name), default_class_name) =
+ extract_base_tag_and_class_name(&tag.tag, imports)
+ {
+ // Case 1: styled.div`css` or styled("div")`css`
+ // Check if tag is styled.div or styled(...)
+ // Extract CSS from template literal
+
+ let styles = css_to_style_literal(&tag.quasi, 0, &None);
+ let mut props_styles: Vec> = styles
+ .iter()
+ .map(|ex| ExtractStyleProp::Static(ex.clone().into()))
+ .collect();
+
+ if let Some(default_class_name) = default_class_name {
+ props_styles.extend(default_class_name.into_iter().map(ExtractStyleProp::Static));
+ }
+
+ let class_name = gen_class_names(ast_builder, &mut props_styles, None, split_filename);
+ let styled_component = create_styled_component(
+ ast_builder,
+ &tag_name,
+ &class_name,
+ &gen_styles(ast_builder, &props_styles, None),
+ );
+
+ let result = ExtractResult {
+ styles: props_styles,
+ tag: Some(ast_builder.expression_string_literal(
+ SPAN,
+ ast_builder.atom(&tag_name),
+ None,
+ )),
+ style_order: None,
+ style_vars: None,
+ props: None,
+ };
+
+ (Some(result), Some(styled_component))
+ } else if let Expression::CallExpression(call) = expression
+ && let (Some(tag_name), default_class_name) =
+ extract_base_tag_and_class_name(&call.callee, imports)
+ && call.arguments.len() == 1
+ {
+ // Case 2: styled.div({ bg: "red" }) or styled("div")({ bg: "red" })
+ // Check if this is a call to styled.div or styled("div")
+
+ // Extract styles from object expression
+ let ExtractResult {
+ mut styles,
+ style_order,
+ style_vars,
+ props,
+ ..
+ } = extract_style_from_expression(
+ ast_builder,
+ None,
+ if let Argument::SpreadElement(spread) = &mut call.arguments[0] {
+ &mut spread.argument
+ } else {
+ call.arguments[0].to_expression_mut()
+ },
+ 0,
+ &None,
+ );
+ if let Some(default_class_name) = default_class_name {
+ styles.extend(default_class_name.into_iter().map(ExtractStyleProp::Static));
+ }
+
+ let class_name = gen_class_names(ast_builder, &mut styles, style_order, split_filename);
+ let styled_component = create_styled_component(
+ ast_builder,
+ &tag_name,
+ &class_name,
+ &gen_styles(ast_builder, &styles, None),
+ );
+
+ let result = ExtractResult {
+ styles,
+ tag: None,
+ style_order,
+ style_vars,
+ props,
+ };
+
+ (Some(result), Some(styled_component))
+ } else {
+ (None, None)
+ };
+ (
+ result.unwrap_or(ExtractResult::default()),
+ new_expr.unwrap_or(expression.clone_in(ast_builder.allocator)),
+ )
+}
+
+fn create_styled_component<'a>(
+ ast_builder: &AstBuilder<'a>,
+ tag_name: &str,
+ class_name: &Option>,
+ style_vars: &Option>,
+) -> Expression<'a> {
+ let params = ast_builder.formal_parameters(
+ SPAN,
+ FormalParameterKind::ArrowFormalParameters,
+ oxc_allocator::Vec::from_iter_in(
+ vec![ast_builder.formal_parameter(
+ SPAN,
+ oxc_allocator::Vec::from_iter_in(vec![], ast_builder.allocator),
+ ast_builder.binding_pattern(
+ ast_builder.binding_pattern_kind_object_pattern(
+ SPAN,
+ oxc_allocator::Vec::from_iter_in(
+ vec![
+ ast_builder.binding_property(
+ SPAN,
+ ast_builder.property_key_static_identifier(SPAN, "style"),
+ ast_builder.binding_pattern(
+ ast_builder.binding_pattern_kind_binding_identifier(
+ SPAN, "style",
+ ),
+ None::<
+ oxc_allocator::Box<
+ oxc_ast::ast::TSTypeAnnotation<'a>,
+ >,
+ >,
+ false,
+ ),
+ true,
+ false,
+ ),
+ ast_builder.binding_property(
+ SPAN,
+ ast_builder
+ .property_key_static_identifier(SPAN, "className"),
+ ast_builder.binding_pattern(
+ ast_builder.binding_pattern_kind_binding_identifier(
+ SPAN,
+ "className",
+ ),
+ None::<
+ oxc_allocator::Box<
+ oxc_ast::ast::TSTypeAnnotation<'a>,
+ >,
+ >,
+ false,
+ ),
+ true,
+ false,
+ ),
+ ],
+ ast_builder.allocator,
+ ),
+ Some(ast_builder.binding_rest_element(
+ SPAN,
+ ast_builder.binding_pattern(
+ ast_builder.binding_pattern_kind_binding_identifier(
+ SPAN,
+ ast_builder.atom("rest"),
+ ),
+ None::>>,
+ false,
+ ),
+ )),
+ ),
+ None::>>,
+ false,
+ ),
+ None,
+ false,
+ false,
+ )],
+ ast_builder.allocator,
+ ),
+ None::>>,
+ );
+ let body = ast_builder.alloc_function_body(
+ SPAN,
+ oxc_allocator::Vec::from_iter_in(vec![], ast_builder.allocator),
+ oxc_allocator::Vec::from_iter_in(
+ vec![ast_builder.statement_expression(
+ SPAN,
+ ast_builder.expression_jsx_element(
+ SPAN,
+ ast_builder.alloc_jsx_opening_element(
+ SPAN,
+ ast_builder.jsx_element_name_identifier(SPAN, ast_builder.atom(tag_name)),
+ None::>>,
+ oxc_allocator::Vec::from_iter_in(
+ vec![
+ ast_builder.jsx_attribute_item_spread_attribute(
+ SPAN,
+ ast_builder
+ .expression_identifier(SPAN, ast_builder.atom("rest")),
+ ),
+ ast_builder.jsx_attribute_item_attribute(
+ SPAN,
+ ast_builder.jsx_attribute_name_identifier(
+ SPAN,
+ ast_builder.atom("className"),
+ ),
+ Some(
+ ast_builder.jsx_attribute_value_expression_container(
+ SPAN,
+ class_name
+ .as_ref()
+ .map(|name| {
+ wrap_array_filter(
+ ast_builder,
+ &[
+ name.clone_in(
+ ast_builder.allocator,
+ ),
+ ast_builder.expression_identifier(
+ SPAN,
+ ast_builder.atom("className"),
+ ),
+ ],
+ )
+ .unwrap()
+ })
+ .unwrap_or_else(|| {
+ ast_builder.expression_identifier(
+ SPAN,
+ ast_builder.atom("className"),
+ )
+ })
+ .into(),
+ ),
+ ),
+ ),
+ ast_builder.jsx_attribute_item_attribute(
+ SPAN,
+ ast_builder.jsx_attribute_name_identifier(
+ SPAN,
+ ast_builder.atom("style"),
+ ),
+ Some(
+ ast_builder.jsx_attribute_value_expression_container(
+ SPAN,
+ style_vars
+ .as_ref()
+ .map(|style_vars| {
+ merge_object_expressions(
+ ast_builder,
+ &[
+ style_vars.clone_in(
+ ast_builder.allocator,
+ ),
+ ast_builder.expression_identifier(
+ SPAN,
+ ast_builder.atom("style"),
+ ),
+ ],
+ )
+ .unwrap()
+ })
+ .unwrap_or_else(|| {
+ ast_builder.expression_identifier(
+ SPAN,
+ ast_builder.atom("style"),
+ )
+ })
+ .into(),
+ ),
+ ),
+ ),
+ ],
+ ast_builder.allocator,
+ ),
+ ),
+ oxc_allocator::Vec::from_iter_in(vec![], ast_builder.allocator),
+ None::>>,
+ ),
+ )],
+ ast_builder.allocator,
+ ),
+ );
+ ast_builder.expression_arrow_function(
+ SPAN,
+ true,
+ false,
+ None::>>,
+ params,
+ None::>>,
+ body,
+ )
+}
diff --git a/libs/extractor/src/extractor/mod.rs b/libs/extractor/src/extractor/mod.rs
index e467d099..e97bb3ab 100644
--- a/libs/extractor/src/extractor/mod.rs
+++ b/libs/extractor/src/extractor/mod.rs
@@ -7,6 +7,7 @@ pub(super) mod extract_keyframes_from_expression;
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;
/**
* type
diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs
index 4d8a11ee..b201ae69 100644
--- a/libs/extractor/src/lib.rs
+++ b/libs/extractor/src/lib.rs
@@ -7563,4 +7563,645 @@ keyframes({
.unwrap()
));
}
+
+ #[test]
+ #[serial]
+ fn test_styled() {
+ // Test 1: styled.div`css`
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled.section`
+ background: red;
+ color: blue;
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ // Test 2: styled("div")`css`
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled("article")`
+ background: red;
+ color: blue;
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ // Test 3: styled("div")({ bg: "red" })
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled("footer")({ bg: "red" })
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ // Test 4: styled.div({ bg: "red" })
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled.aside({ bg: "red" })
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ // Test 5: styled(Component)({ bg: "red" })
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled, Text} from '@devup-ui/core'
+ const StyledComponent = styled(Text)({ bg: "red" })
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled, Text} from '@devup-ui/core'
+ const StyledComponent = styled(Text)`
+ background: red;
+ color: blue;
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled, VStack} from '@devup-ui/core'
+ const StyledComponent = styled(VStack)({ bg: "red" })
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled, VStack} from '@devup-ui/core'
+ const StyledComponent = styled(VStack)`
+ background: red;
+ color: blue;
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledComponent = styled(CustomComponent)({ bg: "red" })
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledComponent = styled(CustomComponent)`
+ background: red;
+ color: blue;
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled.aside<{ test: string }>({ bg: "red" })
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+ }
+
+ #[test]
+ #[serial]
+ fn test_styled_with_variable() {
+ // Test 1: styled.div({ bg: "$text" })
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled.div({ bg: "$text", color: "$primary" })
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ // Test 2: styled("div")({ color: "$primary" })
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled("div")({ bg: "$text", fontSize: 16 })
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ // Test 3: styled.div`css`
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled.div`
+ background: var(--text);
+ color: var(--primary);
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ // Test 4: styled(Component)({ bg: "$text" })
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled, Box} from '@devup-ui/core'
+ const StyledComponent = styled(Box)({ bg: "$text", _hover: { bg: "$primary" } })
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ // Test 5: styled("div")`css`
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled("div")`
+ background-color: var(--text);
+ padding: 16px;
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+ }
+
+ #[test]
+ #[serial]
+ fn test_styled_with_variable_like_emotion() {
+ // Test 1: styled.div`css with ${variable}`
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const color = 'red';
+ const StyledDiv = styled.div`
+ color: ${color};
+ background: blue;
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ // Test 2: styled("div")`css with ${variable}`
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const primaryColor = 'blue';
+ const padding = '16px';
+ const StyledDiv = styled("div")`
+ color: ${primaryColor};
+ padding: ${padding};
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const primaryColor = 'blue';
+ const padding = '16px';
+ const StyledDiv = styled("div")({ bg: primaryColor, padding })
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const primaryColor = 'blue';
+ const padding = '16px';
+ const StyledDiv = styled.div({ bg: primaryColor, padding })
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled("div")`
+ color: ${obj.color};
+ padding: ${func()};
+ background: ${obj.func()};
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled("div")({ bg: obj.bg, padding: func(), color: obj.color() })
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled.div({ bg: obj.bg, padding: func(), color: obj.color() })
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+ }
+
+ #[test]
+ #[serial]
+ fn test_styled_with_variable_like_emotion_props() {
+ // Test 3: styled.div`css with ${props => props.bg}`
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled.div`
+ background: ${props => props.bg};
+ color: red;
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ // Test 4: styled(Component)`css with ${variable}`
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled, Box} from '@devup-ui/core'
+ const fontSize = '18px';
+ const StyledComponent = styled(Box)`
+ font-size: ${fontSize};
+ color: ${props => props.color || 'black'};
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ // Test 5: styled.div`css with multiple ${variables}`
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const margin = '10px';
+ const padding = '20px';
+ const StyledDiv = styled.div`
+ margin: ${margin};
+ padding: ${padding};
+ background: ${props => props.bg || 'white'};
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ // Test 6: styled.div`css with ${expression}`
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const isActive = true;
+ const StyledDiv = styled.div`
+ color: ${isActive ? 'red' : 'blue'};
+ opacity: ${isActive ? 1 : 0.5};
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+ }
+
+ #[test]
+ #[serial]
+ fn test_wrong_styled_with_variable_like_emotion_props() {
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled(null)`
+ background: ${props => props.bg};
+ color: red;
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled("div", "span")`
+ background: ${props => props.bg};
+ color: red;
+ `
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled("div", "span").filter(Boolean)
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled("div")({ bg: "red" }, {})
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+
+ reset_class_map();
+ assert_debug_snapshot!(ToBTreeSet::from(
+ extract(
+ "test.tsx",
+ r#"import {styled} from '@devup-ui/core'
+ const StyledDiv = styled("div")({ bg: "red" }, {})``
+ "#,
+ ExtractOption {
+ package: "@devup-ui/core".to_string(),
+ css_dir: "@devup-ui/core".to_string(),
+ single_css: false,
+ import_main_css: false
+ }
+ )
+ .unwrap()
+ ));
+ }
}
diff --git a/libs/extractor/src/prop_modify_utils.rs b/libs/extractor/src/prop_modify_utils.rs
index 90b0c977..0431174a 100644
--- a/libs/extractor/src/prop_modify_utils.rs
+++ b/libs/extractor/src/prop_modify_utils.rs
@@ -1,7 +1,7 @@
use crate::ExtractStyleProp;
use crate::gen_class_name::gen_class_names;
use crate::gen_style::gen_styles;
-use crate::utils::get_string_by_property_key;
+use crate::utils::{get_string_by_property_key, merge_object_expressions};
use oxc_allocator::CloneIn;
use oxc_ast::AstBuilder;
use oxc_ast::ast::JSXAttributeName::Identifier;
@@ -375,29 +375,6 @@ fn merge_string_expressions<'a>(
)
}
-/// merge expressions to object expression
-fn merge_object_expressions<'a>(
- ast_builder: &AstBuilder<'a>,
- expressions: &[Expression<'a>],
-) -> Option> {
- if expressions.is_empty() {
- return None;
- }
- if expressions.len() == 1 {
- return Some(expressions[0].clone_in(ast_builder.allocator));
- }
- Some(ast_builder.expression_object(
- SPAN,
- oxc_allocator::Vec::from_iter_in(
- expressions.iter().map(|ex| {
- ast_builder
- .object_property_kind_spread_property(SPAN, ex.clone_in(ast_builder.allocator))
- }),
- ast_builder.allocator,
- ),
- ))
-}
-
pub fn convert_class_name<'a>(
ast_builder: &AstBuilder<'a>,
class_name: &Expression<'a>,
diff --git a/libs/extractor/src/snapshots/extractor__tests__negative_props-3.snap b/libs/extractor/src/snapshots/extractor__tests__negative_props-3.snap
index 62cf106a..eaa1e954 100644
--- a/libs/extractor/src/snapshots/extractor__tests__negative_props-3.snap
+++ b/libs/extractor/src/snapshots/extractor__tests__negative_props-3.snap
@@ -1,6 +1,6 @@
---
source: libs/extractor/src/lib.rs
-expression: "ToBTreeSet::from(extract(\"test.jsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())"
+expression: "ToBTreeSet::from(extract(\"test.jsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())"
---
ToBTreeSet {
styles: {
@@ -8,11 +8,11 @@ ToBTreeSet {
ExtractDynamicStyle {
property: "z-index",
level: 0,
- identifier: "-(1 + a)",
+ identifier: "-(1+a)",
selector: None,
style_order: None,
},
),
},
- code: "import \"@devup-ui/core/devup-ui.css\";\n;\n",
+ code: "import \"@devup-ui/core/devup-ui.css\";\n;\n",
}
diff --git a/libs/extractor/src/snapshots/extractor__tests__negative_props-4.snap b/libs/extractor/src/snapshots/extractor__tests__negative_props-4.snap
index 0a00ea49..6b2e3055 100644
--- a/libs/extractor/src/snapshots/extractor__tests__negative_props-4.snap
+++ b/libs/extractor/src/snapshots/extractor__tests__negative_props-4.snap
@@ -1,6 +1,6 @@
---
source: libs/extractor/src/lib.rs
-expression: "ToBTreeSet::from(extract(\"test.jsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())"
+expression: "ToBTreeSet::from(extract(\"test.jsx\",\nr#\"import {Box} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())"
---
ToBTreeSet {
styles: {
@@ -8,11 +8,11 @@ ToBTreeSet {
ExtractDynamicStyle {
property: "z-index",
level: 0,
- identifier: "-1 * a",
+ identifier: "-1*a",
selector: None,
style_order: None,
},
),
},
- code: "import \"@devup-ui/core/devup-ui.css\";\n;\n",
+ code: "import \"@devup-ui/core/devup-ui.css\";\n;\n",
}
diff --git a/libs/extractor/src/snapshots/extractor__tests__props_direct_object_select-3.snap b/libs/extractor/src/snapshots/extractor__tests__props_direct_object_select-3.snap
index 497d6548..d0fd2566 100644
--- a/libs/extractor/src/snapshots/extractor__tests__props_direct_object_select-3.snap
+++ b/libs/extractor/src/snapshots/extractor__tests__props_direct_object_select-3.snap
@@ -1,6 +1,6 @@
---
source: libs/extractor/src/lib.rs
-expression: "ToBTreeSet::from(extract(\"test.jsx\",\nr#\"import {Flex} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())"
+expression: "ToBTreeSet::from(extract(\"test.jsx\",\nr#\"import {Flex} from '@devup-ui/core'\n \n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())"
---
ToBTreeSet {
styles: {
@@ -37,11 +37,11 @@ ToBTreeSet {
ExtractDynamicStyle {
property: "opacity",
level: 0,
- identifier: "any[\"some\"]",
+ identifier: "any[`some`]",
selector: None,
style_order: None,
},
),
},
- code: "import \"@devup-ui/core/devup-ui.css\";\n;\n",
+ code: "import \"@devup-ui/core/devup-ui.css\";\n;\n",
}
diff --git a/libs/extractor/src/snapshots/extractor__tests__remove_semicolon-5.snap b/libs/extractor/src/snapshots/extractor__tests__remove_semicolon-5.snap
index 1de9248e..14a5abd7 100644
--- a/libs/extractor/src/snapshots/extractor__tests__remove_semicolon-5.snap
+++ b/libs/extractor/src/snapshots/extractor__tests__remove_semicolon-5.snap
@@ -1,6 +1,6 @@
---
source: libs/extractor/src/lib.rs
-expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())"
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { Box } from \"@devup-ui/core\";\n\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: true, import_main_css: false\n}).unwrap())"
---
ToBTreeSet {
styles: {
@@ -8,11 +8,11 @@ ToBTreeSet {
ExtractDynamicStyle {
property: "background",
level: 0,
- identifier: "`${color}` + \"\"",
+ identifier: "`${color}`+``",
selector: None,
style_order: None,
},
),
},
- code: "import \"@devup-ui/core/devup-ui.css\";\n;\n",
+ code: "import \"@devup-ui/core/devup-ui.css\";\n;\n",
}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled-10.snap b/libs/extractor/src/snapshots/extractor__tests__styled-10.snap
new file mode 100644
index 00000000..d017d60e
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled-10.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledComponent = styled(CustomComponent)`\n background: red;\n color: blue;\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "red",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "color",
+ value: "blue",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledComponent = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled-11.snap b/libs/extractor/src/snapshots/extractor__tests__styled-11.snap
new file mode 100644
index 00000000..8b2f7728
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled-11.snap
@@ -0,0 +1,18 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled.aside<{ test: string }>({ bg: \"red\" })\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "red",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled-2.snap b/libs/extractor/src/snapshots/extractor__tests__styled-2.snap
new file mode 100644
index 00000000..26242318
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled-2.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled(\"article\")`\n background: red;\n color: blue;\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "red",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "color",
+ value: "blue",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled-3.snap b/libs/extractor/src/snapshots/extractor__tests__styled-3.snap
new file mode 100644
index 00000000..fb62f318
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled-3.snap
@@ -0,0 +1,18 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled(\"footer\")({ bg: \"red\" })\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "red",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled-4.snap b/libs/extractor/src/snapshots/extractor__tests__styled-4.snap
new file mode 100644
index 00000000..964e76c8
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled-4.snap
@@ -0,0 +1,18 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled.aside({ bg: \"red\" })\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "red",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled-5.snap b/libs/extractor/src/snapshots/extractor__tests__styled-5.snap
new file mode 100644
index 00000000..9cfe2825
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled-5.snap
@@ -0,0 +1,18 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled, Text} from '@devup-ui/core'\n const StyledComponent = styled(Text)({ bg: \"red\" })\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "red",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledComponent = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled-6.snap b/libs/extractor/src/snapshots/extractor__tests__styled-6.snap
new file mode 100644
index 00000000..ae0060e7
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled-6.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled, Text} from '@devup-ui/core'\n const StyledComponent = styled(Text)`\n background: red;\n color: blue;\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "red",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "color",
+ value: "blue",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledComponent = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled-7.snap b/libs/extractor/src/snapshots/extractor__tests__styled-7.snap
new file mode 100644
index 00000000..d12feeec
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled-7.snap
@@ -0,0 +1,40 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled, VStack} from '@devup-ui/core'\n const StyledComponent = styled(VStack)({ bg: \"red\" })\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "red",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "display",
+ value: "flex",
+ level: 0,
+ selector: None,
+ style_order: Some(
+ 0,
+ ),
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "flex-direction",
+ value: "column",
+ level: 0,
+ selector: None,
+ style_order: Some(
+ 0,
+ ),
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledComponent = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled-8.snap b/libs/extractor/src/snapshots/extractor__tests__styled-8.snap
new file mode 100644
index 00000000..4415c930
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled-8.snap
@@ -0,0 +1,49 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled, VStack} from '@devup-ui/core'\n const StyledComponent = styled(VStack)`\n background: red;\n color: blue;\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "red",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "color",
+ value: "blue",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "display",
+ value: "flex",
+ level: 0,
+ selector: None,
+ style_order: Some(
+ 0,
+ ),
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "flex-direction",
+ value: "column",
+ level: 0,
+ selector: None,
+ style_order: Some(
+ 0,
+ ),
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledComponent = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled-9.snap b/libs/extractor/src/snapshots/extractor__tests__styled-9.snap
new file mode 100644
index 00000000..c83e781f
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled-9.snap
@@ -0,0 +1,18 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledComponent = styled(CustomComponent)({ bg: \"red\" })\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "red",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledComponent = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled.snap b/libs/extractor/src/snapshots/extractor__tests__styled.snap
new file mode 100644
index 00000000..2103dc1e
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled.section`\n background: red;\n color: blue;\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "red",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "color",
+ value: "blue",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable-2.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable-2.snap
new file mode 100644
index 00000000..f5e69a79
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable-2.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled(\"div\")({ bg: \"$text\", fontSize: 16 })\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "$text",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "font-size",
+ value: "64px",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable-3.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable-3.snap
new file mode 100644
index 00000000..6aaa4e97
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable-3.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled.div`\n background: var(--text);\n color: var(--primary);\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "var(--text)",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "color",
+ value: "var(--primary)",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable-4.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable-4.snap
new file mode 100644
index 00000000..146bc483
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable-4.snap
@@ -0,0 +1,31 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled, Box} from '@devup-ui/core'\n const StyledComponent = styled(Box)({ bg: \"$text\", _hover: { bg: \"$primary\" } })\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "$primary",
+ level: 0,
+ selector: Some(
+ Selector(
+ "&:hover",
+ ),
+ ),
+ style_order: None,
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "$text",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledComponent = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable-5.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable-5.snap
new file mode 100644
index 00000000..61d7f417
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable-5.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled(\"div\")`\n background-color: var(--text);\n padding: 16px;\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background-color",
+ value: "var(--text)",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "padding",
+ value: "16px",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable.snap
new file mode 100644
index 00000000..c266ec31
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled.div({ bg: \"$text\", color: \"$primary\" })\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "$text",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Static(
+ ExtractStaticStyle {
+ property: "color",
+ value: "$primary",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-2.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-2.snap
new file mode 100644
index 00000000..0435b7af
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-2.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const primaryColor = 'blue';\n const padding = '16px';\n const StyledDiv = styled(\"div\")`\n color: ${primaryColor};\n padding: ${padding};\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "color",
+ level: 0,
+ identifier: "primaryColor",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "padding",
+ level: 0,
+ identifier: "padding",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst primaryColor = \"blue\";\nconst padding = \"16px\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-3.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-3.snap
new file mode 100644
index 00000000..e567d1e6
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-3.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const primaryColor = 'blue';\n const padding = '16px';\n const StyledDiv = styled(\"div\")({ bg: primaryColor, padding })\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "background",
+ level: 0,
+ identifier: "primaryColor",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "padding",
+ level: 0,
+ identifier: "padding",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst primaryColor = \"blue\";\nconst padding = \"16px\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-4.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-4.snap
new file mode 100644
index 00000000..e65af195
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-4.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const primaryColor = 'blue';\n const padding = '16px';\n const StyledDiv = styled.div({ bg: primaryColor, padding })\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "background",
+ level: 0,
+ identifier: "primaryColor",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "padding",
+ level: 0,
+ identifier: "padding",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst primaryColor = \"blue\";\nconst padding = \"16px\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-5.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-5.snap
new file mode 100644
index 00000000..9c93db52
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-5.snap
@@ -0,0 +1,36 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled(\"div\")`\n color: ${obj.color};\n padding: ${func()};\n background: ${obj.func()};\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "background",
+ level: 0,
+ identifier: "obj.func()",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "color",
+ level: 0,
+ identifier: "obj.color",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "padding",
+ level: 0,
+ identifier: "func()",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-6.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-6.snap
new file mode 100644
index 00000000..5cbac3d6
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-6.snap
@@ -0,0 +1,36 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled(\"div\")({ bg: obj.bg, padding: func(), color: obj.color() })\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "background",
+ level: 0,
+ identifier: "obj.bg",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "color",
+ level: 0,
+ identifier: "obj.color()",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "padding",
+ level: 0,
+ identifier: "func()",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-7.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-7.snap
new file mode 100644
index 00000000..1b1c4ab7
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion-7.snap
@@ -0,0 +1,36 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled.div({ bg: obj.bg, padding: func(), color: obj.color() })\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "background",
+ level: 0,
+ identifier: "obj.bg",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "color",
+ level: 0,
+ identifier: "obj.color()",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "padding",
+ level: 0,
+ identifier: "func()",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion.snap
new file mode 100644
index 00000000..ff8b8617
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const color = 'red';\n const StyledDiv = styled.div`\n color: ${color};\n background: blue;\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "background",
+ value: "blue",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "color",
+ level: 0,
+ identifier: "color",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst color = \"red\";\nconst StyledDiv = ({ style, className, ...rest }) => ;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion_props-2.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion_props-2.snap
new file mode 100644
index 00000000..42ab43e0
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion_props-2.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled, Box} from '@devup-ui/core'\n const fontSize = '18px';\n const StyledComponent = styled(Box)`\n font-size: ${fontSize};\n color: ${props => props.color || 'black'};\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "color",
+ level: 0,
+ identifier: "(props=>props.color||`black`)(rest)",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "font-size",
+ level: 0,
+ identifier: "fontSize",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst fontSize = \"18px\";\nconst StyledComponent = ({ style, className, ...rest }) => props.color||`black`)(rest)\n\t},\n\t...style\n}} />;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion_props-3.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion_props-3.snap
new file mode 100644
index 00000000..00fe9bb7
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion_props-3.snap
@@ -0,0 +1,36 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const margin = '10px';\n const padding = '20px';\n const StyledDiv = styled.div`\n margin: ${margin};\n padding: ${padding};\n background: ${props => props.bg || 'white'};\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "background",
+ level: 0,
+ identifier: "(props=>props.bg||`white`)(rest)",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "margin",
+ level: 0,
+ identifier: "margin",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "padding",
+ level: 0,
+ identifier: "padding",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst margin = \"10px\";\nconst padding = \"20px\";\nconst StyledDiv = ({ style, className, ...rest }) =>
props.bg||`white`)(rest)\n\t},\n\t...style\n}} />;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion_props-4.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion_props-4.snap
new file mode 100644
index 00000000..17cbc0c7
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion_props-4.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const isActive = true;\n const StyledDiv = styled.div`\n color: ${isActive ? 'red' : 'blue'};\n opacity: ${isActive ? 1 : 0.5};\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "color",
+ level: 0,
+ identifier: "isActive?`red`:`blue`",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "opacity",
+ level: 0,
+ identifier: "isActive?1:.5",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst isActive = true;\nconst StyledDiv = ({ style, className, ...rest }) =>
;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion_props.snap b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion_props.snap
new file mode 100644
index 00000000..215c0c24
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__styled_with_variable_like_emotion_props.snap
@@ -0,0 +1,27 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled.div`\n background: ${props => props.bg};\n color: red;\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {
+ Static(
+ ExtractStaticStyle {
+ property: "color",
+ value: "red",
+ level: 0,
+ selector: None,
+ style_order: None,
+ },
+ ),
+ Dynamic(
+ ExtractDynamicStyle {
+ property: "background",
+ level: 0,
+ identifier: "(props=>props.bg)(rest)",
+ selector: None,
+ style_order: None,
+ },
+ ),
+ },
+ code: "import \"@devup-ui/core/devup-ui-0.css\";\nconst StyledDiv = ({ style, className, ...rest }) =>
props.bg)(rest) },\n\t...style\n}} />;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props-2.snap b/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props-2.snap
new file mode 100644
index 00000000..4d5d5899
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props-2.snap
@@ -0,0 +1,8 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled(\"div\", \"span\")`\n background: ${props => props.bg};\n color: red;\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {},
+ code: "const StyledDiv = styled(\"div\", \"span\")`\n background: ${(props) => props.bg};\n color: red;\n `;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props-3.snap b/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props-3.snap
new file mode 100644
index 00000000..ce1004c2
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props-3.snap
@@ -0,0 +1,8 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled(\"div\", \"span\").filter(Boolean)\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {},
+ code: "const StyledDiv = styled(\"div\", \"span\").filter(Boolean);\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props-4.snap b/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props-4.snap
new file mode 100644
index 00000000..59c8f889
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props-4.snap
@@ -0,0 +1,8 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled(\"div\")({ bg: \"red\" }, {})\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {},
+ code: "const StyledDiv = styled(\"div\")({ bg: \"red\" }, {});\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props-5.snap b/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props-5.snap
new file mode 100644
index 00000000..86997e50
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props-5.snap
@@ -0,0 +1,8 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled(\"div\")({ bg: \"red\" }, {})``\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {},
+ code: "const StyledDiv = styled(\"div\")({ bg: \"red\" }, {})``;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props.snap b/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props.snap
new file mode 100644
index 00000000..323e7b57
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__tests__wrong_styled_with_variable_like_emotion_props.snap
@@ -0,0 +1,8 @@
+---
+source: libs/extractor/src/lib.rs
+expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {styled} from '@devup-ui/core'\n const StyledDiv = styled(null)`\n background: ${props => props.bg};\n color: red;\n `\n \"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_dir:\n \"@devup-ui/core\".to_string(), single_css: false, import_main_css: false\n}).unwrap())"
+---
+ToBTreeSet {
+ styles: {},
+ code: "const StyledDiv = styled(null)`\n background: ${(props) => props.bg};\n color: red;\n `;\n",
+}
diff --git a/libs/extractor/src/snapshots/extractor__utils__tests__wrap_array_filter_a.snap b/libs/extractor/src/snapshots/extractor__utils__tests__wrap_array_filter_a.snap
new file mode 100644
index 00000000..f107868d
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__utils__tests__wrap_array_filter_a.snap
@@ -0,0 +1,5 @@
+---
+source: libs/extractor/src/utils.rs
+expression: code
+---
+a;
diff --git a/libs/extractor/src/snapshots/extractor__utils__tests__wrap_array_filter_a_b.snap b/libs/extractor/src/snapshots/extractor__utils__tests__wrap_array_filter_a_b.snap
new file mode 100644
index 00000000..530f4920
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__utils__tests__wrap_array_filter_a_b.snap
@@ -0,0 +1,5 @@
+---
+source: libs/extractor/src/utils.rs
+expression: code
+---
+[a,b].filter(Boolean).join(` `);
diff --git a/libs/extractor/src/snapshots/extractor__utils__tests__wrap_array_filter_className_quoteclass-namequote.snap b/libs/extractor/src/snapshots/extractor__utils__tests__wrap_array_filter_className_quoteclass-namequote.snap
new file mode 100644
index 00000000..db9ba376
--- /dev/null
+++ b/libs/extractor/src/snapshots/extractor__utils__tests__wrap_array_filter_className_quoteclass-namequote.snap
@@ -0,0 +1,5 @@
+---
+source: libs/extractor/src/utils.rs
+expression: code
+---
+[className,`class-name`].filter(Boolean).join(` `);
diff --git a/libs/extractor/src/utils.rs b/libs/extractor/src/utils.rs
index 6e0a2603..8b9a805f 100644
--- a/libs/extractor/src/utils.rs
+++ b/libs/extractor/src/utils.rs
@@ -1,6 +1,10 @@
use oxc_allocator::{Allocator, CloneIn};
-use oxc_ast::ast::{Expression, JSXAttributeValue, PropertyKey, Statement};
-use oxc_codegen::Codegen;
+use oxc_ast::{
+ AstBuilder,
+ ast::{Argument, Expression, JSXAttributeValue, PropertyKey, Statement},
+};
+
+use oxc_codegen::{Codegen, CodegenOptions};
use oxc_parser::Parser;
use oxc_span::{SPAN, SourceType};
use oxc_syntax::operator::UnaryOperator;
@@ -22,8 +26,14 @@ pub(super) fn expression_to_code(expression: &Expression) -> String {
.alloc_expression_statement(SPAN, expression.clone_in(&allocator)),
),
);
- let code = Codegen::new().build(&parsed.program).code;
- code[0..code.len() - 2].to_string()
+
+ Codegen::new()
+ .with_options(CodegenOptions {
+ minify: true,
+ ..Default::default()
+ })
+ .build(&parsed.program)
+ .code
}
pub(super) fn is_same_expression<'a>(a: &Expression<'a>, b: &Expression<'a>) -> bool {
@@ -111,6 +121,116 @@ pub(super) fn get_string_by_literal_expression(expr: &Expression) -> Option
(
+ builder: &AstBuilder<'a>,
+ expr: &[Expression<'a>],
+) -> Option> {
+ if expr.is_empty() {
+ return None;
+ }
+ if expr.len() == 1 {
+ return Some(expr[0].clone_in(builder.allocator));
+ }
+
+ // 1. Create ArrayExpression: [a, b, ...]
+ let array_elements = oxc_allocator::Vec::from_iter_in(
+ expr.iter().map(|e| e.clone_in(builder.allocator).into()),
+ builder.allocator,
+ );
+ let array_expr = builder.expression_array(SPAN, array_elements);
+
+ // 2. Create StaticMemberExpression: array.filter
+ let filter_member = Expression::StaticMemberExpression(builder.alloc_static_member_expression(
+ SPAN,
+ array_expr,
+ builder.identifier_name(SPAN, builder.atom("filter")),
+ false,
+ ));
+
+ // 3. Create CallExpression: array.filter(Boolean)
+ let filter_call = Expression::CallExpression(builder.alloc_call_expression::