Keep shadow context across template interpolations

Review finding on #553: the end-anchored shadow-context tails excluded
`}` (JS) and `{`/`}` (CSS), so a documented shadow color after a ${...}
interpolation in a boxShadow template literal or a CSS-in-JS
box-shadow line lost its allowance and fired as drift. Both tails now
admit complete ${...} interpolations; a bare `}`, quote, or `;` still
ends the context, so the allowance cannot leak past a template's
closing backtick into a later property.

AI-assisted (Cursor agent), reviewed by maintainer.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Abdul Wahab
2026-08-10 13:05:24 +05:00
co-authored by Cursor
parent 92c857a9ef
commit 94e957d7fc
2 changed files with 25 additions and 3 deletions
+6 -3
View File
@@ -742,9 +742,12 @@ function isShadowPropertyContext(line, match) {
const before = line.slice(0, index);
// 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.
return /(?:^|[{\s;"'`(,])(?:box-shadow|text-shadow)\s*:\s*[^;{}"'`]*$/i.test(before)
|| /(?:^|[,{]\s*)(?:boxShadow|textShadow)\s*[:=]\s*["'`]?[^"'`}]*$/i.test(before);
// 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);
}
function isInsideCssAttributeSelector(line, index) {
+19
View File
@@ -567,6 +567,25 @@ const leak = { boxShadow: "0 1px 2px rgba(0, 0, 0, 0.28)", color: "rgba(0, 0, 0,
assert.equal(findings.some((item) => item.antipattern === 'design-system-color'), false);
});
it('keeps shadow context across template interpolations', () => {
const designSystem = shadowDesignSystem();
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 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.
assert.deepEqual(
colors.map((item) => [item.line, item.ignoreValue]),
[[4, 'rgba(0, 0, 0, 0.28)']],
);
});
it('does not allow a later declaration to inherit shadow context from earlier on the line', () => {
const designSystem = shadowDesignSystem();
const findings = checkSourceDesignSystem(