Reject invalid custom and arbitrary variants (#8345)

* WIP

Still need to write error message

* Update error message

first pass at something better

* Detect invalid variant formats returned by functions

* Add proper error message

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This commit is contained in:
Adam Wathan 2022-05-14 13:58:02 -04:00 committed by GitHub
parent e41bf3d2a7
commit 7fa2a200b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 2 deletions

View File

@ -9,7 +9,7 @@ import * as sharedState from './sharedState'
import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
import { asClass } from '../util/nameClass'
import { normalize } from '../util/dataTypes'
import { parseVariant } from './setupContextUtils'
import { isValidVariantFormatString, parseVariant } from './setupContextUtils'
import isValidArbitraryValue from '../util/isValidArbitraryValue'
import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js'
@ -131,6 +131,10 @@ function applyVariant(variant, matches, context) {
if (isArbitraryValue(variant) && !context.variantMap.has(variant)) {
let selector = normalize(variant.slice(1, -1))
if (!isValidVariantFormatString(selector)) {
return []
}
let fn = parseVariant(selector)
let sort = Array.from(context.variantOrder.values()).pop() << 1n

View File

@ -170,6 +170,10 @@ function withIdentifiers(styles) {
})
}
export function isValidVariantFormatString(format) {
return format.startsWith('@') || format.includes('&')
}
export function parseVariant(variant) {
variant = variant
.replace(/\n+/g, '')
@ -221,10 +225,24 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
if (typeof variantFunction !== 'string') {
// Safelist public API functions
return ({ modifySelectors, container, separator }) => {
return variantFunction({ modifySelectors, container, separator })
let result = variantFunction({ modifySelectors, container, separator })
if (typeof result === 'string' && !isValidVariantFormatString(result)) {
throw new Error(
`Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
)
}
return result
}
}
if (!isValidVariantFormatString(variantFunction)) {
throw new Error(
`Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
)
}
return parseVariant(variantFunction)
})

View File

@ -77,6 +77,34 @@ test('arbitrary variants with modifiers', () => {
})
})
test('variants without & or an at-rule are ignored', () => {
let config = {
content: [
{
raw: html`
<div class="[div]:underline"></div>
<div class="[:hover]:underline"></div>
<div class="[wtf-bbq]:underline"></div>
<div class="[lol]:hover:underline"></div>
`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
`)
})
})
test('arbitrary variants are sorted after other variants', () => {
let config = {
content: [{ raw: html`<div class="[&>*]:underline underline lg:underline"></div>` }],

View File

@ -206,6 +206,44 @@ describe('custom advanced variants', () => {
`)
})
})
test('variant format string must include at-rule or & (1)', async () => {
let config = {
content: [
{
raw: html` <div class="wtf-bbq:text-center"></div> `,
},
],
plugins: [
function ({ addVariant }) {
addVariant('wtf-bbq', 'lol')
},
],
}
await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError(
"Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder."
)
})
test('variant format string must include at-rule or & (2)', async () => {
let config = {
content: [
{
raw: html` <div class="wtf-bbq:text-center"></div> `,
},
],
plugins: [
function ({ addVariant }) {
addVariant('wtf-bbq', () => 'lol')
},
],
}
await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError(
"Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder."
)
})
})
test('stacked peer variants', async () => {