Don't output unparsable arbitrary values (#7789)
* Refactor * Don’t output unparsable arbitrary values * Update changelog
This commit is contained in:
parent
c6097d59fc
commit
68d896bbbe
3 changed files with 76 additions and 42 deletions
|
|
@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Only add `!` to selector class matching template candidate when using important modifier with mutli-class selectors ([#7664](https://github.com/tailwindlabs/tailwindcss/pull/7664))
|
||||
- Correctly parse and prefix animation names with dots ([#7163](https://github.com/tailwindlabs/tailwindcss/pull/7163))
|
||||
- Fix extraction from template literal/function with array ([#7481](https://github.com/tailwindlabs/tailwindcss/pull/7481))
|
||||
- Don't output unparsable arbitrary values ([#7789](https://github.com/tailwindlabs/tailwindcss/pull/7789))
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -303,6 +303,19 @@ function looksLikeUri(declaration) {
|
|||
}
|
||||
}
|
||||
|
||||
function isParsableNode(node) {
|
||||
let isParsable = true
|
||||
|
||||
node.walkDecls((decl) => {
|
||||
if (!isParsableCssValue(decl.name, decl.value)) {
|
||||
isParsable = false
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
return isParsable
|
||||
}
|
||||
|
||||
function isParsableCssValue(property, value) {
|
||||
// We don't want to to treat [https://example.com] as a custom property
|
||||
// Even though, according to the CSS grammar, it's a totally valid CSS declaration
|
||||
|
|
@ -456,60 +469,66 @@ function* resolveMatches(candidate, 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) ?? [])]))
|
||||
if (isArbitraryValue(modifier)) {
|
||||
// When generated arbitrary values are ambiguous, we can't know
|
||||
// which to pick so don't generate any utilities for them
|
||||
if (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
|
||||
// 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
|
||||
for (let otherGroup of typesPerPlugin) {
|
||||
if (pluginTypes === otherGroup) continue
|
||||
|
||||
if (otherGroup.has(type)) {
|
||||
otherGroup.delete(type)
|
||||
removeFromOwnGroup = true
|
||||
if (otherGroup.has(type)) {
|
||||
otherGroup.delete(type)
|
||||
removeFromOwnGroup = true
|
||||
}
|
||||
}
|
||||
|
||||
if (removeFromOwnGroup) pluginTypes.delete(type)
|
||||
}
|
||||
|
||||
if (removeFromOwnGroup) pluginTypes.delete(type)
|
||||
}
|
||||
}
|
||||
|
||||
let messages = []
|
||||
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')
|
||||
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(
|
||||
` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``
|
||||
)
|
||||
.join('\n\n')
|
||||
|
||||
messages.push(` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``)
|
||||
break
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
log.warn([
|
||||
`The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
|
||||
...messages,
|
||||
`If this is content and not a class, replace it with \`${candidate
|
||||
.replace('[', '[')
|
||||
.replace(']', ']')}\` to silence this warning.`,
|
||||
])
|
||||
continue
|
||||
}
|
||||
|
||||
log.warn([
|
||||
`The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
|
||||
...messages,
|
||||
`If this is content and not a class, replace it with \`${candidate
|
||||
.replace('[', '[')
|
||||
.replace(']', ']')}\` to silence this warning.`,
|
||||
])
|
||||
continue
|
||||
matches = matches.map((list) => list.filter((match) => isParsableNode(match[1])))
|
||||
}
|
||||
|
||||
matches = matches.flat()
|
||||
|
|
|
|||
|
|
@ -370,3 +370,17 @@ it('should be possible to read theme values in arbitrary values (with quotes) wh
|
|||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should not output unparsable arbitrary CSS values', () => {
|
||||
let config = {
|
||||
content: [
|
||||
{
|
||||
raw: 'let classes = `w-[${sizes.width}]`',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
return expect(result.css).toMatchFormattedCss(``)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue