From 6cdbcf9b0c1e8b16c7173833ff76adfd4837ee35 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Sun, 11 Sep 2022 17:56:15 -0400 Subject: [PATCH] Replaces classes in utility selectors like :where and :has --- src/util/formatVariantSelector.js | 31 ++++++++++--- tests/variants.test.js | 73 +++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 7 deletions(-) diff --git a/src/util/formatVariantSelector.js b/src/util/formatVariantSelector.js index 47d9ade7a4fc..6a2bd916306e 100644 --- a/src/util/formatVariantSelector.js +++ b/src/util/formatVariantSelector.js @@ -81,6 +81,29 @@ function resortSelector(sel) { return sel } +function eliminateIrrelevantSelectors(sel, base) { + let hasClassesMatchingCandidate = sel.some((n) => n.type === 'class' && n.value === base) + + sel.walk((child) => { + if (child.type === 'class' && child.value === base) { + hasClassesMatchingCandidate = true + return false // Stop walking + } + }) + + if (!hasClassesMatchingCandidate) { + sel.remove() + } + + // We do NOT recursively eliminate sub selectors that don't have the base class + // as this is NOT a safe operation. For example, if we have: + // `.space-x-2 > :not([hidden]) ~ :not([hidden])` + // We cannot remove the [hidden] from the :not() because it would change the + // meaning of the selector. + + // TODO: Can we do this for :matches, :is, and :where? +} + export function finalizeSelector( format, { @@ -115,13 +138,7 @@ export function finalizeSelector( // Remove extraneous selectors that do not include the base class/candidate being matched against // For example if we have a utility defined `.a, .b { color: red}` // And the formatted variant is sm:b then we want the final selector to be `.sm\:b` and not `.a, .sm\:b` - ast.each((node) => { - let hasClassesMatchingCandidate = node.some((n) => n.type === 'class' && n.value === base) - - if (!hasClassesMatchingCandidate) { - node.remove() - } - }) + ast.each((sel) => eliminateIrrelevantSelectors(sel, base)) // Normalize escaped classes, e.g.: // diff --git a/tests/variants.test.js b/tests/variants.test.js index 5d9131786920..5e1f9818ea20 100644 --- a/tests/variants.test.js +++ b/tests/variants.test.js @@ -944,3 +944,76 @@ test('multi-class utilities handle selector-mutating variants correctly', () => `) }) }) + +test('class inside pseudo-class function :has', () => { + let config = { + content: [ + { raw: html`
` }, + // { raw: html`
` }, + // { raw: html`
` }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + @layer utilities { + :where(.foo) { + color: red; + } + :matches(.foo, .bar, .baz) { + color: orange; + } + :is(.foo) { + color: yellow; + } + html:has(.foo) { + color: green; + } + } + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + :where(.foo) { + color: red; + } + :matches(.foo, .bar, .baz) { + color: orange; + } + :is(.foo) { + color: yellow; + } + html:has(.foo) { + color: green; + } + + :where(.hover\:foo:hover) { + color: red; + } + :matches(.hover\:foo:hover, .bar, .baz) { + color: orange; + } + :is(.hover\:foo:hover) { + color: yellow; + } + html:has(.hover\:foo:hover) { + color: green; + } + @media (min-width: 640px) { + :where(.sm\:foo) { + color: red; + } + :matches(.sm\:foo, .bar, .baz) { + color: orange; + } + :is(.sm\:foo) { + color: yellow; + } + html:has(.sm\:foo) { + color: green; + } + } + `) + }) +})