Skip to content

Commit

Permalink
Fix generation of div:not(.foo) if .foo is never defined (#7815)
Browse files Browse the repository at this point in the history
* fix little typo

* ensure that `div:not(.unknown-class)` gets generated

* update changelog
  • Loading branch information
RobinMalfait authored Mar 17, 2022
1 parent 7b4cc36 commit 48728ed
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 6 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
- Correctly parse and prefix animation names with dots ([#7163](https://github.com/tailwindlabs/tailwindcss/pull/7163))
- Fix extraction from template literal/function with array ([#7481](https://github.com/tailwindlabs/tailwindcss/pull/7481))
- Don't output unparsable arbitrary values ([#7789](https://github.com/tailwindlabs/tailwindcss/pull/7789))
- Fix generation of `div:not(.foo)` if `.foo` is never defined ([#7815](https://github.com/tailwindlabs/tailwindcss/pull/7815))

### Changed

Expand Down
26 changes: 21 additions & 5 deletions src/lib/setupContextUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,18 @@ function parseStyles(styles) {
})
}

function getClasses(selector) {
function getClasses(selector, mutate) {
let parser = selectorParser((selectors) => {
let allClasses = []

if (mutate) {
mutate(selectors)
}

selectors.walkClasses((classNode) => {
allClasses.push(classNode.value)
})

return allClasses
})
return parser.transformSync(selector)
Expand All @@ -101,8 +107,20 @@ function extractCandidates(node, state = { containsNonOnDemandable: false }, dep

// 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()
}
})
}

for (let selector of node.selectors) {
let classCandidates = getClasses(selector)
let classCandidates = getClasses(selector, ignoreNot)
// At least one of the selectors contains non-"on-demandable" candidates.
if (classCandidates.length === 0) {
state.containsNonOnDemandable = true
Expand All @@ -117,9 +135,7 @@ function extractCandidates(node, state = { containsNonOnDemandable: false }, dep
// 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, state, depth + 1)
)) {
for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) {
classes.push(classCandidate)
}
})
Expand Down
27 changes: 26 additions & 1 deletion tests/basic-usage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ it('does not produce duplicate output when seeing variants preceding a wildcard
})
})

it('it can parse box shadows with variables', () => {
it('can parse box shadows with variables', () => {
let config = {
content: [{ raw: html`<div class="shadow-lg"></div>` }],
theme: {
Expand All @@ -376,3 +376,28 @@ it('it can parse box shadows with variables', () => {
`)
})
})

it('should generate styles using :not(.unknown-class) even if `.unknown-class` does not exist', () => {
let config = {
content: [{ raw: html`<div></div>` }],
corePlugins: { preflight: false },
}

let input = css`
@tailwind components;
@layer components {
div:not(.unknown-class) {
color: red;
}
}
`

return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
div:not(.unknown-class) {
color: red;
}
`)
})
})

0 comments on commit 48728ed

Please sign in to comment.