mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Disallow multi-selector arbitrary variants (#10655)
* Allow escaping in `splitAtTopLevelOnly` * Correctly parse arbitrary variants that have multiple selectors * Explicitly disallow multiple selector arbitrary variants Now that we parse them correctly we can restrict them to explicitly supporting only a single selector * Add test to verify that multiple selector arbitrary variants are dropped * Add test * Make prettier happy * Fix CS * Update changelog
This commit is contained in:
parent
d6121f0ede
commit
9bbdd9b10d
@ -19,6 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Add `caption-side` utilities ([#10470](https://github.com/tailwindlabs/tailwindcss/pull/10470))
|
||||
- Add `justify-normal` and `justify-stretch` utilities ([#10560](https://github.com/tailwindlabs/tailwindcss/pull/10560))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Disallow multiple selectors in arbitrary variants ([#10655](https://github.com/tailwindlabs/tailwindcss/pull/10655))
|
||||
|
||||
### Changed
|
||||
|
||||
- [Oxide] Disable color opacity plugins by default in the `oxide` engine ([#10618](https://github.com/tailwindlabs/tailwindcss/pull/10618))
|
||||
|
||||
@ -205,17 +205,26 @@ function applyVariant(variant, matches, context) {
|
||||
|
||||
// Register arbitrary variants
|
||||
if (isArbitraryValue(variant) && !context.variantMap.has(variant)) {
|
||||
let selector = normalize(variant.slice(1, -1))
|
||||
let sort = context.offsets.recordVariant(variant)
|
||||
|
||||
if (!isValidVariantFormatString(selector)) {
|
||||
let selector = normalize(variant.slice(1, -1))
|
||||
let selectors = splitAtTopLevelOnly(selector, ',')
|
||||
|
||||
// We do not support multiple selectors for arbitrary variants
|
||||
if (selectors.length > 1) {
|
||||
return []
|
||||
}
|
||||
|
||||
let fn = parseVariant(selector)
|
||||
if (!selectors.every(isValidVariantFormatString)) {
|
||||
return []
|
||||
}
|
||||
|
||||
let sort = context.offsets.recordVariant(variant)
|
||||
let records = selectors.map((sel, idx) => [
|
||||
context.offsets.applyParallelOffset(sort, idx),
|
||||
parseVariant(sel.trim()),
|
||||
])
|
||||
|
||||
context.variantMap.set(variant, [[sort, fn]])
|
||||
context.variantMap.set(variant, records)
|
||||
}
|
||||
|
||||
if (context.variantMap.has(variant)) {
|
||||
|
||||
@ -17,17 +17,24 @@ export function splitAtTopLevelOnly(input, separator) {
|
||||
let stack = []
|
||||
let parts = []
|
||||
let lastPos = 0
|
||||
let isEscaped = false
|
||||
|
||||
for (let idx = 0; idx < input.length; idx++) {
|
||||
let char = input[idx]
|
||||
|
||||
if (stack.length === 0 && char === separator[0]) {
|
||||
if (stack.length === 0 && char === separator[0] && !isEscaped) {
|
||||
if (separator.length === 1 || input.slice(idx, idx + separator.length) === separator) {
|
||||
parts.push(input.slice(lastPos, idx))
|
||||
lastPos = idx + separator.length
|
||||
}
|
||||
}
|
||||
|
||||
if (isEscaped) {
|
||||
isEscaped = false
|
||||
} else if (char === '\\') {
|
||||
isEscaped = true
|
||||
}
|
||||
|
||||
if (char === '(' || char === '[' || char === '{') {
|
||||
stack.push(char)
|
||||
} else if (
|
||||
|
||||
@ -1116,6 +1116,53 @@ crosscheck(({ stable, oxide }) => {
|
||||
})
|
||||
})
|
||||
|
||||
it('it should discard arbitrary variants with multiple selectors', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: html`
|
||||
<div class="p-1"></div>
|
||||
<div class="[div]:p-1"></div>
|
||||
<div class="[div_&]:p-1"></div>
|
||||
<div class="[div,span]:p-1"></div>
|
||||
<div class="[div_&,span]:p-1"></div>
|
||||
<div class="[div,span_&]:p-1"></div>
|
||||
<div class="[div_&,span_&]:p-1"></div>
|
||||
<div class="hover:[div]:p-1"></div>
|
||||
<div class="hover:[div_&]:p-1"></div>
|
||||
<div class="hover:[div,span]:p-1"></div>
|
||||
<div class="hover:[div_&,span]:p-1"></div>
|
||||
<div class="hover:[div,span_&]:p-1"></div>
|
||||
<div class="hover:[div_&,span_&]:p-1"></div>
|
||||
<div class="hover:[:is(span,div)_&]:p-1"></div>
|
||||
`,
|
||||
},
|
||||
{
|
||||
// escaped commas are a-ok
|
||||
// This is separate because prettier complains about `\,` in the template string
|
||||
raw: '<div class="hover:[.span\\,div_&]:p-1"></div>',
|
||||
},
|
||||
],
|
||||
corePlugins: { preflight: false },
|
||||
}
|
||||
|
||||
let input = css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
return run(input, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
.p-1,
|
||||
.span\,div .hover\:\[\.span\\\,div_\&\]\:p-1:hover,
|
||||
:is(span, div) .hover\:\[\:is\(span\,div\)_\&\]\:p-1:hover,
|
||||
div .\[div_\&\]\:p-1,
|
||||
div .hover\:\[div_\&\]\:p-1:hover {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should sort multiple variant fns with normal variants between them', () => {
|
||||
/** @type {string[]} */
|
||||
let lines = []
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user