From b85123fce0c097649bd602566cb3601107c190f6 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Tue, 28 Oct 2025 13:38:20 +0900 Subject: [PATCH 1/8] Implement advencedSelector --- .changeset/wacky-snails-show.md | 6 + Cargo.lock | 69 ++++------ apps/landing/src/app/DevupUICard.tsx | 6 + bindings/devup-ui-wasm/Cargo.toml | 6 +- .../extract_style_from_expression.rs | 44 +++++- libs/extractor/src/lib.rs | 130 ++++++++++++++++++ ...r__tests__extract_advenced_selector-2.snap | 22 +++ ...r__tests__extract_advenced_selector-3.snap | 8 ++ ...r__tests__extract_advenced_selector-4.snap | 22 +++ ...r__tests__extract_advenced_selector-5.snap | 22 +++ ...r__tests__extract_advenced_selector-6.snap | 22 +++ ...tor__tests__extract_advenced_selector.snap | 22 +++ .../react/src/types/props/selector/index.ts | 57 ++++++-- packages/react/src/utils/global-css.ts | 10 +- 14 files changed, 376 insertions(+), 70 deletions(-) create mode 100644 .changeset/wacky-snails-show.md create mode 100644 libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-2.snap create mode 100644 libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-3.snap create mode 100644 libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-4.snap create mode 100644 libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-5.snap create mode 100644 libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-6.snap create mode 100644 libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector.snap diff --git a/.changeset/wacky-snails-show.md b/.changeset/wacky-snails-show.md new file mode 100644 index 00000000..1fdd1750 --- /dev/null +++ b/.changeset/wacky-snails-show.md @@ -0,0 +1,6 @@ +--- +'@devup-ui/wasm': patch +'@devup-ui/react': patch +--- + +Support selector with params diff --git a/Cargo.lock b/Cargo.lock index 0b6be0cd..03d3d50b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,9 +86,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.41" +version = "1.2.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" dependencies = [ "find-msvc-tools", "shlex", @@ -175,7 +175,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -521,9 +521,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1476,9 +1476,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -1487,25 +1487,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-futures" -version = "0.4.54" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -1516,9 +1502,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1526,31 +1512,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e381134e148c1062f965a42ed1f5ee933eef2927c3f70d1812158f711d39865" +checksum = "bfc379bfb624eb59050b509c13e77b4eb53150c350db69628141abce842f2373" dependencies = [ "js-sys", "minicov", @@ -1561,9 +1547,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b673bca3298fe582aeef8352330ecbad91849f85090805582400850f8270a2e8" +checksum = "085b2df989e1e6f9620c1311df6c996e83fe16f57792b272ce1e024ac16a90f1" dependencies = [ "proc-macro2", "quote", @@ -1572,9 +1558,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -1586,7 +1572,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1604,15 +1590,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-targets" version = "0.52.6" diff --git a/apps/landing/src/app/DevupUICard.tsx b/apps/landing/src/app/DevupUICard.tsx index 8d131212..6aa11e42 100644 --- a/apps/landing/src/app/DevupUICard.tsx +++ b/apps/landing/src/app/DevupUICard.tsx @@ -3,6 +3,12 @@ import { Box, Flex, Image, Text, VStack } from '@devup-ui/react' export function DevupUICard() { return ( ( } Expression::ObjectExpression(obj) => { let mut props = vec![]; + let params = obj.properties.iter().find_map(|p| { + if let ObjectPropertyKind::ObjectProperty(o) = p + && o.key.name().unwrap() == "params" + && selector.is_some() + && let Expression::ArrayExpression(array) = &o.value + { + Some( + array + .elements + .iter() + .filter_map(|e| { + if let Some(e) = e.as_expression() + && let Some(s) = get_string_by_literal_expression(e) + && !s.is_empty() + { + Some(s) + } else { + None + } + }) + .collect::>() + .join(","), + ) + } else { + None + } + }); + + let selector = selector.clone().map(|s| { + if let Some(params) = params + && let StyleSelector::Selector(selector) = s + { + StyleSelector::Selector(format!("{}({})", selector, params)) + } else { + s + } + }); + for p in obj.properties.iter_mut() { - if let ObjectPropertyKind::ObjectProperty(o) = p { + if let ObjectPropertyKind::ObjectProperty(o) = p + && o.key.name().unwrap() != "params" + { for name in disassemble_property(&o.key.name().unwrap()) { props.extend( extract_style_from_expression( @@ -571,7 +611,7 @@ pub fn extract_style_from_expression<'a>( Some(&name), &mut o.value, level, - selector, + &selector, ) .styles, ); diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 032a879d..81f838d3 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -7268,4 +7268,134 @@ keyframes({ .unwrap() )); } + + #[test] + #[serial] + fn extract_advenced_selector() { + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + + "#, + 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 {Box} from '@devup-ui/core' + + "#, + ExtractOption { + package: "@devup-ui/core".to_string(), + css_dir: "@devup-ui/core".to_string(), + single_css: false, + import_main_css: false + } + ) + .unwrap() + )); + + // empty + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {Box} from '@devup-ui/core' + + "#, + 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 {Box} from '@devup-ui/core' + + "#, + 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 {Box} from '@devup-ui/core' + + "#, + 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 {Box} from '@devup-ui/core' + + "#, + 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/snapshots/extractor__tests__extract_advenced_selector-2.snap b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-2.snap new file mode 100644 index 00000000..f7c7d383 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-2.snap @@ -0,0 +1,22 @@ +--- +source: libs/extractor/src/lib.rs +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: false, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Selector( + "&:is(test,test2)", + ), + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-3.snap b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-3.snap new file mode 100644 index 00000000..49d64257 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-3.snap @@ -0,0 +1,8 @@ +--- +source: libs/extractor/src/lib.rs +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: false, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: {}, + code: "
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-4.snap b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-4.snap new file mode 100644 index 00000000..0b7b4c40 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-4.snap @@ -0,0 +1,22 @@ +--- +source: libs/extractor/src/lib.rs +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: false, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Selector( + "&:is():hover", + ), + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-5.snap b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-5.snap new file mode 100644 index 00000000..87419994 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-5.snap @@ -0,0 +1,22 @@ +--- +source: libs/extractor/src/lib.rs +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: false, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Selector( + "&:is():hover", + ), + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-6.snap b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-6.snap new file mode 100644 index 00000000..75dbf5ce --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-6.snap @@ -0,0 +1,22 @@ +--- +source: libs/extractor/src/lib.rs +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: false, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Selector( + "&:is(test):hover", + ), + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n
;\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector.snap b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector.snap new file mode 100644 index 00000000..67ab2c4d --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector.snap @@ -0,0 +1,22 @@ +--- +source: libs/extractor/src/lib.rs +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: false, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Selector( + "&:is(test)", + ), + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n
;\n", +} diff --git a/packages/react/src/types/props/selector/index.ts b/packages/react/src/types/props/selector/index.ts index adf700b1..1b24b574 100644 --- a/packages/react/src/types/props/selector/index.ts +++ b/packages/react/src/types/props/selector/index.ts @@ -1,4 +1,4 @@ -import type { Pseudos } from 'csstype' +import type { AdvancedPseudos, SimplePseudos } from 'csstype' import type { ResponsiveValue } from '../../responsive-value' import type { DevupTheme } from '../../theme' @@ -13,36 +13,65 @@ type CamelCase = type PascalCase = Capitalize> -export type SelectorProps = ResponsiveValue +export type SelectorProps = ResponsiveValue export type DevupThemeSelectorProps = keyof DevupTheme extends undefined - ? Partial> - : Partial}`, SelectorProps>> + ? Partial>> + : Partial< + Record<`_theme${PascalCase}`, SelectorProps> + > -export type NormalSelector = Exclude< - Pseudos, +export type NormalizedSelector = Exclude< + T, `:-${string}` | `::-${string}` | `${string}()` > -export type ExtractSelector = T extends `::${infer R}` +export type SimpleSelector = NormalizedSelector + +export type AdvancedSelector = NormalizedSelector + +export type ExtractSelector = T extends `::${infer R}` ? R : T extends `:${infer R}` ? R : never -export type CommonSelectorProps = { - [K in ExtractSelector as + +export type AdvancedSelectorProps = { + [K in ExtractSelector> as + | `_${CamelCase}` + | `_group${PascalCase}`]?: SimpleSelectorProps & + AdvancedSelectorProps & { + params: string[] + selectors?: Selectors + } +} + +export type MultipleSelectorProps = { + [K in ExtractSelector> as + | `_${CamelCase}` + | `_group${PascalCase}`]?: SelectorProps & { + params?: string[] + } +} + +export type SimpleSelectorProps = { + [K in ExtractSelector> as | `_${CamelCase}` - | `_group${PascalCase}`]?: SelectorProps + | `_group${PascalCase}`]?: SelectorProps } export type Selectors = Partial< Record< - (string & {}) | `&${NormalSelector}` | `_${CamelCase}`, - SelectorProps + | (string & {}) + | `&${SimpleSelector}` + | `_${CamelCase>}`, + SelectorProps > > -export interface DevupSelectorProps extends CommonSelectorProps { +export interface DevupSelectorProps + extends SimpleSelectorProps, + AdvancedSelectorProps { // media query - _print?: SelectorProps + _print?: SelectorProps selectors?: Selectors diff --git a/packages/react/src/utils/global-css.ts b/packages/react/src/utils/global-css.ts index 4264c8a5..edfa6ff5 100644 --- a/packages/react/src/utils/global-css.ts +++ b/packages/react/src/utils/global-css.ts @@ -2,15 +2,15 @@ import type { DevupCommonProps } from '../types/props' import type { DevupThemeSelectorProps, ExtractSelector, - NormalSelector, + SimpleSelector, } from '../types/props/selector' import type { DevupSelectorProps } from '../types/props/selector' type GlobalCssKeys = - | `*${NormalSelector | ''}` - | `${keyof HTMLElementTagNameMap}${NormalSelector | ''}` - | `${keyof SVGElementTagNameMap}${NormalSelector | ''}` - | `_${ExtractSelector}` + | `*${SimpleSelector | ''}` + | `${keyof HTMLElementTagNameMap}${SimpleSelector | ''}` + | `${keyof SVGElementTagNameMap}${SimpleSelector | ''}` + | `_${ExtractSelector}` | (string & {}) type GlobalCssProps = { From 41a90b6034fbe0d43436bccdc7c36426a39369bc Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Tue, 28 Oct 2025 13:42:09 +0900 Subject: [PATCH 2/8] Remove test --- apps/landing/src/app/DevupUICard.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/landing/src/app/DevupUICard.tsx b/apps/landing/src/app/DevupUICard.tsx index 6aa11e42..8d131212 100644 --- a/apps/landing/src/app/DevupUICard.tsx +++ b/apps/landing/src/app/DevupUICard.tsx @@ -3,12 +3,6 @@ import { Box, Flex, Image, Text, VStack } from '@devup-ui/react' export function DevupUICard() { return ( Date: Tue, 28 Oct 2025 14:13:58 +0900 Subject: [PATCH 3/8] Support globalCss --- .../extract_style_from_expression.rs | 12 ++++-- libs/extractor/src/lib.rs | 25 +++++++++++ ...r__tests__extract_advenced_selector-7.snap | 41 +++++++++++++++++++ .../src/rules/no-useless-responsive/index.ts | 2 +- .../react/src/types/props/selector/index.ts | 7 +--- packages/react/src/utils/global-css.ts | 34 ++++++++++----- 6 files changed, 101 insertions(+), 20 deletions(-) create mode 100644 libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-7.snap diff --git a/libs/extractor/src/extractor/extract_style_from_expression.rs b/libs/extractor/src/extractor/extract_style_from_expression.rs index 33c9cb22..34c323b2 100644 --- a/libs/extractor/src/extractor/extract_style_from_expression.rs +++ b/libs/extractor/src/extractor/extract_style_from_expression.rs @@ -591,10 +591,14 @@ pub fn extract_style_from_expression<'a>( }); let selector = selector.clone().map(|s| { - if let Some(params) = params - && let StyleSelector::Selector(selector) = s - { - StyleSelector::Selector(format!("{}({})", selector, params)) + if let Some(params) = params { + if let StyleSelector::Selector(selector) = s { + StyleSelector::Selector(format!("{}({})", selector, params)) + } else if let StyleSelector::Global(selector, file) = s { + StyleSelector::Global(format!("{}({})", selector, params), file) + } else { + s + } } else { s } diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 81f838d3..14e6ef12 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -7397,5 +7397,30 @@ keyframes({ ) .unwrap() )); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from( + extract( + "test.tsx", + r#"import {globalCss} from '@devup-ui/core' + globalCss({ + "_is": { + params: ["test", variable], + _hover: { + bg: "blue" + }, + 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/snapshots/extractor__tests__extract_advenced_selector-7.snap b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-7.snap new file mode 100644 index 00000000..4fcb372e --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-7.snap @@ -0,0 +1,41 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {globalCss} from '@devup-ui/core'\n globalCss({\n \"_is\": {\n params: [\"test\", variable],\n _hover: {\n bg: \"blue\"\n },\n bg: \"red\"\n }\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: Some( + Global( + "*:is(test):hover", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Global( + "*:is(test)", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n;\n", +} diff --git a/packages/eslint-plugin/src/rules/no-useless-responsive/index.ts b/packages/eslint-plugin/src/rules/no-useless-responsive/index.ts index dec11483..5cd1ccda 100644 --- a/packages/eslint-plugin/src/rules/no-useless-responsive/index.ts +++ b/packages/eslint-plugin/src/rules/no-useless-responsive/index.ts @@ -98,7 +98,7 @@ export const noUselessResponsive = createRule({ if ( devupContext && node.key.type === AST_NODE_TYPES.Identifier && - node.key.name === 'imports' + ['imports', 'params', 'fontFaces'].includes(node.key.name) ) { devupContext = null } diff --git a/packages/react/src/types/props/selector/index.ts b/packages/react/src/types/props/selector/index.ts index 1b24b574..62bce9c2 100644 --- a/packages/react/src/types/props/selector/index.ts +++ b/packages/react/src/types/props/selector/index.ts @@ -4,7 +4,7 @@ import type { ResponsiveValue } from '../../responsive-value' import type { DevupTheme } from '../../theme' import type { DevupProps } from '../index' -type CamelCase = +export type CamelCase = S extends Lowercase ? S extends `${infer F}-${infer RF}${infer R}` ? `${F}${Uppercase}${CamelCase}` @@ -20,10 +20,7 @@ export type DevupThemeSelectorProps = keyof DevupTheme extends undefined Record<`_theme${PascalCase}`, SelectorProps> > -export type NormalizedSelector = Exclude< - T, - `:-${string}` | `::-${string}` | `${string}()` -> +export type NormalizedSelector = Exclude export type SimpleSelector = NormalizedSelector export type AdvancedSelector = NormalizedSelector diff --git a/packages/react/src/utils/global-css.ts b/packages/react/src/utils/global-css.ts index edfa6ff5..b3dfcc72 100644 --- a/packages/react/src/utils/global-css.ts +++ b/packages/react/src/utils/global-css.ts @@ -1,23 +1,37 @@ import type { DevupCommonProps } from '../types/props' import type { + AdvancedSelector, + CamelCase, DevupThemeSelectorProps, ExtractSelector, SimpleSelector, } from '../types/props/selector' import type { DevupSelectorProps } from '../types/props/selector' -type GlobalCssKeys = - | `*${SimpleSelector | ''}` - | `${keyof HTMLElementTagNameMap}${SimpleSelector | ''}` - | `${keyof SVGElementTagNameMap}${SimpleSelector | ''}` - | `_${ExtractSelector}` - | (string & {}) +type GlobalCssKeys = + | `*${T}` + | `${keyof HTMLElementTagNameMap}${T}` + | `${keyof SVGElementTagNameMap}${T}` + | `_${CamelCase>}` type GlobalCssProps = { - [K in GlobalCssKeys]?: - | DevupCommonProps - | DevupSelectorProps - | DevupThemeSelectorProps + [K in GlobalCssKeys]?: DevupCommonProps & + DevupSelectorProps & + DevupThemeSelectorProps & { + params: string[] + } +} & { + [K in GlobalCssKeys< + Exclude + >]?: DevupCommonProps & + DevupSelectorProps & + DevupThemeSelectorProps & { + params?: string[] + } +} & { + [K in GlobalCssKeys | (string & {})]?: DevupCommonProps & + DevupSelectorProps & + DevupThemeSelectorProps } interface FontFaceProps { From c9adc26fbb2dcb1850c39b963ef0316a7872c0f5 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Tue, 28 Oct 2025 14:30:12 +0900 Subject: [PATCH 4/8] Fix media test code --- .../extract_style_from_expression.rs | 28 +++++++++---------- libs/extractor/src/lib.rs | 3 ++ ...r__tests__extract_advenced_selector-7.snap | 18 +++++++++++- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/libs/extractor/src/extractor/extract_style_from_expression.rs b/libs/extractor/src/extractor/extract_style_from_expression.rs index 34c323b2..9c2c6893 100644 --- a/libs/extractor/src/extractor/extract_style_from_expression.rs +++ b/libs/extractor/src/extractor/extract_style_from_expression.rs @@ -568,22 +568,20 @@ pub fn extract_style_from_expression<'a>( && selector.is_some() && let Expression::ArrayExpression(array) = &o.value { + let arr = array.elements.iter(); Some( - array - .elements - .iter() - .filter_map(|e| { - if let Some(e) = e.as_expression() - && let Some(s) = get_string_by_literal_expression(e) - && !s.is_empty() - { - Some(s) - } else { - None - } - }) - .collect::>() - .join(","), + arr.filter_map(|e| { + if let Some(e) = e.as_expression() + && let Some(s) = get_string_by_literal_expression(e) + && !s.is_empty() + { + Some(s) + } else { + None + } + }) + .collect::>() + .join(","), ) } else { None diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index 14e6ef12..fae30d3c 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -7409,6 +7409,9 @@ keyframes({ _hover: { bg: "blue" }, + _print: { + bg: "green", + }, bg: "red" } }) diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-7.snap b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-7.snap index 4fcb372e..81683571 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-7.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-7.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {globalCss} from '@devup-ui/core'\n globalCss({\n \"_is\": {\n params: [\"test\", variable],\n _hover: {\n bg: \"blue\"\n },\n bg: \"red\"\n }\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())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {globalCss} from '@devup-ui/core'\n globalCss({\n \"_is\": {\n params: [\"test\", variable],\n _hover: {\n bg: \"blue\"\n },\n _print: {\n bg: \"green\",\n },\n bg: \"red\"\n }\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: { @@ -20,6 +20,22 @@ ToBTreeSet { ), }, ), + Static( + ExtractStaticStyle { + property: "background", + value: "green", + level: 0, + selector: Some( + Global( + "*:is(test):print", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), Static( ExtractStaticStyle { property: "background", From e4f1789dba20b395411720da70b83f584863f670 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Tue, 28 Oct 2025 14:39:23 +0900 Subject: [PATCH 5/8] Fix coverage --- .../extractor/extract_style_from_expression.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/libs/extractor/src/extractor/extract_style_from_expression.rs b/libs/extractor/src/extractor/extract_style_from_expression.rs index 9c2c6893..bb4b0ebe 100644 --- a/libs/extractor/src/extractor/extract_style_from_expression.rs +++ b/libs/extractor/src/extractor/extract_style_from_expression.rs @@ -588,18 +588,22 @@ pub fn extract_style_from_expression<'a>( } }); - let selector = selector.clone().map(|s| { - if let Some(params) = params { - if let StyleSelector::Selector(selector) = s { + let selector = selector.clone().map(|s| match &s { + StyleSelector::Selector(selector) => { + if let Some(params) = params { StyleSelector::Selector(format!("{}({})", selector, params)) - } else if let StyleSelector::Global(selector, file) = s { - StyleSelector::Global(format!("{}({})", selector, params), file) } else { s } - } else { - s } + StyleSelector::Global(selector, file) => { + if let Some(params) = params { + StyleSelector::Global(format!("{}({})", selector, params), file.clone()) + } else { + s + } + } + _ => s, }); for p in obj.properties.iter_mut() { From 585ba162f4d8484f8617a865e1ebe2edfebddc0e Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Tue, 28 Oct 2025 15:20:22 +0900 Subject: [PATCH 6/8] Fix property key logic --- libs/css/src/lib.rs | 41 +++++++++++++++++++ .../extract_global_style_from_expression.rs | 23 +++-------- .../extract_keyframes_from_expression.rs | 20 ++------- .../extract_style_from_expression.rs | 31 +++++--------- .../extract_style_from_member_expression.rs | 16 ++++---- libs/extractor/src/lib.rs | 25 +++++++++-- libs/extractor/src/prop_modify_utils.rs | 10 ++--- ...r__tests__extract_advenced_selector-7.snap | 18 +------- ...r__tests__extract_advenced_selector-8.snap | 22 ++++++++++ ...ctor__tests__extract_wrong_global_css.snap | 23 +++++++++-- ...ractor__tests__extract_wrong_keyframs.snap | 11 ++++- .../extractor__tests__style_variables-9.snap | 4 +- ...actor__tests__support_transpile_cjs-6.snap | 15 ++++++- libs/extractor/src/utils.rs | 14 ++++++- libs/extractor/src/visit.rs | 12 +++--- 15 files changed, 181 insertions(+), 104 deletions(-) create mode 100644 libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-8.snap diff --git a/libs/css/src/lib.rs b/libs/css/src/lib.rs index 465b602f..acdbeaef 100644 --- a/libs/css/src/lib.rs +++ b/libs/css/src/lib.rs @@ -68,6 +68,19 @@ pub fn disassemble_property(property: &str) -> Vec { }) } +pub fn add_selector_params(selector: StyleSelector, params: &str) -> StyleSelector { + match selector { + StyleSelector::Selector(value) => StyleSelector::Selector(format!("{}({})", value, params)), + StyleSelector::Global(value, file) => { + StyleSelector::Global(format!("{}({})", value, params), file) + } + StyleSelector::Media { query, selector } => StyleSelector::Media { + query: query.to_string(), + selector: selector.map(|s| format!("{}({})", s, params)), + }, + } +} + pub fn get_enum_property_value(property: &str, value: &str) -> Option> { if let Some(map) = GLOBAL_ENUM_STYLE_PROPERTY.get(property) { if let Some(map) = map.get(value) { @@ -677,4 +690,32 @@ mod tests { assert_eq!(keyframes_to_keyframes_name("spin", None), "k-spin"); assert_eq!(keyframes_to_keyframes_name("spin1", None), "k-spin1"); } + + #[test] + fn test_add_selector_params() { + assert_eq!( + add_selector_params(StyleSelector::Selector("hover:is".to_string()), "test"), + StyleSelector::Selector("hover:is(test)".to_string()) + ); + assert_eq!( + add_selector_params( + StyleSelector::Global("&:is".to_string(), "file.ts".to_string()), + "test" + ), + StyleSelector::Global("&:is(test)".to_string(), "file.ts".to_string()) + ); + assert_eq!( + add_selector_params( + StyleSelector::Media { + query: "print".to_string(), + selector: Some("&:is".to_string()) + }, + "test" + ), + StyleSelector::Media { + query: "print".to_string(), + selector: Some("&:is(test)".to_string()) + } + ); + } } 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 b1988c38..70d8498e 100644 --- a/libs/extractor/src/extractor/extract_global_style_from_expression.rs +++ b/libs/extractor/src/extractor/extract_global_style_from_expression.rs @@ -10,7 +10,7 @@ use crate::{ extractor::{ GlobalExtractResult, extract_style_from_expression::extract_style_from_expression, }, - utils::get_string_by_literal_expression, + utils::{get_string_by_literal_expression, get_string_by_property_key}, }; use css::{ disassemble_property, @@ -19,7 +19,7 @@ use css::{ }; use oxc_ast::{ AstBuilder, - ast::{ArrayExpressionElement, Expression, ObjectPropertyKind, PropertyKey}, + ast::{ArrayExpressionElement, Expression, ObjectPropertyKind}, }; pub fn extract_global_style_from_expression<'a>( @@ -33,20 +33,7 @@ pub fn extract_global_style_from_expression<'a>( for p in obj.properties.iter_mut() { match p { ObjectPropertyKind::ObjectProperty(o) => { - if let Some(name) = if let PropertyKey::StaticIdentifier(ident) = &o.key { - Some(ident.name.to_string()) - } else if let PropertyKey::StringLiteral(s) = &o.key { - Some(s.value.to_string()) - } else if let PropertyKey::TemplateLiteral(t) = &o.key { - Some( - t.quasis - .iter() - .map(|q| q.value.raw.as_str()) - .collect::(), - ) - } else { - None - } { + if let Some(name) = get_string_by_property_key(&o.key) { if name == "imports" { if let Expression::ArrayExpression(arr) = &o.value { for p in arr.elements.iter() { @@ -108,11 +95,11 @@ pub fn extract_global_style_from_expression<'a>( .iter() .filter_map(|p| { if let ObjectPropertyKind::ObjectProperty(o) = p - && let PropertyKey::StaticIdentifier(ident) = &o.key + && let Some(property_name) = get_string_by_property_key(&o.key) && let Some(s) = get_string_by_literal_expression(&o.value) { Some( - disassemble_property(&ident.name) + disassemble_property(&property_name) .iter() .map(|p| { let v = if check_multi_css_optimize(p) { optimize_mutli_css_value(&s) } else { s.clone() }; diff --git a/libs/extractor/src/extractor/extract_keyframes_from_expression.rs b/libs/extractor/src/extractor/extract_keyframes_from_expression.rs index fd1f4766..4d8f72d4 100644 --- a/libs/extractor/src/extractor/extract_keyframes_from_expression.rs +++ b/libs/extractor/src/extractor/extract_keyframes_from_expression.rs @@ -5,10 +5,11 @@ use crate::{ ExtractResult, KeyframesExtractResult, extract_style_from_expression::extract_style_from_expression, }, + utils::get_string_by_property_key, }; use oxc_ast::{ AstBuilder, - ast::{Expression, ObjectPropertyKind, PropertyKey}, + ast::{Expression, ObjectPropertyKind}, }; pub fn extract_keyframes_from_expression<'a>( @@ -20,22 +21,7 @@ pub fn extract_keyframes_from_expression<'a>( if let Expression::ObjectExpression(obj) = expression { for p in obj.properties.iter_mut() { if let ObjectPropertyKind::ObjectProperty(o) = p - && let Some(name) = if let PropertyKey::StaticIdentifier(ident) = &o.key { - Some(ident.name.to_string()) - } else if let PropertyKey::StringLiteral(s) = &o.key { - Some(s.value.to_string()) - } else if let PropertyKey::TemplateLiteral(t) = &o.key { - Some( - t.quasis - .iter() - .map(|q| q.value.raw.as_str()) - .collect::(), - ) - } else if let PropertyKey::NumericLiteral(n) = &o.key { - Some(n.value.to_string()) - } else { - None - } + && let Some(name) = get_string_by_property_key(&o.key) { let ExtractResult { styles, .. } = extract_style_from_expression(ast_builder, None, &mut o.value, 0, &None); diff --git a/libs/extractor/src/extractor/extract_style_from_expression.rs b/libs/extractor/src/extractor/extract_style_from_expression.rs index bb4b0ebe..12929af5 100644 --- a/libs/extractor/src/extractor/extract_style_from_expression.rs +++ b/libs/extractor/src/extractor/extract_style_from_expression.rs @@ -10,18 +10,18 @@ use crate::{ }, utils::{ expression_to_code, get_number_by_literal_expression, get_string_by_literal_expression, - is_same_expression, + get_string_by_property_key, is_same_expression, }, }; use css::{ - disassemble_property, get_enum_property_map, get_enum_property_value, + add_selector_params, disassemble_property, get_enum_property_map, get_enum_property_value, is_special_property::is_special_property, style_selector::StyleSelector, utils::to_kebab_case, }; use oxc_allocator::CloneIn; use oxc_ast::{ AstBuilder, ast::{ - BinaryOperator, Expression, LogicalOperator, ObjectPropertyKind, PropertyKey, + BinaryOperator, Expression, LogicalOperator, ObjectPropertyKind, TemplateElementValue, UnaryOperator, }, }; @@ -50,9 +50,8 @@ pub fn extract_style_from_expression<'a>( let mut prop = obj.properties.remove(idx); if !match &mut prop { ObjectPropertyKind::ObjectProperty(prop) => { - if let PropertyKey::StaticIdentifier(ident) = &prop.key - && let name = ident.name.as_str() - && !is_special_property(name) + if let Some(name) = get_string_by_property_key(&prop.key) + && !is_special_property(&name) { let property_name = name.to_string(); for name in disassemble_property(&property_name) { @@ -588,22 +587,12 @@ pub fn extract_style_from_expression<'a>( } }); - let selector = selector.clone().map(|s| match &s { - StyleSelector::Selector(selector) => { - if let Some(params) = params { - StyleSelector::Selector(format!("{}({})", selector, params)) - } else { - s - } - } - StyleSelector::Global(selector, file) => { - if let Some(params) = params { - StyleSelector::Global(format!("{}({})", selector, params), file.clone()) - } else { - s - } + let selector = selector.clone().map(|s| { + if let Some(params) = params { + add_selector_params(s, ¶ms) + } else { + s } - _ => s, }); for p in obj.properties.iter_mut() { diff --git a/libs/extractor/src/extractor/extract_style_from_member_expression.rs b/libs/extractor/src/extractor/extract_style_from_member_expression.rs index 5a5c8b13..b71782fd 100644 --- a/libs/extractor/src/extractor/extract_style_from_member_expression.rs +++ b/libs/extractor/src/extractor/extract_style_from_member_expression.rs @@ -4,7 +4,10 @@ use crate::{ ExtractResult, extract_style_from_expression::{dynamic_style, extract_style_from_expression}, }, - utils::{get_number_by_literal_expression, get_string_by_literal_expression}, + utils::{ + get_number_by_literal_expression, get_string_by_literal_expression, + get_string_by_property_key, + }, }; use css::style_selector::StyleSelector; use oxc_allocator::CloneIn; @@ -12,7 +15,6 @@ use oxc_ast::{ AstBuilder, ast::{ ArrayExpressionElement, ComputedMemberExpression, Expression, ObjectPropertyKind, - PropertyKey, }, }; use oxc_span::SPAN; @@ -113,8 +115,8 @@ pub(super) fn extract_style_from_member_expression<'a>( let mut etc = None; for p in obj.properties.iter_mut() { if let ObjectPropertyKind::ObjectProperty(o) = p { - if let PropertyKey::StaticIdentifier(ref pk) = o.key - && pk.name == k + if let Some(property_name) = get_string_by_property_key(&o.key) + && property_name == k { return ExtractResult { styles: extract_style_from_expression( @@ -154,12 +156,10 @@ pub(super) fn extract_style_from_member_expression<'a>( for p in obj.properties.iter_mut() { if let ObjectPropertyKind::ObjectProperty(o) = p - && let PropertyKey::StaticIdentifier(_) - | PropertyKey::NumericLiteral(_) - | PropertyKey::StringLiteral(_) = o.key + && let Some(property_name) = get_string_by_property_key(&o.key) { map.insert( - o.key.name().unwrap().to_string(), + property_name, Box::new(ExtractStyleProp::StaticArray( extract_style_from_expression( ast_builder, diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index fae30d3c..c629b558 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -7409,9 +7409,28 @@ keyframes({ _hover: { bg: "blue" }, - _print: { - bg: "green", - }, + 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 {css} from '@devup-ui/core' + css({ + "_is": { + params: ["test"], bg: "red" } }) diff --git a/libs/extractor/src/prop_modify_utils.rs b/libs/extractor/src/prop_modify_utils.rs index 96b3553b..90b0c977 100644 --- a/libs/extractor/src/prop_modify_utils.rs +++ b/libs/extractor/src/prop_modify_utils.rs @@ -1,6 +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 oxc_allocator::CloneIn; use oxc_ast::AstBuilder; use oxc_ast::ast::JSXAttributeName::Identifier; @@ -27,8 +28,7 @@ pub fn modify_prop_object<'a>( let prop = props.remove(idx); match prop { ObjectPropertyKind::ObjectProperty(attr) => { - if let PropertyKey::StaticIdentifier(ident) = &attr.key { - let name = ident.name.as_str(); + if let Some(name) = get_string_by_property_key(&attr.key) { if name == "className" { class_name_prop = Some(attr.value.clone_in(ast_builder.allocator)); } else if name == "style" { @@ -430,10 +430,8 @@ pub fn convert_style_vars<'a>( let mut prop = obj.properties.remove(idx); if let ObjectPropertyKind::ObjectProperty(p) = &mut prop { - let name = if let PropertyKey::StaticIdentifier(ident) = &p.key { - Some(ident.name) - } else if let PropertyKey::StringLiteral(ident) = &p.key { - Some(ident.value) + let name = if let Some(name) = get_string_by_property_key(&p.key) { + Some(name) } else { obj.properties.insert( idx, diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-7.snap b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-7.snap index 81683571..4fcb372e 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-7.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-7.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {globalCss} from '@devup-ui/core'\n globalCss({\n \"_is\": {\n params: [\"test\", variable],\n _hover: {\n bg: \"blue\"\n },\n _print: {\n bg: \"green\",\n },\n bg: \"red\"\n }\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())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {globalCss} from '@devup-ui/core'\n globalCss({\n \"_is\": {\n params: [\"test\", variable],\n _hover: {\n bg: \"blue\"\n },\n bg: \"red\"\n }\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: { @@ -20,22 +20,6 @@ ToBTreeSet { ), }, ), - Static( - ExtractStaticStyle { - property: "background", - value: "green", - level: 0, - selector: Some( - Global( - "*:is(test):print", - "test.tsx", - ), - ), - style_order: Some( - 0, - ), - }, - ), Static( ExtractStaticStyle { property: "background", diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-8.snap b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-8.snap new file mode 100644 index 00000000..f9e69e47 --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__extract_advenced_selector-8.snap @@ -0,0 +1,22 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import {css} from '@devup-ui/core'\n css({\n \"_is\": {\n params: [\"test\"],\n bg: \"red\"\n }\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: Some( + Selector( + "&:is(test)", + ), + ), + style_order: None, + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui-0.css\";\n\"a-a\";\n", +} diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_wrong_global_css.snap b/libs/extractor/src/snapshots/extractor__tests__extract_wrong_global_css.snap index 826bfb86..ae6f23bf 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_wrong_global_css.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_wrong_global_css.snap @@ -1,8 +1,25 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n [1]: {\n bg: \"red\"\n }\n})\n\"#,\nExtractOption\n{ package: \"@devup-ui/core\".to_string(), css_file: None }).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { globalCss } from \"@devup-ui/core\";\nglobalCss({\n [1]: {\n bg: \"red\"\n }\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: {}, - code: ";\n", + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "red", + level: 0, + selector: Some( + Global( + "1", + "test.tsx", + ), + ), + style_order: Some( + 0, + ), + }, + ), + }, + code: "import \"@devup-ui/core/devup-ui.css\";\n;\n", } diff --git a/libs/extractor/src/snapshots/extractor__tests__extract_wrong_keyframs.snap b/libs/extractor/src/snapshots/extractor__tests__extract_wrong_keyframs.snap index d012877d..b911c2f4 100644 --- a/libs/extractor/src/snapshots/extractor__tests__extract_wrong_keyframs.snap +++ b/libs/extractor/src/snapshots/extractor__tests__extract_wrong_keyframs.snap @@ -1,6 +1,6 @@ --- source: libs/extractor/src/lib.rs -expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { keyframes } from \"@devup-ui/core\";\nkeyframes({\n from: { opacity: 0 },\n [true]: { opacity: 0.5 },\n to: { opacity: 1, color: dy }\n})\n\"#,\nExtractOption\n{\n package: \"@devup-ui/core\".to_string(), css_file: None, single_css: true,import_main_css: false\n}).unwrap())" +expression: "ToBTreeSet::from(extract(\"test.tsx\",\nr#\"import { keyframes } from \"@devup-ui/core\";\nkeyframes({\n from: { opacity: 0 },\n [true]: { opacity: 0.5 },\n to: { opacity: 1, color: dy }\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: { @@ -25,6 +25,15 @@ ToBTreeSet { style_order: None, }, ], + "true": [ + ExtractStaticStyle { + property: "opacity", + value: ".5", + level: 0, + selector: None, + style_order: None, + }, + ], }, }, ), diff --git a/libs/extractor/src/snapshots/extractor__tests__style_variables-9.snap b/libs/extractor/src/snapshots/extractor__tests__style_variables-9.snap index 01e7f25d..5542eefe 100644 --- a/libs/extractor/src/snapshots/extractor__tests__style_variables-9.snap +++ b/libs/extractor/src/snapshots/extractor__tests__style_variables-9.snap @@ -1,8 +1,8 @@ --- 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: {}, - code: "
;\n", + code: "
;\n", } diff --git a/libs/extractor/src/snapshots/extractor__tests__support_transpile_cjs-6.snap b/libs/extractor/src/snapshots/extractor__tests__support_transpile_cjs-6.snap index 162958c2..29f82ae9 100644 --- a/libs/extractor/src/snapshots/extractor__tests__support_transpile_cjs-6.snap +++ b/libs/extractor/src/snapshots/extractor__tests__support_transpile_cjs-6.snap @@ -13,6 +13,19 @@ ToBTreeSet { style_order: None, }, ), + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), Static( ExtractStaticStyle { property: "color", @@ -46,5 +59,5 @@ ToBTreeSet { "header", ), }, - code: "\"use strict\";\nimport \"@devup-ui/react/devup-ui.css\";\nObject.defineProperty(exports, Symbol.toStringTag, { value: \"Module\" });\nconst e = require(\"react/jsx-runtime\"), { Box, Text, Flex } = require(\"@devup-ui/react\");\nfunction t() {\n\treturn e.jsxs(\"div\", { children: [\n\t\te.jsx(\"div\", {\n\t\t\t[\"_hover\"]: { bg: \"blue\" },\n\t\t\tchildren: \"hello\",\n\t\t\tclassName: \"a b\"\n\t\t}),\n\t\te.jsx(\"span\", {\n\t\t\tchildren: \"typo\",\n\t\t\tclassName: \"typo-header\"\n\t\t}),\n\t\te.jsx(\"section\", {\n\t\t\tchildren: \"section\",\n\t\t\tclassName: \"c d\"\n\t\t})\n\t] });\n}\nexports.Lib = t;\n", + code: "\"use strict\";\nimport \"@devup-ui/react/devup-ui.css\";\nObject.defineProperty(exports, Symbol.toStringTag, { value: \"Module\" });\nconst e = require(\"react/jsx-runtime\"), { Box, Text, Flex } = require(\"@devup-ui/react\");\nfunction t() {\n\treturn e.jsxs(\"div\", { children: [\n\t\te.jsx(\"div\", {\n\t\t\tchildren: \"hello\",\n\t\t\tclassName: \"a b c\"\n\t\t}),\n\t\te.jsx(\"span\", {\n\t\t\tchildren: \"typo\",\n\t\t\tclassName: \"typo-header\"\n\t\t}),\n\t\te.jsx(\"section\", {\n\t\t\tchildren: \"section\",\n\t\t\tclassName: \"d e\"\n\t\t})\n\t] });\n}\nexports.Lib = t;\n", } diff --git a/libs/extractor/src/utils.rs b/libs/extractor/src/utils.rs index c1e200a5..6e0a2603 100644 --- a/libs/extractor/src/utils.rs +++ b/libs/extractor/src/utils.rs @@ -1,5 +1,5 @@ use oxc_allocator::{Allocator, CloneIn}; -use oxc_ast::ast::{Expression, JSXAttributeValue, Statement}; +use oxc_ast::ast::{Expression, JSXAttributeValue, PropertyKey, Statement}; use oxc_codegen::Codegen; use oxc_parser::Parser; use oxc_span::{SPAN, SourceType}; @@ -111,6 +111,18 @@ pub(super) fn get_string_by_literal_expression(expr: &Expression) -> Option Option { + if let PropertyKey::StaticIdentifier(ident) = key { + Some(ident.name.to_string()) + } else if let Some(s) = key.as_expression() + && let Some(s) = get_string_by_literal_expression(s) + { + Some(s) + } else { + None + } +} + pub fn gcd(a: u32, b: u32) -> u32 { if b == 0 { a } else { gcd(b, a % b) } } diff --git a/libs/extractor/src/visit.rs b/libs/extractor/src/visit.rs index a71c0bbf..aaae4d37 100644 --- a/libs/extractor/src/visit.rs +++ b/libs/extractor/src/visit.rs @@ -24,7 +24,7 @@ use oxc_ast::ast::JSXAttributeItem::Attribute; use oxc_ast::ast::JSXAttributeName::Identifier; use oxc_ast::ast::{ Argument, BindingPatternKind, CallExpression, Expression, ImportDeclaration, - ImportOrExportKind, JSXAttributeValue, JSXChild, JSXElement, Program, PropertyKey, Statement, + ImportOrExportKind, JSXAttributeValue, JSXChild, JSXElement, Program, Statement, VariableDeclarator, WithClause, }; use oxc_ast_visit::VisitMut; @@ -34,7 +34,7 @@ use oxc_ast_visit::walk_mut::{ }; use strum::IntoEnumIterator; -use crate::utils::jsx_expression_to_number; +use crate::utils::{get_string_by_property_key, jsx_expression_to_number}; use oxc_ast::AstBuilder; use oxc_span::SPAN; use std::collections::{HashMap, HashSet}; @@ -392,13 +392,13 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { self.jsx_object = Some(ident.name.to_string()); } else if let BindingPatternKind::ObjectPattern(object) = &it.id.kind { for prop in &object.properties { - if let PropertyKey::StaticIdentifier(ident) = &prop.key + if let Some(name) = get_string_by_property_key(&prop.key) && let Some(k) = prop .value .get_binding_identifier() .map(|id| id.name.to_string()) { - self.jsx_imports.insert(k, ident.name.to_string()); + self.jsx_imports.insert(k, name); } } } @@ -407,7 +407,7 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { self.import_object = Some(ident.name.to_string()); } else if let BindingPatternKind::ObjectPattern(object) = &it.id.kind { for prop in &object.properties { - if let PropertyKey::StaticIdentifier(ident) = &prop.key + if let Some(name) = get_string_by_property_key(&prop.key) && let Ok(kind) = ExportVariableKind::try_from( prop.value .get_binding_identifier() @@ -415,7 +415,7 @@ impl<'a> VisitMut<'a> for DevupVisitor<'a> { .unwrap_or_default(), ) { - self.imports.insert(ident.name.to_string(), kind); + self.imports.insert(name, kind); } } } From 20f86966f2319d06370ab3bc65a6a8857bda7bf4 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Tue, 28 Oct 2025 15:23:24 +0900 Subject: [PATCH 7/8] Fix sort --- libs/extractor/src/extractor/extract_style_from_expression.rs | 4 ++-- .../src/extractor/extract_style_from_member_expression.rs | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/libs/extractor/src/extractor/extract_style_from_expression.rs b/libs/extractor/src/extractor/extract_style_from_expression.rs index 12929af5..ee6d97db 100644 --- a/libs/extractor/src/extractor/extract_style_from_expression.rs +++ b/libs/extractor/src/extractor/extract_style_from_expression.rs @@ -21,8 +21,8 @@ use oxc_allocator::CloneIn; use oxc_ast::{ AstBuilder, ast::{ - BinaryOperator, Expression, LogicalOperator, ObjectPropertyKind, - TemplateElementValue, UnaryOperator, + BinaryOperator, Expression, LogicalOperator, ObjectPropertyKind, TemplateElementValue, + UnaryOperator, }, }; use oxc_span::SPAN; diff --git a/libs/extractor/src/extractor/extract_style_from_member_expression.rs b/libs/extractor/src/extractor/extract_style_from_member_expression.rs index b71782fd..091e4f27 100644 --- a/libs/extractor/src/extractor/extract_style_from_member_expression.rs +++ b/libs/extractor/src/extractor/extract_style_from_member_expression.rs @@ -13,9 +13,7 @@ use css::style_selector::StyleSelector; use oxc_allocator::CloneIn; use oxc_ast::{ AstBuilder, - ast::{ - ArrayExpressionElement, ComputedMemberExpression, Expression, ObjectPropertyKind, - }, + ast::{ArrayExpressionElement, ComputedMemberExpression, Expression, ObjectPropertyKind}, }; use oxc_span::SPAN; use std::collections::BTreeMap; From b0eff391e6023ccdbebe5cfb2243a3623404b9e6 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Tue, 28 Oct 2025 15:40:53 +0900 Subject: [PATCH 8/8] Fix selector --- libs/extractor/src/lib.rs | 3 ++ ...actor__tests__support_transpile_cjs-7.snap | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 libs/extractor/src/snapshots/extractor__tests__support_transpile_cjs-7.snap diff --git a/libs/extractor/src/lib.rs b/libs/extractor/src/lib.rs index c629b558..94dbacb2 100644 --- a/libs/extractor/src/lib.rs +++ b/libs/extractor/src/lib.rs @@ -3720,6 +3720,9 @@ e(o, { className: "a", bg: variable, style: { color: "blue" }, ...props }) reset_class_map(); assert_debug_snapshot!(ToBTreeSet::from(extract("test.js", r#""use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),{Box,Text,Flex}=require("@devup-ui/react");function t(){return e.jsxs("div",{children:[e.jsx(Box,{["_hover"]:{bg:"blue"},bg:"$text",color:"red",children:"hello"}),e.jsx(Text,{typography:`header`,children:"typo"}),e.jsx(Flex,{as:"section",mt:2,children:"section"})]})}exports.Lib=t;"#, ExtractOption { package: "@devup-ui/react".to_string(), css_dir: "@devup-ui/react".to_string(), single_css: true, import_main_css: false }).unwrap())); + + reset_class_map(); + assert_debug_snapshot!(ToBTreeSet::from(extract("test.js", r#""use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),{Box,Text,Flex}=require("@devup-ui/react");function t(){return e.jsxs("div",{children:[e.jsx(Box,{["_hover"]:{bg:"blue"},bg:"$text",[variable]:"red",children:"hello"})]})}exports.Lib=t;"#, ExtractOption { package: "@devup-ui/react".to_string(), css_dir: "@devup-ui/react".to_string(), single_css: true, import_main_css: false }).unwrap())); } #[test] diff --git a/libs/extractor/src/snapshots/extractor__tests__support_transpile_cjs-7.snap b/libs/extractor/src/snapshots/extractor__tests__support_transpile_cjs-7.snap new file mode 100644 index 00000000..fa95c4ba --- /dev/null +++ b/libs/extractor/src/snapshots/extractor__tests__support_transpile_cjs-7.snap @@ -0,0 +1,31 @@ +--- +source: libs/extractor/src/lib.rs +expression: "ToBTreeSet::from(extract(\"test.js\",\nr#\"\"use strict\";Object.defineProperty(exports,Symbol.toStringTag,{value:\"Module\"});const e=require(\"react/jsx-runtime\"),{Box,Text,Flex}=require(\"@devup-ui/react\");function t(){return e.jsxs(\"div\",{children:[e.jsx(Box,{[\"_hover\"]:{bg:\"blue\"},bg:\"$text\",[variable]:\"red\",children:\"hello\"})]})}exports.Lib=t;\"#,\nExtractOption\n{\n package: \"@devup-ui/react\".to_string(), css_dir:\n \"@devup-ui/react\".to_string(), single_css: true, import_main_css: false\n}).unwrap())" +--- +ToBTreeSet { + styles: { + Static( + ExtractStaticStyle { + property: "background", + value: "$text", + level: 0, + selector: None, + style_order: None, + }, + ), + Static( + ExtractStaticStyle { + property: "background", + value: "blue", + level: 0, + selector: Some( + Selector( + "&:hover", + ), + ), + style_order: None, + }, + ), + }, + code: "\"use strict\";\nimport \"@devup-ui/react/devup-ui.css\";\nObject.defineProperty(exports, Symbol.toStringTag, { value: \"Module\" });\nconst e = require(\"react/jsx-runtime\"), { Box, Text, Flex } = require(\"@devup-ui/react\");\nfunction t() {\n\treturn e.jsxs(\"div\", { children: [e.jsx(\"div\", {\n\t\t[variable]: \"red\",\n\t\tchildren: \"hello\",\n\t\tclassName: \"a b\"\n\t})] });\n}\nexports.Lib = t;\n", +}