Skip to content

Commit 1e7dd3a

Browse files
Skip over classes inside :not(…) when nested in an at-rule (#12105)
* Skip over classes inside `:not(…)` when nested in an at-rule When defining a utility we skip over classes inside `:not(…)` but we missed doing this when classes were contained within an at-rule. This fixes that. * Update changelog
1 parent 61dc99f commit 1e7dd3a

File tree

3 files changed

+64
-29
lines changed

3 files changed

+64
-29
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
- Make `content` optional for presets in TypeScript types ([#11730](https://github.com/tailwindlabs/tailwindcss/pull/11730))
2020
- Handle variable colors that have variable fallback values ([#12049](https://github.com/tailwindlabs/tailwindcss/pull/12049))
2121
- Batch reading content files to prevent `too many open files` error ([#12079](https://github.com/tailwindlabs/tailwindcss/pull/12079))
22+
- Skip over classes inside `:not(…)` when nested in an at-rule ([#12105](https://github.com/tailwindlabs/tailwindcss/pull/12105))
2223

2324
### Added
2425

src/lib/setupContextUtils.js

+31-29
Original file line numberDiff line numberDiff line change
@@ -147,43 +147,45 @@ function getClasses(selector, mutate) {
147147
return parser.transformSync(selector)
148148
}
149149

150+
/**
151+
* Ignore everything inside a :not(...). This allows you to write code like
152+
* `div:not(.foo)`. If `.foo` is never found in your code, then we used to
153+
* not generated it. But now we will ignore everything inside a `:not`, so
154+
* that it still gets generated.
155+
*
156+
* @param {selectorParser.Root} selectors
157+
*/
158+
function ignoreNot(selectors) {
159+
selectors.walkPseudos((pseudo) => {
160+
if (pseudo.value === ':not') {
161+
pseudo.remove()
162+
}
163+
})
164+
}
165+
150166
function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
151167
let classes = []
168+
let selectors = []
152169

153-
// Handle normal rules
154170
if (node.type === 'rule') {
155-
// Ignore everything inside a :not(...). This allows you to write code like
156-
// `div:not(.foo)`. If `.foo` is never found in your code, then we used to
157-
// not generated it. But now we will ignore everything inside a `:not`, so
158-
// that it still gets generated.
159-
function ignoreNot(selectors) {
160-
selectors.walkPseudos((pseudo) => {
161-
if (pseudo.value === ':not') {
162-
pseudo.remove()
163-
}
164-
})
165-
}
171+
// Handle normal rules
172+
selectors.push(...node.selectors)
173+
} else if (node.type === 'atrule') {
174+
// Handle at-rules (which contains nested rules)
175+
node.walkRules((rule) => selectors.push(...rule.selectors))
176+
}
166177

167-
for (let selector of node.selectors) {
168-
let classCandidates = getClasses(selector, ignoreNot)
169-
// At least one of the selectors contains non-"on-demandable" candidates.
170-
if (classCandidates.length === 0) {
171-
state.containsNonOnDemandable = true
172-
}
178+
for (let selector of selectors) {
179+
let classCandidates = getClasses(selector, ignoreNot)
173180

174-
for (let classCandidate of classCandidates) {
175-
classes.push(classCandidate)
176-
}
181+
// At least one of the selectors contains non-"on-demandable" candidates.
182+
if (classCandidates.length === 0) {
183+
state.containsNonOnDemandable = true
177184
}
178-
}
179185

180-
// Handle at-rules (which contains nested rules)
181-
else if (node.type === 'atrule') {
182-
node.walkRules((rule) => {
183-
for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) {
184-
classes.push(classCandidate)
185-
}
186-
})
186+
for (let classCandidate of classCandidates) {
187+
classes.push(classCandidate)
188+
}
187189
}
188190

189191
if (depth === 0) {

tests/basic-usage.test.js

+32
Original file line numberDiff line numberDiff line change
@@ -760,3 +760,35 @@ test('handled quoted arbitrary values containing escaped spaces', async () => {
760760
`
761761
)
762762
})
763+
764+
test('Skips classes inside :not() when nested inside an at-rule', async () => {
765+
let config = {
766+
content: [
767+
{
768+
raw: html` <div class="disabled !disabled"></div> `,
769+
},
770+
],
771+
corePlugins: { preflight: false },
772+
plugins: [
773+
function ({ addUtilities }) {
774+
addUtilities({
775+
'.hand:not(.disabled)': {
776+
'@supports (cursor: pointer)': {
777+
cursor: 'pointer',
778+
},
779+
},
780+
})
781+
},
782+
],
783+
}
784+
785+
let input = css`
786+
@tailwind utilities;
787+
`
788+
789+
// We didn't find the hand class therefore
790+
// nothing should be generated
791+
let result = await run(input, config)
792+
793+
expect(result.css).toMatchFormattedCss(css``)
794+
})

0 commit comments

Comments
 (0)