diff --git a/cli/engine/design-system.mjs b/cli/engine/design-system.mjs index 511329c5c..c7e993c8f 100644 --- a/cli/engine/design-system.mjs +++ b/cli/engine/design-system.mjs @@ -743,11 +743,12 @@ function isShadowPropertyContext(line, match) { // Unlike jsColorKeyContext, the JS tail admits commas: a multi-layer shadow // string is comma-separated, and a later property on the same line is still // blocked because it sits past the string's closing quote. Both tails also - // admit complete `${...}` interpolations, so a tokenized dynamic shadow - // (template literal or CSS-in-JS) keeps its context; a bare `}`, quote, or - // `;` still ends it. - return /(?:^|[{\s;"'`(,])(?:box-shadow|text-shadow)\s*:\s*(?:\$\{[^}"'`]*\}|[^;{}"'`])*$/i.test(before) - || /(?:^|[,{]\s*)(?:boxShadow|textShadow)\s*[:=]\s*["'`]?(?:\$\{[^}"'`]*\}|[^"'`}])*$/i.test(before); + // admit complete `${...}` interpolations (including paired quoted strings + // inside them, for function arguments and ternaries), so a tokenized + // dynamic shadow (template literal or CSS-in-JS) keeps its context; a bare + // `}`, quote, or `;` still ends it. + return /(?:^|[{\s;"'`(,])(?:box-shadow|text-shadow)\s*:\s*(?:\$\{(?:"[^"]*"|'[^']*'|[^}"'`])*\}|[^;{}"'`])*$/i.test(before) + || /(?:^|[,{]\s*)(?:boxShadow|textShadow)\s*[:=]\s*["'`]?(?:\$\{(?:"[^"]*"|'[^']*'|[^}"'`])*\}|[^"'`}])*$/i.test(before); } function isInsideCssAttributeSelector(line, index) { diff --git a/tests/design-system.test.mjs b/tests/design-system.test.mjs index c9bddd8c6..2d9db30a5 100644 --- a/tests/design-system.test.mjs +++ b/tests/design-system.test.mjs @@ -572,17 +572,21 @@ const leak = { boxShadow: "0 1px 2px rgba(0, 0, 0, 0.28)", color: "rgba(0, 0, 0, const findings = checkSourceDesignSystem(` const card = { boxShadow: \`0 \${offset}px 2px rgba(0, 0, 0, 0.28)\` }; box-shadow: 0 1px \${blur}px rgba(0, 0, 0, 0.28); +const fn = { boxShadow: \`0 \${getShadow('lg')} 2px rgba(0, 0, 0, 0.28)\` }; +const tern = { boxShadow: \`0 1px \${dark ? "4px" : "2px"} rgba(0, 0, 0, 0.28)\` }; + box-shadow: 0 \${theme('blur')} rgba(0, 0, 0, 0.28); const leak = { boxShadow: \`0 \${offset}px rgba(0, 0, 0, 0.28)\`, color: "rgba(0, 0, 0, 0.28)" }; `, '/tmp/interpolated.js', { designSystem }); const colors = findings.filter((item) => item.antipattern === 'design-system-color'); // The documented shadow color passes after a \${...} interpolation in - // both the JS template literal and the CSS-in-JS line; the color key on - // the leak line still fires because it sits past the template's closing - // backtick. + // the JS template literal and the CSS-in-JS line, including + // interpolations carrying quoted function arguments or ternary branches; + // the color key on the leak line still fires because it sits past the + // template's closing backtick. assert.deepEqual( colors.map((item) => [item.line, item.ignoreValue]), - [[4, 'rgba(0, 0, 0, 0.28)']], + [[7, 'rgba(0, 0, 0, 0.28)']], ); });