Fix bug with nested @apply rules in utility classes (#17924) (#17925)

Fixes #17924

When an `@apply` pointed to utility that nested usages of `@apply`,
these nested usages were not properly carried through the dependency
chain. This was because we were only tracking dependencies on the
immediate parent rather than all parents.

To fix this, this PR:

- Modifies the dependency resolution to track dependencies through the
entire parent path
- Uses a `walk(…)` for the node replacement logic so that all nested
`@apply` usages are also resolved (as these are now tracked in the
dependency list anyways

## Test Plan

- Added a regression test for #17924 to the unit tests and ensure
existing tests don't break
This commit is contained in:
Philipp Spiess 2025-05-08 18:26:07 +02:00 committed by GitHub
parent 179e5ddd7c
commit 17ca56d386
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 92 additions and 8 deletions

View File

@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix incorrectly replacing `_` with ` ` in arbitrary modifier shorthand `bg-red-500/(--my_opacity)` ([#17889](https://github.com/tailwindlabs/tailwindcss/pull/17889))
- Upgrade: Bump dependencies in parallel and make the upgrade faster ([#17898](https://github.com/tailwindlabs/tailwindcss/pull/17898))
- Don't scan `.log` files for classes by default ([#17906](https://github.com/tailwindlabs/tailwindcss/pull/17906))
- Ensure that custom utilities applying other custom utilities don't swallow nested `@apply` rules ([#17925](https://github.com/tailwindlabs/tailwindcss/pull/17925))
## [4.1.5] - 2025-04-30

View File

@ -23,7 +23,7 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
let definitions = new DefaultMap(() => new Set<AstNode>())
// Collect all new `@utility` definitions and all `@apply` rules first
walk([root], (node, { parent }) => {
walk([root], (node, { parent, path }) => {
if (node.kind !== 'at-rule') return
// Do not allow `@apply` rules inside `@keyframes` rules.
@ -66,7 +66,12 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
parents.add(parent)
for (let dependency of resolveApplyDependencies(node, designSystem)) {
dependencies.get(parent).add(dependency)
// Mark every parent in the path as having a dependency to that utility.
for (let parent of path) {
if (parent === node) continue
if (!parents.has(parent)) continue
dependencies.get(parent).add(dependency)
}
}
}
})
@ -151,11 +156,10 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
for (let parent of sorted) {
if (!('nodes' in parent)) continue
for (let i = 0; i < parent.nodes.length; i++) {
let node = parent.nodes[i]
if (node.kind !== 'at-rule' || node.name !== '@apply') continue
walk(parent.nodes, (child, { replaceWith }) => {
if (child.kind !== 'at-rule' || child.name !== '@apply') return
let candidates = node.params.split(/\s+/g)
let candidates = child.params.split(/\s+/g)
// Replace the `@apply` rule with the actual utility classes
{
@ -181,10 +185,11 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
}
}
parent.nodes.splice(i, 1, ...newNodes)
replaceWith(newNodes)
}
}
})
}
return features
}

View File

@ -612,6 +612,84 @@ describe('@apply', () => {
}"
`)
})
// https://github.com/tailwindlabs/tailwindcss/issues/17924
it('should correctly apply nested usages of @apply when one @utility applies another', async () => {
expect(
await compileCss(
css`
@theme {
--color-green-500: green;
--color-red-500: red;
--color-indigo-500: indigo;
}
@tailwind utilities;
@utility test2 {
@apply test;
}
@utility test {
@apply bg-green-500;
&:hover {
@apply bg-red-500;
}
&:disabled {
@apply bg-indigo-500;
}
}
.foo {
@apply test2;
}
`,
['foo', 'test', 'test2'],
),
).toMatchInlineSnapshot(`
":root, :host {
--color-green-500: green;
--color-red-500: red;
--color-indigo-500: indigo;
}
.test {
background-color: var(--color-green-500);
}
.test:hover {
background-color: var(--color-red-500);
}
.test:disabled {
background-color: var(--color-indigo-500);
}
.test2 {
background-color: var(--color-green-500);
}
.test2:hover {
background-color: var(--color-red-500);
}
.test2:disabled {
background-color: var(--color-indigo-500);
}
.foo {
background-color: var(--color-green-500);
}
.foo:hover {
background-color: var(--color-red-500);
}
.foo:disabled {
background-color: var(--color-indigo-500);
}"
`)
})
})
describe('arbitrary variants', () => {