Skip to content

Commit

Permalink
Skip over classes inside :not(…) when nested in an at-rule (#12105)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
thecrypticace committed Sep 28, 2023
1 parent 61dc99f commit 1e7dd3a
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Make `content` optional for presets in TypeScript types ([#11730](https://github.com/tailwindlabs/tailwindcss/pull/11730))
- Handle variable colors that have variable fallback values ([#12049](https://github.com/tailwindlabs/tailwindcss/pull/12049))
- Batch reading content files to prevent `too many open files` error ([#12079](https://github.com/tailwindlabs/tailwindcss/pull/12079))
- Skip over classes inside `:not(…)` when nested in an at-rule ([#12105](https://github.com/tailwindlabs/tailwindcss/pull/12105))

### Added

Expand Down
60 changes: 31 additions & 29 deletions src/lib/setupContextUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,43 +147,45 @@ function getClasses(selector, mutate) {
return parser.transformSync(selector)
}

/**
* Ignore everything inside a :not(...). This allows you to write code like
* `div:not(.foo)`. If `.foo` is never found in your code, then we used to
* not generated it. But now we will ignore everything inside a `:not`, so
* that it still gets generated.
*
* @param {selectorParser.Root} selectors
*/
function ignoreNot(selectors) {
selectors.walkPseudos((pseudo) => {
if (pseudo.value === ':not') {
pseudo.remove()
}
})
}

function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
let classes = []
let selectors = []

// Handle normal rules
if (node.type === 'rule') {
// Ignore everything inside a :not(...). This allows you to write code like
// `div:not(.foo)`. If `.foo` is never found in your code, then we used to
// not generated it. But now we will ignore everything inside a `:not`, so
// that it still gets generated.
function ignoreNot(selectors) {
selectors.walkPseudos((pseudo) => {
if (pseudo.value === ':not') {
pseudo.remove()
}
})
}
// Handle normal rules
selectors.push(...node.selectors)
} else if (node.type === 'atrule') {
// Handle at-rules (which contains nested rules)
node.walkRules((rule) => selectors.push(...rule.selectors))
}

for (let selector of node.selectors) {
let classCandidates = getClasses(selector, ignoreNot)
// At least one of the selectors contains non-"on-demandable" candidates.
if (classCandidates.length === 0) {
state.containsNonOnDemandable = true
}
for (let selector of selectors) {
let classCandidates = getClasses(selector, ignoreNot)

for (let classCandidate of classCandidates) {
classes.push(classCandidate)
}
// At least one of the selectors contains non-"on-demandable" candidates.
if (classCandidates.length === 0) {
state.containsNonOnDemandable = true
}
}

// Handle at-rules (which contains nested rules)
else if (node.type === 'atrule') {
node.walkRules((rule) => {
for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) {
classes.push(classCandidate)
}
})
for (let classCandidate of classCandidates) {
classes.push(classCandidate)
}
}

if (depth === 0) {
Expand Down
32 changes: 32 additions & 0 deletions tests/basic-usage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -760,3 +760,35 @@ test('handled quoted arbitrary values containing escaped spaces', async () => {
`
)
})

test('Skips classes inside :not() when nested inside an at-rule', async () => {
let config = {
content: [
{
raw: html` <div class="disabled !disabled"></div> `,
},
],
corePlugins: { preflight: false },
plugins: [
function ({ addUtilities }) {
addUtilities({
'.hand:not(.disabled)': {
'@supports (cursor: pointer)': {
cursor: 'pointer',
},
},
})
},
],
}

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``)
})

0 comments on commit 1e7dd3a

Please sign in to comment.