mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
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:
parent
e41bf3d2a7
commit
7fa2a200b2
@ -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
|
||||
|
||||
@ -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)
|
||||
})
|
||||
|
||||
|
||||
@ -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>` }],
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user