Reject candidates with multiple modifiers (#20466)
<!-- 👋 Hey, thanks for your interest in contributing to Tailwind! **Please ask first before starting work on any significant new features.** It's never a fun experience to have your pull request declined after investing a lot of time and effort into a new feature. To avoid this from happening, we request that contributors create a discussion to first discuss any significant new features. For more info, check out the contributing guide: https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md --> ## Summary <!-- Provide a summary of the issue and the changes you're making. How does your change solve the problem? --> `segment()` preserves empty top-level segments, but the candidate and variant parsers currently use the truthiness of the third segment to detect additional modifiers. As a result, inputs such as `bg-red-500/50/`, `bg-red-500/50//foo`, `group-hover/foo/:flex`, and `group-hover/foo//bar:flex` can be parsed as valid candidates even though they contain multiple slash modifier segments. This change checks the number of segments instead of the value of the third segment. Single modifiers such as `bg-red-500/50` and `group-hover/foo:flex` continue to parse normally, while all additional top-level `/` segments are rejected. ## Test plan <!-- Explain how you tested your changes. Include the exact commands that you used to verify the change works and include screenshots/screen recordings of the update behavior in the browser if applicable. --> - `pnpm exec vitest run packages/tailwindcss/src/candidate.test.ts --hideSkippedTests` - `pnpm exec vitest run packages/tailwindcss/src --hideSkippedTests` - `pnpm exec prettier --check packages/tailwindcss/src/candidate.ts packages/tailwindcss/src/candidate.test.ts` `pnpm --filter=tailwindcss lint` was also attempted, but currently fails on existing cross-package dependency and Bun type errors unrelated to this change. --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
parent
f723e834ad
commit
41d9cae8e5
3 changed files with 32 additions and 14 deletions
|
|
@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Don't generate utilities when a modifier is used that would otherwise be silently ignored (e.g. `rounded-sm/[5]`, `shadow-sm/foo`, `stroke-2/50`) ([#20419](https://github.com/tailwindlabs/tailwindcss/pull/20419))
|
||||
- Only normalize top-level `and`, `or`, and `not` keywords in `supports-[…]` variants (e.g. `selector(a: not (.foo))` → `selector(a:not(.foo))`) ([#20420](https://github.com/tailwindlabs/tailwindcss/pull/20420))
|
||||
- Don't warn about Angular's `::ng-deep` and `:host-context()` when optimizing CSS ([#20434](https://github.com/tailwindlabs/tailwindcss/pull/20434))
|
||||
- Don't generate CSS for candidates containing an empty additional modifier (e.g. `bg-red-500/50/` and `group-hover/foo//bar:flex`) ([#20466](https://github.com/tailwindlabs/tailwindcss/pull/20466))
|
||||
|
||||
## [4.3.3] - 2026-07-16
|
||||
|
||||
|
|
|
|||
|
|
@ -516,6 +516,8 @@ it('should not parse functional utilities with multiple modifiers', () => {
|
|||
utilities.functional('bg', () => [])
|
||||
|
||||
expect(run('bg-red-1/2/3', { utilities })).toMatchInlineSnapshot(`[]`)
|
||||
expect(run('bg-red-500/50/', { utilities })).toMatchInlineSnapshot(`[]`)
|
||||
expect(run('bg-red-500/50//foo', { utilities })).toMatchInlineSnapshot(`[]`)
|
||||
})
|
||||
|
||||
it('should parse a utility with an arbitrary value', () => {
|
||||
|
|
@ -1416,6 +1418,19 @@ it('should parse a functional variant with a modifier', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
it('should not parse variants with multiple modifiers', () => {
|
||||
let utilities = new Utilities()
|
||||
utilities.static('flex', () => [])
|
||||
|
||||
let variants = new Variants()
|
||||
variants.static('hover', () => {})
|
||||
variants.compound('group', Compounds.StyleRules, () => {})
|
||||
|
||||
expect(run('group-hover/foo:flex', { utilities, variants })).toHaveLength(1)
|
||||
expect(run('group-hover/foo/:flex', { utilities, variants })).toEqual([])
|
||||
expect(run('group-hover/foo//bar:flex', { utilities, variants })).toEqual([])
|
||||
})
|
||||
|
||||
it('should parse a functional variant starting with @', () => {
|
||||
let utilities = new Utilities()
|
||||
utilities.static('flex', () => [])
|
||||
|
|
|
|||
|
|
@ -372,6 +372,14 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter
|
|||
}
|
||||
}
|
||||
|
||||
// If there's more than one modifier, the utility is invalid.
|
||||
//
|
||||
// E.g.:
|
||||
//
|
||||
// - `bg-red-500/50/50`
|
||||
let parts = segment(base, '/')
|
||||
if (parts.length > 2) return
|
||||
|
||||
// Figure out the new base and the modifier segment if present.
|
||||
//
|
||||
// E.g.:
|
||||
|
|
@ -381,14 +389,7 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter
|
|||
// ^^^^^^^^^^ -> Base without modifier
|
||||
// ^^ -> Modifier segment
|
||||
// ```
|
||||
let [baseWithoutModifier, modifierSegment = null, additionalModifier] = segment(base, '/')
|
||||
|
||||
// If there's more than one modifier, the utility is invalid.
|
||||
//
|
||||
// E.g.:
|
||||
//
|
||||
// - `bg-red-500/50/50`
|
||||
if (additionalModifier) return
|
||||
let [baseWithoutModifier, modifierSegment = null] = parts
|
||||
|
||||
let parsedModifier = modifierSegment === null ? null : parseModifier(modifierSegment)
|
||||
|
||||
|
|
@ -707,17 +708,18 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia
|
|||
|
||||
// Static, functional and compound variants
|
||||
{
|
||||
// group-hover/group-name
|
||||
// ^^^^^^^^^^^ -> Variant without modifier
|
||||
// ^^^^^^^^^^ -> Modifier
|
||||
let [variantWithoutModifier, modifier = null, additionalModifier] = segment(variant, '/')
|
||||
|
||||
// If there's more than one modifier, the variant is invalid.
|
||||
//
|
||||
// E.g.:
|
||||
//
|
||||
// - `group-hover/foo/bar`
|
||||
if (additionalModifier) return null
|
||||
let parts = segment(variant, '/')
|
||||
if (parts.length > 2) return null
|
||||
|
||||
// group-hover/group-name
|
||||
// ^^^^^^^^^^^ -> Variant without modifier
|
||||
// ^^^^^^^^^^ -> Modifier
|
||||
let [variantWithoutModifier, modifier = null] = parts
|
||||
|
||||
let roots = findRoots(variantWithoutModifier, (root) => {
|
||||
return designSystem.variants.has(root)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue