diff --git a/CHANGELOG.md b/CHANGELOG.md index 96d38c1b1b1a..46ba7a7c30f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add missing `main` and `browser` fields for `@tailwindcss/browser` ([#15594](https://github.com/tailwindlabs/tailwindcss/pull/15594)) - _Upgrade (experimental)_: Pretty print `--spacing(…)` to prevent ambiguity ([#15596](https://github.com/tailwindlabs/tailwindcss/pull/15596)) +- Discard invalid variants such as `data-checked-[selected=1]:*` ([#15629](https://github.com/tailwindlabs/tailwindcss/pull/15629)) ## [4.0.0-beta.9] - 2025-01-09 diff --git a/packages/tailwindcss/src/candidate.test.ts b/packages/tailwindcss/src/candidate.test.ts index 848590a46a88..723ae0fc8e08 100644 --- a/packages/tailwindcss/src/candidate.test.ts +++ b/packages/tailwindcss/src/candidate.test.ts @@ -836,6 +836,63 @@ it('should not parse invalid arbitrary values', () => { } }) +it('should not parse invalid arbitrary values in variants', () => { + let utilities = new Utilities() + utilities.static('flex', () => []) + + let variants = new Variants() + variants.functional('data', () => {}) + + for (let candidate of [ + 'data-foo-[#0088cc]:flex', + 'data-foo[#0088cc]:flex', + + 'data-foo-[color:var(--value)]:flex', + 'data-foo[color:var(--value)]:flex', + + 'data-foo-[#0088cc]/50:flex', + 'data-foo[#0088cc]/50:flex', + + 'data-foo-[#0088cc]/[50%]:flex', + 'data-foo[#0088cc]/[50%]:flex', + + 'data-foo-[#0088cc]:flex!', + 'data-foo[#0088cc]:flex!', + + 'data-foo-[var(--value)]:flex', + 'data-foo[var(--value)]:flex', + + 'data-foo-[var(--value)]:flex!', + 'data-foo[var(--value)]:flex!', + + 'data-foo-(color:--value):flex', + 'data-foo(color:--value):flex', + + 'data-foo-(color:--value)/50:flex', + 'data-foo(color:--value)/50:flex', + + 'data-foo-(color:--value)/(--mod):flex', + 'data-foo(color:--value)/(--mod):flex', + + 'data-foo-(color:--value)/(number:--mod):flex', + 'data-foo(color:--value)/(number:--mod):flex', + + 'data-foo-(--value):flex', + 'data-foo(--value):flex', + + 'data-foo-(--value)/50:flex', + 'data-foo(--value)/50:flex', + + 'data-foo-(--value)/(--mod):flex', + 'data-foo(--value)/(--mod):flex', + + 'data-foo-(--value)/(number:--mod):flex', + 'data-foo(--value)/(number:--mod):flex', + ]) { + expect(run(candidate, { utilities, variants })).toEqual([]) + } +}) + it('should parse a utility with an implicit variable as the modifier', () => { let utilities = new Utilities() utilities.functional('bg', () => []) @@ -966,6 +1023,18 @@ it('should parse a utility with an explicit variable as the modifier that is imp `) }) +it('should not parse a partial variant', () => { + let utilities = new Utilities() + utilities.static('flex', () => []) + + let variants = new Variants() + variants.static('open', () => {}) + variants.functional('data', () => {}) + + expect(run('open-:flex', { utilities, variants })).toMatchInlineSnapshot(`[]`) + expect(run('data-:flex', { utilities, variants })).toMatchInlineSnapshot(`[]`) +}) + it('should parse a static variant starting with @', () => { let utilities = new Utilities() utilities.static('flex', () => []) diff --git a/packages/tailwindcss/src/candidate.ts b/packages/tailwindcss/src/candidate.ts index 6c15b2ba6163..abb230da005d 100644 --- a/packages/tailwindcss/src/candidate.ts +++ b/packages/tailwindcss/src/candidate.ts @@ -620,7 +620,10 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia } } - if (value[0] === '[' && value[value.length - 1] === ']') { + if (value[value.length - 1] === ']') { + // Discard values like `foo-[#bar]` + if (value[0] !== '[') continue + let arbitraryValue = decodeArbitraryValue(value.slice(1, -1)) // Empty arbitrary values are invalid. E.g.: `data-[]:` @@ -638,7 +641,10 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia } } - if (value[0] === '(' && value[value.length - 1] === ')') { + if (value[value.length - 1] === ')') { + // Discard values like `foo-(--bar)` + if (value[0] !== '(') continue + let arbitraryValue = decodeArbitraryValue(value.slice(1, -1)) // Empty arbitrary values are invalid. E.g.: `data-():`