Fix infinite loop when using @variant inside @custom-variant that points to another @custom-variant (#19633)
This PR fixes an infinite loop when you use a `@variant` inside of a
`@custom-variant`, where the `@variant` used is another
`@custom-variant`.
The issue stems from the fact that a `@custom-variant` can use a `@slot`
that we have to replace with the proper AST nodes. However in this
setup, the AST nodes will include a `@slot` node as well, which causes
us to replace the `@slot` again, and so on, causing an infinite loop.
```css
@custom-variant a {
@slot;
}
@custom-variant b {
@variant a {
@slot;
}
}
```
The solution here is to replace the `@slot` nodes and then skip walking
the nodes that were just inserted. This does mean that we end up with a
`@slot` node in the final AST but that's not a real issue because that
will get replaced later when handling the next `@custom-variant`.
## Test plan
1. Existing tests still pass
2. Added a regression test to ensure that the infinite loop does not
happen anymore
3. Added additional tests to ensure that the behavior is correct
Thanks @wongjn for your initial debugging help and providing a test case
as well!
Fixes: #19618
This commit is contained in:
parent
d52c94ff5f
commit
df96ea5eba
3 changed files with 93 additions and 1 deletions
|
|
@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Detect utilities when containing capital letters followed by numbers ([#19465](https://github.com/tailwindlabs/tailwindcss/pull/19465))
|
||||
- Fix class extraction for Rails' strict locals ([#19525](https://github.com/tailwindlabs/tailwindcss/pull/19525))
|
||||
- Align `@utility` name validation with Oxide scanner rules ([#19524](https://github.com/tailwindlabs/tailwindcss/pull/19524))
|
||||
- Fix infinite loop when using `@variant` inside `@custom-variant` ([#19633](https://github.com/tailwindlabs/tailwindcss/pull/19633))
|
||||
|
||||
### Deprecated
|
||||
|
||||
|
|
|
|||
|
|
@ -4558,6 +4558,97 @@ describe('@custom-variant', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/19618
|
||||
test('@custom-variant can use a @variant that eventually uses another @custom-variant', async () => {
|
||||
expect(
|
||||
await compileCss(
|
||||
css`
|
||||
@custom-variant a {
|
||||
@slot;
|
||||
}
|
||||
|
||||
@custom-variant b {
|
||||
@variant a {
|
||||
@slot;
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
`,
|
||||
['a:flex', 'b:flex', 'a:b:flex', 'b:a:flex'],
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
".a\\:flex, .b\\:flex, .a\\:b\\:flex, .b\\:a\\:flex {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('@custom-variant can use a @variant that eventually uses another @custom-variant (2)', async () => {
|
||||
expect(
|
||||
await compileCss(
|
||||
css`
|
||||
@custom-variant a {
|
||||
.a {
|
||||
@slot;
|
||||
}
|
||||
}
|
||||
|
||||
@custom-variant b {
|
||||
.b {
|
||||
@variant a {
|
||||
.a-inside-b {
|
||||
@slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
`,
|
||||
['a:flex', 'b:flex', 'a:b:flex', 'b:a:flex'],
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
".a\\:flex .a, .b\\:flex .b .a .a-inside-b, .a\\:b\\:flex .a .b .a .a-inside-b, .b\\:a\\:flex .b .a .a-inside-b .a {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/19618#issuecomment-3830775912
|
||||
test('@custom-variant can use existing @slot @variants', async () => {
|
||||
expect(
|
||||
await compileCss(
|
||||
css`
|
||||
@custom-variant hocus {
|
||||
@variant hover {
|
||||
@variant focus {
|
||||
@slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@custom-variant hover {
|
||||
&:hover {
|
||||
@slot;
|
||||
}
|
||||
|
||||
&[data-hover] {
|
||||
@slot;
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
`,
|
||||
['hocus:flex'],
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
".hocus\\:flex:hover:focus, .hocus\\:flex[data-hover]:focus {
|
||||
display: flex;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('@custom-variant setup that results in a circular dependency error can be solved', async () => {
|
||||
expect(
|
||||
await compileCss(
|
||||
|
|
|
|||
|
|
@ -1196,7 +1196,7 @@ export function substituteAtSlot(ast: AstNode[], nodes: AstNode[]) {
|
|||
walk(ast, (node) => {
|
||||
// Replace `@slot` with rule nodes
|
||||
if (node.kind === 'at-rule' && node.name === '@slot') {
|
||||
return WalkAction.Replace(nodes)
|
||||
return WalkAction.ReplaceSkip(nodes)
|
||||
}
|
||||
|
||||
// Wrap `@keyframes` and `@property` in `AtRoot` nodes
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue