From 2c23b8da1ccf78a10c6dcf8c97eb13be7e9ea6ae Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 29 Sep 2023 16:31:43 -0400 Subject: [PATCH] Eliminate irrelevant rules when applying variants (#12113) * Eliminate irrelevant rules when applying variants * Update changelog --- CHANGELOG.md | 1 + src/lib/generateRules.js | 13 ++++++++-- src/util/formatVariantSelector.js | 7 +++++ tests/basic-usage.test.js | 43 +++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9109028ac75a..4ef780e40e86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Skip over classes inside `:not(…)` when nested in an at-rule ([#12105](https://github.com/tailwindlabs/tailwindcss/pull/12105)) - Update types to work with `Node16` module resolution ([#12097](https://github.com/tailwindlabs/tailwindcss/pull/12097)) - Don’t crash when important and parent selectors are equal in `@apply` ([#12112](https://github.com/tailwindlabs/tailwindcss/pull/12112)) +- Eliminate irrelevant rules when applying variants ([#12113](https://github.com/tailwindlabs/tailwindcss/pull/12113)) ## [3.3.3] - 2023-07-13 diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 475cbeff74ff..b15351bffb6d 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -820,10 +820,19 @@ function applyFinalFormat(match, { context, candidate }) { } try { - rule.selector = finalizeSelector(rule.selector, finalFormat, { - candidate: original, + let selector = finalizeSelector(rule.selector, finalFormat, { + candidate, context, }) + + // Finalize Selector determined that this candidate is irrelevant + // TODO: This elimination should happen earlier so this never happens + if (selector === null) { + rule.remove() + return + } + + rule.selector = selector } catch { // If this selector is invalid we also want to skip it // But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content diff --git a/src/util/formatVariantSelector.js b/src/util/formatVariantSelector.js index bf8076183655..6ba6f2cb2b43 100644 --- a/src/util/formatVariantSelector.js +++ b/src/util/formatVariantSelector.js @@ -186,6 +186,13 @@ export function finalizeSelector(current, formats, { context, candidate, base }) // Remove extraneous selectors that do not include the base candidate selector.each((sel) => eliminateIrrelevantSelectors(sel, base)) + // If ffter eliminating irrelevant selectors, we end up with nothing + // Then the whole "rule" this is associated with does not need to exist + // We use `null` as a marker value for that case + if (selector.length === 0) { + return null + } + // If there are no formats that means there were no variants added to the candidate // so we can just return the selector as-is let formatAst = Array.isArray(formats) diff --git a/tests/basic-usage.test.js b/tests/basic-usage.test.js index 66a5c67d42d0..fefaab93460c 100644 --- a/tests/basic-usage.test.js +++ b/tests/basic-usage.test.js @@ -1112,3 +1112,46 @@ crosscheck(({ stable, oxide, engine }) => { expect(result.css).toMatchFormattedCss(css``) }) }) + +test('Irrelevant rules are removed when applying variants', async () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + function ({ addUtilities }) { + addUtilities({ + '@supports (foo: bar)': { + // This doesn't contain `w-full` so it should not exist in the output + '.outer': { color: 'red' }, + '.outer:is(.w-full)': { color: 'green' }, + }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + // We didn't find the hand class therefore + // nothing should be generated + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + @media (min-width: 768px) { + .md\:w-full { + width: 100%; + } + @supports (foo: bar) { + .outer.md\:w-full { + color: green; + } + } + } + `) +})