From aecc8521dc0cad9b7df728b2dedc51abf405398a Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 29 Sep 2025 18:11:09 -0400 Subject: [PATCH] v3: Remove irrelevant utility rules when matching important classes (#19030) Fixes #18678 When given a simple plugin like this: ```js export default { content: [{ raw: '!a' },], plugins: [ ({ addBase }) => addBase({ '@media (min-width: 1728px)': { '.a': { 'padding-top': '1rem !important' }, '.b': { 'padding-right': '1rem !important' }, } }), ], } ``` If this plugin saw the utility `!a` it would correctly modify both rules to eliminate any irrelevant classes. Unfortunately, in the case of the 2nd rule this meant it's left with an empty selector which is invalid. We now detect this case and remove the rule if that happens. --- CHANGELOG.md | 1 + src/lib/generateRules.js | 9 ++- tests/custom-plugins.test.js | 149 +++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92f03b87c..f9af15ba2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix `require.cache` error when loaded through a TypeScript file in Node 22.18+ ([#18665](https://github.com/tailwindlabs/tailwindcss/pull/18665)) - Support `import.meta.resolve(…)` in configs for new enough Node.js versions ([#18938](https://github.com/tailwindlabs/tailwindcss/pull/18938)) - Allow using newer versions of `postcss-load-config` for better ESM and TypeScript PostCSS config support with the CLI ([#18938](https://github.com/tailwindlabs/tailwindcss/pull/18938)) +- Remove irrelevant utility rules when matching important classes ([#19030](https://github.com/tailwindlabs/tailwindcss/pull/19030)) ## [3.4.17] - 2024-12-17 diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 12690e5e6..ba2e30557 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -143,7 +143,14 @@ function applyImportant(matches, classCandidate) { className === classCandidate ? `!${className}` : className ) - r.selector = ast.toString() + let newSelector = ast.toString() + + if (newSelector.trim() === '') { + r.remove() + return + } + + r.selector = newSelector r.walkDecls((d) => (d.important = true)) }) diff --git a/tests/custom-plugins.test.js b/tests/custom-plugins.test.js index d1ae598e5..a5176441b 100644 --- a/tests/custom-plugins.test.js +++ b/tests/custom-plugins.test.js @@ -1914,3 +1914,152 @@ test('custom properties are not converted to kebab-case when added to base layer expect(result.css).toContain(`--colors-primaryThing-500: 0, 0, 255;`) }) }) + +test('important candidates discard rules with irrelevant selectors', async () => { + let config = { + content: [{ raw: '!a1 !b1 !c1' }], + corePlugins: { preflight: false }, + plugins: [ + ({ addBase }) => { + addBase({ + '@media (min-width: 1728px)': { + '.a1': { 'padding-top': '1rem !important' }, + '.a2': { 'padding-right': '1rem !important' }, + }, + }) + + addBase({ + '@media (min-width: 1728px)': { '.b1': { 'padding-top': '1rem !important' } }, + '@media (min-width: 1729px)': { '.b2': { 'padding-right': '1rem !important' } }, + }) + + addBase({ + '.c1': { '@media (min-width: 1728px)': { 'padding-top': '1rem !important' } }, + '.c2': { '@media (min-width: 1728px)': { 'padding-right': '1rem !important' } }, + }) + }, + ], + } + + let result = await run('@tailwind base', config) + + expect(result.css).toMatchInlineSnapshot(` + "*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: + } + ::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: + } + @media (min-width: 1728px) { + .\\!a1 { + padding-top: 1rem !important + } + .\\!b1 { + padding-top: 1rem !important + } + .\\!c1 { + padding-top: 1rem !important + } + }" + `) +})