From d4c44a181fc0c4dd53114dd770c94501d09efbc1 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Wed, 2 Jul 2025 22:03:06 +0900 Subject: [PATCH 1/3] Feat auto props --- libs/extractor/src/lib.rs | 19 ++++ libs/extractor/src/prop_modify_utils.rs | 96 ++++++++++++++++--- ...ts__extract_class_name_from_component.snap | 2 +- ...extract_style_props_with_class_name-5.snap | 2 +- ...extract_style_props_with_class_name-6.snap | 2 +- ...extract_style_props_with_class_name-7.snap | 2 +- ...extract_style_props_with_class_name-8.snap | 2 +- .../extractor__tests__rest_props.snap | 4 +- ...actor__tests__support_transpile_mjs-5.snap | 18 ---- 9 files changed, 109 insertions(+), 38 deletions(-) delete mode 100644 libs/extractor/src/snapshots/extractor__tests__support_transpile_mjs-5.snap diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 74b53a4f..d88304d5 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -1902,6 +1902,25 @@ e(o, { className: "a", bg: variable, style: { color: "blue" }, ...props }) )); } + #[test] + #[serial] + fn support_transpile_cjs22() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.js", + r#"import { jsx as e } from "react/jsx-runtime"; + import { Box as o } from "@devup-ui/core"; + e(o, { className: "a", bg: variable, style: { color: "blue" }, ...props }) + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_file: None + } + ) + .unwrap() + )); + } #[test] #[serial] fn support_transpile_cjs() { diff --git a/libs/extractor/src/prop_modify_utils.rs b/libs/extractor/src/prop_modify_utils.rs index a01866f7..0723e0f2 100644 --- a/libs/extractor/src/prop_modify_utils.rs +++ b/libs/extractor/src/prop_modify_utils.rs @@ -6,8 +6,8 @@ use oxc_ast::AstBuilder; use oxc_ast::ast::JSXAttributeItem::Attribute; use oxc_ast::ast::JSXAttributeName::Identifier; use oxc_ast::ast::{ - Expression, JSXAttributeItem, JSXAttributeValue, JSXExpression, ObjectPropertyKind, - PropertyKey, PropertyKind, TemplateElementValue, + Expression, JSXAttributeItem, JSXAttributeValue, JSXExpression, LogicalOperator, + ObjectPropertyKind, PropertyKey, PropertyKind, TemplateElementValue, }; use oxc_span::SPAN; @@ -21,6 +21,7 @@ pub fn modify_prop_object<'a>( ) { let mut class_name_prop = None; let mut style_prop = None; + let mut spread_props = vec![]; for idx in (0..props.len()).rev() { let prop = props.remove(idx); match prop { @@ -40,14 +41,20 @@ pub fn modify_prop_object<'a>( } props.insert(idx, ObjectPropertyKind::ObjectProperty(attr)); } - _ => { - props.insert(idx, prop); + ObjectPropertyKind::SpreadProperty(spread) => { + spread_props.push(spread.argument.clone_in(ast_builder.allocator)); + props.insert(idx, ObjectPropertyKind::SpreadProperty(spread)); } } } - if let Some(ex) = get_class_name_expression(ast_builder, &class_name_prop, styles, style_order) - { + if let Some(ex) = get_class_name_expression( + ast_builder, + &class_name_prop, + styles, + style_order, + &spread_props, + ) { props.push(ObjectPropertyKind::ObjectProperty( ast_builder.alloc_object_property( SPAN, @@ -60,7 +67,9 @@ pub fn modify_prop_object<'a>( ), )); } - if let Some(ex) = get_style_expression(ast_builder, &style_prop, styles, &style_vars) { + if let Some(ex) = + get_style_expression(ast_builder, &style_prop, styles, &style_vars, &spread_props) + { props.push(ObjectPropertyKind::ObjectProperty( ast_builder.alloc_object_property( SPAN, @@ -84,6 +93,7 @@ pub fn modify_props<'a>( ) { let mut class_name_prop = None; let mut style_prop = None; + let mut spread_props = vec![]; for idx in (0..props.len()).rev() { let prop = props.remove(idx); match prop { @@ -125,13 +135,19 @@ pub fn modify_props<'a>( } props.insert(idx, Attribute(attr)); } - _ => { - props.insert(idx, prop); + JSXAttributeItem::SpreadAttribute(spread) => { + spread_props.push(spread.argument.clone_in(ast_builder.allocator)); + props.insert(idx, JSXAttributeItem::SpreadAttribute(spread)); } } } - if let Some(ex) = get_class_name_expression(ast_builder, &class_name_prop, styles, style_order) - { + if let Some(ex) = get_class_name_expression( + ast_builder, + &class_name_prop, + styles, + style_order, + &spread_props, + ) { props.push(Attribute(ast_builder.alloc_jsx_attribute( SPAN, Identifier(ast_builder.alloc_jsx_identifier(SPAN, "className")), @@ -144,7 +160,9 @@ pub fn modify_props<'a>( }), ))); } - if let Some(ex) = get_style_expression(ast_builder, &style_prop, styles, &style_vars) { + if let Some(ex) = + get_style_expression(ast_builder, &style_prop, styles, &style_vars, &spread_props) + { props.push(Attribute(ast_builder.alloc_jsx_attribute( SPAN, Identifier(ast_builder.alloc_jsx_identifier(SPAN, "style")), @@ -160,16 +178,30 @@ pub fn get_class_name_expression<'a>( class_name_prop: &Option>, styles: &mut [ExtractStyleProp<'a>], style_order: Option, + spread_props: &[Expression<'a>], ) -> Option> { // should modify class name prop merge_string_expressions( ast_builder, [ - class_name_prop.clone_in(ast_builder.allocator), + class_name_prop + .as_ref() + .map(|class_name| convert_class_name(ast_builder, class_name)), gen_class_names(ast_builder, styles, style_order), ] .into_iter() .flatten() + .chain(spread_props.iter().map(|ex| { + convert_class_name( + ast_builder, + &Expression::StaticMemberExpression(ast_builder.alloc_static_member_expression( + SPAN, + ex.clone_in(ast_builder.allocator), + ast_builder.identifier_name(SPAN, ast_builder.atom("className")), + true, + )), + ) + })) .collect::>() .as_slice(), ) @@ -180,6 +212,7 @@ pub fn get_style_expression<'a>( style_prop: &Option>, styles: &[ExtractStyleProp<'a>], style_vars: &Option>, + spread_props: &[Expression<'a>], ) -> Option> { merge_object_expressions( ast_builder, @@ -192,6 +225,14 @@ pub fn get_style_expression<'a>( ] .into_iter() .flatten() + .chain(spread_props.iter().map(|ex| { + Expression::StaticMemberExpression(ast_builder.alloc_static_member_expression( + SPAN, + ex.clone_in(ast_builder.allocator), + ast_builder.identifier_name(SPAN, ast_builder.atom("style")), + true, + )) + })) .collect::>() .as_slice(), ) @@ -333,6 +374,35 @@ fn merge_object_expressions<'a>( )) } +pub fn convert_class_name<'a>( + ast_builder: &AstBuilder<'a>, + class_name: &Expression<'a>, +) -> Expression<'a> { + if matches!( + class_name, + Expression::StringLiteral(_) + | Expression::TemplateLiteral(_) + | Expression::NumericLiteral(_) + ) { + return class_name.clone_in(ast_builder.allocator); + } + + // wrap ( and ?? '' + Expression::LogicalExpression( + ast_builder.alloc_logical_expression( + SPAN, + Expression::ParenthesizedExpression( + ast_builder.alloc_parenthesized_expression( + SPAN, + class_name.clone_in(ast_builder.allocator), + ), + ), + LogicalOperator::Coalesce, + Expression::StringLiteral(ast_builder.alloc_string_literal(SPAN, "", None)), + ), + ) +} + pub fn convert_style_vars<'a>( ast_builder: &AstBuilder<'a>, style_vars: &Expression<'a>, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_class_name_from_component.snap b/libs/extractor/src/snapshots/extractor__tests__extract_class_name_from_component.snap index 2f5a8a07..35710bbf 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_class_name_from_component.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_class_name_from_component.snap @@ -45,5 +45,5 @@ ToBTreeSet { }, ), }, - 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__extract_style_props_with_class_name-5.snap b/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-5.snap index 39471513..bf8ef647 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-5.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-5.snap @@ -23,5 +23,5 @@ ToBTreeSet { }, ), }, - 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__extract_style_props_with_class_name-6.snap b/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-6.snap index a946c166..0e4c4d64 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-6.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-6.snap @@ -23,5 +23,5 @@ ToBTreeSet { }, ), }, - code: "import \"@devup-ui/core/devup-ui.css\";\n\"Next.js;\n", + code: "import \"@devup-ui/core/devup-ui.css\";\n\"Next.js;\n", } diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-7.snap b/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-7.snap index 39660de1..b24eb8e8 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-7.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-7.snap @@ -23,5 +23,5 @@ ToBTreeSet { }, ), }, - 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__extract_style_props_with_class_name-8.snap b/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-8.snap index 6bbe6ff7..dfafcdf2 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-8.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_style_props_with_class_name-8.snap @@ -40,5 +40,5 @@ ToBTreeSet { }, ), }, - 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__rest_props.snap b/libs/extractor/src/snapshots/extractor__tests__rest_props.snap index a59494cb..58e5952c 100644 --- a/libs/extractor/src/snapshots/extractor__tests__rest_props.snap +++ b/libs/extractor/src/snapshots/extractor__tests__rest_props.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.js\",\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{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" --- ToBTreeSet { styles: { @@ -25,5 +25,5 @@ ToBTreeSet { }, ), }, - 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__support_transpile_mjs-5.snap b/libs/extractor/src/snapshots/extractor__tests__support_transpile_mjs-5.snap deleted file mode 100644 index dfd2cf8b..00000000 --- a/libs/extractor/src/snapshots/extractor__tests__support_transpile_mjs-5.snap +++ /dev/null @@ -1,18 +0,0 @@ ---- -source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.js\",\nr#\"import { jsx as e } from \"react/jsx-runtime\";\nimport { Box as o } from \"@devup-ui/core\";\ne(o, { className: \"a\", bg: variable, style: { color: \"blue\" }, ...props })\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" ---- -ToBTreeSet { - styles: { - Dynamic( - ExtractDynamicStyle { - property: "background", - level: 0, - identifier: "variable", - selector: None, - style_order: None, - }, - ), - }, - code: "import \"@devup-ui/core/devup-ui.css\";\nimport { jsx as e } from \"react/jsx-runtime\";\ne(\"div\", {\n\t...props,\n\tclassName: \"a d0\",\n\tstyle: {\n\t\t...{ \"--d1\": variable },\n\t\t...{ color: \"blue\" }\n\t}\n});\n", -} From 7465ab09bbe15e585590af40ef9d8225e9be9afe Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Wed, 2 Jul 2025 22:16:06 +0900 Subject: [PATCH 2/3] Fix suffix prefix --- libs/extractor/src/lib.rs | 19 ----- libs/extractor/src/prop_modify_utils.rs | 19 +++-- ...tor__tests__avoid_same_name_component.snap | 2 +- .../extractor__tests__backtick_prop-2.snap | 2 +- .../extractor__tests__backtick_prop.snap | 2 +- .../extractor__tests__component_in_func.snap | 2 +- ..._css_props_destructuring_assignment-2.snap | 2 +- ...s__css_props_destructuring_assignment.snap | 2 +- .../extractor__tests__custom_selector-2.snap | 2 +- .../extractor__tests__custom_selector-3.snap | 2 +- .../extractor__tests__custom_selector.snap | 2 +- ...tractor__tests__duplicate_style_props.snap | 2 +- ...tractor__tests__extract_style_props-7.snap | 8 --- ...xtractor__tests__group_selector_props.snap | 2 +- .../extractor__tests__negative_props-2.snap | 2 +- .../extractor__tests__negative_props-3.snap | 2 +- .../extractor__tests__negative_props-4.snap | 2 +- .../extractor__tests__negative_props-5.snap | 2 +- .../extractor__tests__negative_props-6.snap | 2 +- .../extractor__tests__negative_props-7.snap | 2 +- .../extractor__tests__negative_props.snap | 2 +- ...rops_direct_array_responsive_select-2.snap | 2 +- ..._props_direct_array_responsive_select.snap | 2 +- ...r__tests__props_direct_array_select-2.snap | 2 +- ...r__tests__props_direct_array_select-3.snap | 2 +- ...r__tests__props_direct_array_select-4.snap | 2 +- ...r__tests__props_direct_array_select-5.snap | 2 +- ...r__tests__props_direct_array_select-6.snap | 2 +- ...r__tests__props_direct_array_select-7.snap | 2 +- ...tor__tests__props_direct_array_select.snap | 2 +- ...props_direct_hybrid_responsive_select.snap | 2 +- ...ops_direct_object_responsive_select-2.snap | 2 +- ...props_direct_object_responsive_select.snap | 2 +- ...__tests__props_direct_object_select-2.snap | 2 +- ...__tests__props_direct_object_select-3.snap | 2 +- ...__tests__props_direct_object_select-4.snap | 2 +- ...or__tests__props_direct_object_select.snap | 2 +- ...sts__props_direct_responsive_select-2.snap | 72 ------------------- ...tests__props_direct_responsive_select.snap | 72 ------------------- ...ractor__tests__props_direct_select-10.snap | 26 ------- ...ractor__tests__props_direct_select-11.snap | 44 ------------ ...ractor__tests__props_direct_select-12.snap | 44 ------------ ...ractor__tests__props_direct_select-13.snap | 26 ------- ...tractor__tests__props_direct_select-2.snap | 18 ----- ...tractor__tests__props_direct_select-3.snap | 27 ------- ...tractor__tests__props_direct_select-4.snap | 26 ------- ...tractor__tests__props_direct_select-5.snap | 27 ------- ...tractor__tests__props_direct_select-6.snap | 18 ----- ...tractor__tests__props_direct_select-7.snap | 26 ------- ...tractor__tests__props_direct_select-8.snap | 26 ------- ...tractor__tests__props_direct_select-9.snap | 26 ------- ...extractor__tests__props_direct_select.snap | 18 ----- ...rect_variable_array_responsive_select.snap | 2 +- ...ect_variable_object_responsive_select.snap | 2 +- ...props_direct_variable_object_select-2.snap | 2 +- ...__props_direct_variable_object_select.snap | 2 +- ...ops_direct_variable_responsive_select.snap | 66 ----------------- ...ts__props_wrong_direct_array_select-2.snap | 2 +- ...ts__props_wrong_direct_array_select-3.snap | 2 +- ...ts__props_wrong_direct_array_select-4.snap | 2 +- ...ests__props_wrong_direct_array_select.snap | 2 +- ...s__props_wrong_direct_object_select-2.snap | 2 +- ...s__props_wrong_direct_object_select-3.snap | 2 +- ...s__props_wrong_direct_object_select-4.snap | 2 +- ...sts__props_wrong_direct_object_select.snap | 2 +- .../extractor__tests__style_order-6.snap | 2 +- .../extractor__tests__style_order-7.snap | 2 +- .../extractor__tests__style_order.snap | 2 +- ...tractor__tests__style_variables_mjs-2.snap | 8 --- ...actor__tests__support_transpile_mjs-5.snap | 18 +++++ ...ctor__tests__template_literal_props-2.snap | 2 +- ...ctor__tests__template_literal_props-3.snap | 2 +- ...ctor__tests__template_literal_props-4.snap | 2 +- ...ractor__tests__template_literal_props.snap | 2 +- ...tests__ternary_operator_in_selector-2.snap | 2 +- ...tests__ternary_operator_in_selector-3.snap | 2 +- ...__tests__ternary_operator_in_selector.snap | 2 +- .../extractor__tests__theme_props.snap | 2 +- .../extractor__tests__theme_selector-2.snap | 2 +- .../extractor__tests__theme_selector-3.snap | 2 +- .../extractor__tests__theme_selector.snap | 2 +- 81 files changed, 91 insertions(+), 663 deletions(-) delete mode 100644 libs/extractor/src/snapshots/extractor__tests__extract_style_props-7.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_responsive_select-2.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_responsive_select.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_select-10.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_select-11.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_select-12.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_select-13.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_select-2.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_select-3.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_select-4.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_select-5.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_select-6.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_select-7.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_select-8.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_select-9.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_select.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__props_direct_variable_responsive_select.snap delete mode 100644 libs/extractor/src/snapshots/extractor__tests__style_variables_mjs-2.snap create mode 100644 libs/extractor/src/snapshots/extractor__tests__support_transpile_mjs-5.snap diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index d88304d5..74b53a4f 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -1902,25 +1902,6 @@ e(o, { className: "a", bg: variable, style: { color: "blue" }, ...props }) )); } - #[test] - #[serial] - fn support_transpile_cjs22() { - reset_class_map(); - assert_debug_snapshot!(ToBTreeSet::from( - extract( - "test.js", - r#"import { jsx as e } from "react/jsx-runtime"; - import { Box as o } from "@devup-ui/core"; - e(o, { className: "a", bg: variable, style: { color: "blue" }, ...props }) - "#, - ExtractOption { - package: "@devup-ui/core".to_string(), - css_file: None - } - ) - .unwrap() - )); - } #[test] #[serial] fn support_transpile_cjs() { diff --git a/libs/extractor/src/prop_modify_utils.rs b/libs/extractor/src/prop_modify_utils.rs index 0723e0f2..10830240 100644 --- a/libs/extractor/src/prop_modify_utils.rs +++ b/libs/extractor/src/prop_modify_utils.rs @@ -251,9 +251,14 @@ fn merge_string_expressions<'a>( let mut string_literals: std::vec::Vec = vec![]; let mut other_expressions = vec![]; + let mut prev_str = false; for ex in expressions { - string_literals.push("".to_string()); + if !prev_str { + string_literals.push("".to_string()); + prev_str = false; + } if let Expression::StringLiteral(literal) = ex { + prev_str = true; if !string_literals.is_empty() { string_literals .last_mut() @@ -314,12 +319,14 @@ fn merge_string_expressions<'a>( let trimmed = s.trim(); if trimmed.is_empty() { "".to_string() - } else if idx > 0 && idx == string_literals.len() - 1 { - if string_literals.len() == other_expressions.len() { - format!(" {trimmed} ") + } else if idx == string_literals.len() - 1 { + let prefix = if idx == 0 { "" } else { " " }; + let suffix = if string_literals.len() == other_expressions.len() { + " " } else { - format!(" {trimmed}") - } + "" + }; + format!("{prefix}{trimmed}{suffix}") } else if idx == string_literals.len() - 1 { trimmed.to_string() } else { diff --git a/libs/extractor/src/snapshots/extractor__tests__avoid_same_name_component.snap b/libs/extractor/src/snapshots/extractor__tests__avoid_same_name_component.snap index 772c8d8d..09a66b94 100644 --- a/libs/extractor/src/snapshots/extractor__tests__avoid_same_name_component.snap +++ b/libs/extractor/src/snapshots/extractor__tests__avoid_same_name_component.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.js\",\nr#\"import {Box} from '@devup-ui/core'\nimport {Button} from '@devup/ui'\n ;\n ;