diff --git a/packages/@stylexjs/eslint-plugin/__tests__/stylex-valid-styles-test.js b/packages/@stylexjs/eslint-plugin/__tests__/stylex-valid-styles-test.js index 27f4cefbb..faa69cdff 100644 --- a/packages/@stylexjs/eslint-plugin/__tests__/stylex-valid-styles-test.js +++ b/packages/@stylexjs/eslint-plugin/__tests__/stylex-valid-styles-test.js @@ -37,6 +37,24 @@ eslintTester.run('stylex-valid-styles', rule.default, { } }); `, + // TEST CASE for nested conditional styles with vars and stylex.when + { + code: ` + import * as stylex from '@stylexjs/stylex'; + const styles = stylex.create({ + icon: { + transitionDuration: { + default: vars.slow, + [stylex.when.ancestor(":active")]: vars.fast, + [stylex.when.ancestor(":hover")]: { + default: null, + "@media (hover: hover)": vars.fast, + }, + }, + }, + }); + `, + }, ` import * as stylex from '@stylexjs/stylex'; const styles = stylex.create({ diff --git a/packages/@stylexjs/eslint-plugin/src/stylex-valid-styles.js b/packages/@stylexjs/eslint-plugin/src/stylex-valid-styles.js index 5482792db..2308af31f 100644 --- a/packages/@stylexjs/eslint-plugin/src/stylex-valid-styles.js +++ b/packages/@stylexjs/eslint-plugin/src/stylex-valid-styles.js @@ -603,6 +603,39 @@ const stylexValidStyles = { }; } } + function isConditionalObject(node) { + if (node.type !== 'ObjectExpression') return false; + + return node.properties.every((prop) => { + const key = + prop.key.type === 'Identifier' ? prop.key.name : prop.key.value; + return key === 'default' || key.startsWith('@') || key.startsWith(':'); + }); + } + + function isStylexWhenKey(style) { + const key = style.key; + if ( + !style.computed || + key.type !== 'CallExpression' || + key.callee.type !== 'MemberExpression' + ) { + return false; + } + const calleeObject = key.callee.object; + const calleeProperty = key.callee.property; + return ( + (calleeObject.type === 'MemberExpression' && + calleeObject.object.type === 'Identifier' && + styleXDefaultImports.has(calleeObject.object.name) && + calleeObject.property.type === 'Identifier' && + calleeObject.property.name === 'when' && + calleeProperty.type === 'Identifier') || + (calleeObject.type === 'Identifier' && + styleXWhenImports.has(calleeObject.name) && + calleeProperty.type === 'Identifier') + ); + } function checkStyleProperty( style: Node, level: number, @@ -647,6 +680,17 @@ const stylexValidStyles = { if (isStylexResolvedVarsToken(key, stylexResolvedVarsTokenImports)) { return undefined; } + // stylex.when.*() computed key with object value - allow it! + if (isStylexWhenKey(style) && propName != null) { + return styleValue.properties.forEach((prop) => + checkStyleProperty( + prop, + level + 1, + propName ?? keyName, + outerIsPseudoElement, + ), + ); + } if ( typeof keyName !== 'string' || (key.type !== 'Literal' && key.type !== 'Identifier') @@ -682,7 +726,10 @@ const stylexValidStyles = { } else { const ruleCheck = pseudoClassesAndAtRules(key, variables); - if (ruleCheck !== undefined) { + if ( + ruleCheck !== undefined && + !isConditionalObject(style.value) + ) { return context.report({ node: style.value, loc: style.value.loc, diff --git a/yarn.lock b/yarn.lock index 25976e4a4..087972c4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14084,10 +14084,10 @@ react-router@7.9.6: cookie "^1.0.1" set-cookie-parser "^2.6.0" -react-server-dom-webpack@19.2.1, react-server-dom-webpack@^19.2.1: - version "19.2.1" - resolved "https://registry.npmjs.org/react-server-dom-webpack/-/react-server-dom-webpack-19.2.1.tgz" - integrity sha512-6z3FuEtZ7wVWyPYRhKKRo4TF7IyQ7XrQZRW1fTjzK2mLVmcv7xJtoANSixaUJ+EFjCh2ekNOaBSoI9spiMSnNA== +react-server-dom-webpack@19.2.5: + version "19.2.5" + resolved "https://registry.yarnpkg.com/react-server-dom-webpack/-/react-server-dom-webpack-19.2.5.tgz#b16c0bd15e6cb469bab952f367f8ceaaebe11206" + integrity sha512-bYhdd2cZJhXHqyJBoloYaJrn8MrL9Egf3ZZVn0OrIODCCORm2goFD7C+xszf6xgfsSJi0rtgB/ichcuHfkJ4yQ== dependencies: acorn-loose "^8.3.0" neo-async "^2.6.1" @@ -14102,6 +14102,15 @@ react-server-dom-webpack@19.3.0-canary-561ee24d-20251101: neo-async "^2.6.1" webpack-sources "^3.2.0" +react-server-dom-webpack@^19.2.1: + version "19.2.1" + resolved "https://registry.npmjs.org/react-server-dom-webpack/-/react-server-dom-webpack-19.2.1.tgz" + integrity sha512-6z3FuEtZ7wVWyPYRhKKRo4TF7IyQ7XrQZRW1fTjzK2mLVmcv7xJtoANSixaUJ+EFjCh2ekNOaBSoI9spiMSnNA== + dependencies: + acorn-loose "^8.3.0" + neo-async "^2.6.1" + webpack-sources "^3.2.0" + react-style-singleton@^2.2.2, react-style-singleton@^2.2.3: version "2.2.3" resolved "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz"