Only detect nesting when using @apply (#13325)

* drop nesting detection for `@tailwind`

* drop separate nesting detection entirely

* detect nesting only when using `@apply` with a class that uses nesting

* drop unnecessary `important` config

* add test to verify applying nested user CSS errors

* add error reason to tests

* update `@apply` error message
This commit is contained in:
Robin Malfait 2024-03-24 00:10:51 +01:00 • committed by GitHub
parent 3ba51d14c7
commit bda84210d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 86 additions and 178 deletions

View file

@ -1,47 +0,0 @@
function isRoot(node) {
return node.type === 'root'
}
function isAtLayer(node) {
return node.type === 'atrule' && node.name === 'layer'
}
export default function (_context) {
return (root, result) => {
let found = false
root.walkAtRules('tailwind', (node) => {
if (found) return false
if (node.parent && !(isRoot(node.parent) || isAtLayer(node.parent))) {
found = true
node.warn(
result,
[
'Nested @tailwind rules were detected, but are not supported.',
"Consider using a prefix to scope Tailwind's classes: https://tailwindcss.com/docs/configuration#prefix",
'Alternatively, use the important selector strategy: https://tailwindcss.com/docs/configuration#selector-strategy',
].join('\n')
)
return false
}
})
root.walkRules((rule) => {
if (found) return false
rule.walkRules((nestedRule) => {
found = true
nestedRule.warn(
result,
[
'Nested CSS was detected, but CSS nesting has not been configured correctly.',
'Please enable a CSS nesting plugin *before* Tailwind in your configuration.',
'See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting',
].join('\n')
)
return false
})
})
}
}

View file

@ -432,6 +432,23 @@ function processApply(root, context, localCache) {
let rules = applyClassCache.get(applyCandidate)
// Verify that we can apply the class
for (let [, rule] of rules) {
if (rule.type === 'atrule') {
continue
}
rule.walkRules(() => {
throw apply.error(
[
`The \`${applyCandidate}\` class cannot be used with \`@apply\` because \`@apply\` does not currently support nested CSS.`,
'Rewrite the selector without nesting or configure the `tailwindcss/nesting` plugin:',
'https://tailwindcss.com/docs/using-with-preprocessors#nesting',
].join('\n')
)
})
}
candidates.push([applyCandidate, important, rules])
}
}

View file

@ -7,7 +7,6 @@ import resolveDefaultsAtRules from './lib/resolveDefaultsAtRules'
import collapseAdjacentRules from './lib/collapseAdjacentRules'
import collapseDuplicateDeclarations from './lib/collapseDuplicateDeclarations'
import partitionApplyAtRules from './lib/partitionApplyAtRules'
import detectNesting from './lib/detectNesting'
import { createContext } from './lib/setupContextUtils'
import { issueFlagNotices } from './featureFlags'
@ -15,8 +14,6 @@ export default function processTailwindFeatures(setupContext) {
return async function (root, result) {
let { tailwindDirectives, applyDirectives } = normalizeTailwindDirectives(root)
detectNesting()(root, result)
// Partition apply rules that are found in the css
// itself.
partitionApplyAtRules()(root, result)

View file

@ -2143,3 +2143,72 @@ test('should not break replacing important selector when the same as the parent
}
`)
})
test('applying classes with nested CSS should result in an error', async () => {
let config = {
content: [
{
raw: html`<div class="foo"></div>`,
},
],
}
let input = css`
@tailwind components;
@layer components {
.bar .baz {
color: red;
&:hover {
color: red;
}
}
.foo {
@apply flex baz;
}
}
`
expect.assertions(1)
return run(input, config).catch((err) => {
expect(err.reason).toMatchInlineSnapshot(`
"The \`baz\` class cannot be used with \`@apply\` because \`@apply\` does not currently support nested CSS.
Rewrite the selector without nesting or configure the \`tailwindcss/nesting\` plugin:
https://tailwindcss.com/docs/using-with-preprocessors#nesting"
`)
})
})
test('applying user defined classes with nested CSS should result in an error', async () => {
let config = {
content: [
{
raw: html`<div class="example"></div>`,
},
],
}
let input = css`
.foo {
.bar {
color: red;
}
}
.example {
@apply bar;
}
`
expect.assertions(1)
return run(input, config).catch((err) => {
expect(err.reason).toMatchInlineSnapshot(`
"The \`bar\` class cannot be used with \`@apply\` because \`@apply\` does not currently support nested CSS.
Rewrite the selector without nesting or configure the \`tailwindcss/nesting\` plugin:
https://tailwindcss.com/docs/using-with-preprocessors#nesting"
`)
})
})

View file

@ -1,128 +0,0 @@
import { run, html, css } from './util/run'
it('should warn when we detect nested css', () => {
let config = {
content: [{ raw: html`<div class="nested"></div>` }],
}
let input = css`
@tailwind utilities;
.nested {
.example {
}
}
`
return run(input, config).then((result) => {
expect(result.messages).toHaveLength(1)
expect(result.messages).toMatchObject([
{
type: 'warning',
text: [
'Nested CSS was detected, but CSS nesting has not been configured correctly.',
'Please enable a CSS nesting plugin *before* Tailwind in your configuration.',
'See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting',
].join('\n'),
},
])
})
})
it('should not warn when we detect nested css inside css @layer rules', () => {
let config = {
content: [{ raw: html`<div class="underline"></div>` }],
}
let input = css`
@layer tw-base, tw-components, tw-utilities;
@layer tw-utilities {
@tailwind utilities;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@layer tw-base, tw-components, tw-utilities;
@layer tw-utilities {
.underline {
text-decoration-line: underline;
}
}
`)
expect(result.messages).toHaveLength(0)
})
})
it('should warn when we detect namespaced @tailwind at rules', () => {
let config = {
content: [{ raw: html`<div class="text-center"></div>` }],
}
let input = css`
.namespace {
@tailwind utilities;
}
`
return run(input, config).then((result) => {
expect(result.messages).toHaveLength(1)
expect(result.messages).toMatchObject([
{
type: 'warning',
text: [
'Nested @tailwind rules were detected, but are not supported.',
"Consider using a prefix to scope Tailwind's classes: https://tailwindcss.com/docs/configuration#prefix",
'Alternatively, use the important selector strategy: https://tailwindcss.com/docs/configuration#selector-strategy',
].join('\n'),
},
])
})
})
it('should not warn when nesting a single rule inside a media query', () => {
let config = {
content: [{ raw: html`<div class="nested"></div>` }],
}
let input = css`
@tailwind utilities;
@media (min-width: 768px) {
.nested {
}
}
`
return run(input, config).then((result) => {
expect(result.messages).toHaveLength(0)
expect(result.messages).toEqual([])
})
})
it('should only warn for the first detected nesting ', () => {
let config = {
content: [{ raw: html`<div class="nested other"></div>` }],
}
let input = css`
@tailwind utilities;
.nested {
.example {
}
.other {
}
}
.other {
.example {
}
}
`
return run(input, config).then((result) => {
expect(result.messages).toHaveLength(1)
})
})