From b56f12e6e737ce5333b9f98a0592f98124666c7b Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Tue, 25 Feb 2025 17:00:55 +0100 Subject: [PATCH] Ensure nested functions in selectors used with JavaScript plugins are not truncated (#16802) Fixes #16799 This was caused by a wrong condition in the CSS value parser that put child function arguments into the parent function by accident. ## Test plan - Added a unit test to guard against regressions - Validated against the repro: Screenshot 2025-02-25 at 16 31 14 --- CHANGELOG.md | 1 + .../src/compat/selector-parser.test.ts | 54 +++++++++++++++++++ .../tailwindcss/src/compat/selector-parser.ts | 6 ++- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b9687e14..0863058bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix an issue where `@reference "…"` would sometimes omit keyframe animations ([#16774](https://github.com/tailwindlabs/tailwindcss/pull/16774)) - Ensure `z-*!` utilities are property marked as `!important` ([#16795](https://github.com/tailwindlabs/tailwindcss/pull/16795)) - Read UTF-8 CSS files that start with a byte-order mark (BOM) ([#16796](https://github.com/tailwindlabs/tailwindcss/pull/16796)) +- Ensure nested functions in selectors used with JavaScript plugins are not truncated ([#16802](https://github.com/tailwindlabs/tailwindcss/pull/16802)) ## [4.0.8] - 2025-02-21 diff --git a/packages/tailwindcss/src/compat/selector-parser.test.ts b/packages/tailwindcss/src/compat/selector-parser.test.ts index 1c0b808b0..f995c9de7 100644 --- a/packages/tailwindcss/src/compat/selector-parser.test.ts +++ b/packages/tailwindcss/src/compat/selector-parser.test.ts @@ -77,6 +77,60 @@ describe('parse', () => { }, ]) }) + + it('parses &:has(.child:nth-child(2))', () => { + expect(parse('&:has(.child:nth-child(2))')).toEqual([ + { + kind: 'selector', + value: '&', + }, + { + kind: 'function', + value: ':has', + nodes: [ + { + kind: 'selector', + value: '.child', + }, + { + kind: 'function', + value: ':nth-child', + nodes: [ + { + kind: 'value', + value: '2', + }, + ], + }, + ], + }, + ]) + }) + + it('parses &:has(:nth-child(2))', () => { + expect(parse('&:has(:nth-child(2))')).toEqual([ + { + kind: 'selector', + value: '&', + }, + { + kind: 'function', + value: ':has', + nodes: [ + { + kind: 'function', + value: ':nth-child', + nodes: [ + { + kind: 'value', + value: '2', + }, + ], + }, + ], + }, + ]) + }) }) describe('toCss', () => { diff --git a/packages/tailwindcss/src/compat/selector-parser.ts b/packages/tailwindcss/src/compat/selector-parser.ts index 341f9b581..a5ea5e098 100644 --- a/packages/tailwindcss/src/compat/selector-parser.ts +++ b/packages/tailwindcss/src/compat/selector-parser.ts @@ -300,7 +300,11 @@ export function parse(input: string) { buffer = '' i = end - ast.push(node) + if (parent) { + parent.nodes.push(node) + } else { + ast.push(node) + } break }