Detect ambiguity in arbitrary values (#5634)

* detect ambiguity in arbitrary values

* update warning message
This commit is contained in:
Robin Malfait 2021-09-30 12:39:41 +02:00 committed by GitHub
parent ef9187566b
commit 11bfa0a9bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 92 additions and 3 deletions

View File

@ -4,6 +4,7 @@ import parseObjectStyles from '../util/parseObjectStyles'
import isPlainObject from '../util/isPlainObject'
import prefixSelector from '../util/prefixSelector'
import { updateAllClasses } from '../util/pluginUtils'
import log from '../util/log'
let classNameParser = selectorParser((selectors) => {
return selectors.first.filter(({ type }) => type === 'class').pop().value
@ -225,15 +226,19 @@ function* resolveMatches(candidate, context) {
for (let matchedPlugins of resolveMatchedPlugins(classCandidate, context)) {
let matches = []
let typesByMatches = new Map()
let [plugins, modifier] = matchedPlugins
let isOnlyPlugin = plugins.length === 1
for (let [sort, plugin] of plugins) {
let matchesPerPlugin = []
if (typeof plugin === 'function') {
for (let ruleSet of [].concat(plugin(modifier, { isOnlyPlugin }))) {
let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
for (let rule of rules) {
matches.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
}
}
}
@ -242,12 +247,80 @@ function* resolveMatches(candidate, context) {
let ruleSet = plugin
let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
for (let rule of rules) {
matches.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
}
}
if (matchesPerPlugin.length > 0) {
typesByMatches.set(matchesPerPlugin, sort.options?.type)
matches.push(matchesPerPlugin)
}
}
matches = applyPrefix(matches, context)
// Only keep the result of the very first plugin if we are dealing with
// arbitrary values, to protect against ambiguity.
if (isArbitraryValue(modifier) && matches.length > 1) {
let typesPerPlugin = matches.map((match) => new Set([...(typesByMatches.get(match) ?? [])]))
// Remove duplicates, so that we can detect proper unique types for each plugin.
for (let pluginTypes of typesPerPlugin) {
for (let type of pluginTypes) {
let removeFromOwnGroup = false
for (let otherGroup of typesPerPlugin) {
if (pluginTypes === otherGroup) continue
if (otherGroup.has(type)) {
otherGroup.delete(type)
removeFromOwnGroup = true
}
}
if (removeFromOwnGroup) pluginTypes.delete(type)
}
}
let messages = []
for (let [idx, group] of typesPerPlugin.entries()) {
for (let type of group) {
let rules = matches[idx]
.map(([, rule]) => rule)
.flat()
.map((rule) =>
rule
.toString()
.split('\n')
.slice(1, -1) // Remove selector and closing '}'
.map((line) => line.trim())
.map((x) => ` ${x}`) // Re-indent
.join('\n')
)
.join('\n\n')
messages.push(
` - Replace "${candidate}" with "${candidate.replace(
'[',
`[${type}:`
)}" for:\n${rules}\n`
)
break
}
}
log.warn([
// TODO: Update URL
`The class "${candidate}" is ambiguous and matches multiple utilities. Use a type hint (https://tailwindcss.com/docs/just-in-time-mode#ambiguous-values) to fix this.`,
'',
...messages,
`If this is just part of your content and not a class, replace it with "${candidate
.replace('[', '[')
.replace(']', ']')}" to silence this warning.`,
])
continue
}
matches = applyPrefix(matches.flat(), context)
if (important) {
matches = applyImportant(matches, context)
@ -317,4 +390,8 @@ function generateRules(candidates, context) {
})
}
function isArbitraryValue(input) {
return input.startsWith('[') && input.endsWith(']')
}
export { resolveMatches, generateRules }

View File

@ -197,3 +197,15 @@ it('should not convert escaped underscores with spaces', () => {
`)
})
})
it('should warn and not generate if arbitrary values are ambigu', () => {
// If we don't protect against this, then `bg-[200px_100px]` would both
// generate the background-size as well as the background-position utilities.
let config = {
content: [{ raw: html`<div class="bg-[200px_100px]"></div>` }],
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css``)
})
})