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:
Jordan Pittman 2023-02-22 14:44:54 -05:00 committed by GitHub
parent d6121f0ede
commit 9bbdd9b10d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 6 deletions

View File

@ -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))

View File

@ -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)) {

View File

@ -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 (

View File

@ -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 = []