diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dbab90aa08a..7f909b3a25de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Don't emit generated utilities with invalid uses of theme functions ([#9319](https://github.com/tailwindlabs/tailwindcss/pull/9319)) - Revert change that only listened for stdin close on TTYs ([#9331](https://github.com/tailwindlabs/tailwindcss/pull/9331)) - Ignore unset values (like `null` or `undefined`) when resolving the classList for intellisense ([#9385](https://github.com/tailwindlabs/tailwindcss/pull/9385)) +- Implement fallback plugins when arbitrary values result in css from multiple plugins ([#9376](https://github.com/tailwindlabs/tailwindcss/pull/9376)) ## [3.1.8] - 2022-08-05 diff --git a/src/corePlugins.js b/src/corePlugins.js index 2bd8e9d0357a..3fd4ed99c51a 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -1062,7 +1062,7 @@ export let corePlugins = { } }, }, - { values: theme('divideWidth'), type: ['line-width', 'length'] } + { values: theme('divideWidth'), type: ['line-width', 'length', 'any'] } ) addUtilities({ @@ -1110,7 +1110,7 @@ export let corePlugins = { }, { values: (({ DEFAULT: _, ...colors }) => colors)(flattenColorPalette(theme('divideColor'))), - type: 'color', + type: ['color', 'any'], } ) }, @@ -1290,7 +1290,7 @@ export let corePlugins = { }, { values: (({ DEFAULT: _, ...colors }) => colors)(flattenColorPalette(theme('borderColor'))), - type: ['color'], + type: ['color', 'any'], } ) @@ -1327,7 +1327,7 @@ export let corePlugins = { }, { values: (({ DEFAULT: _, ...colors }) => colors)(flattenColorPalette(theme('borderColor'))), - type: 'color', + type: ['color', 'any'], } ) @@ -1388,7 +1388,7 @@ export let corePlugins = { }, { values: (({ DEFAULT: _, ...colors }) => colors)(flattenColorPalette(theme('borderColor'))), - type: 'color', + type: ['color', 'any'], } ) }, @@ -1414,7 +1414,7 @@ export let corePlugins = { }) }, }, - { values: flattenColorPalette(theme('backgroundColor')), type: 'color' } + { values: flattenColorPalette(theme('backgroundColor')), type: ['color', 'any'] } ) }, @@ -1482,7 +1482,7 @@ export let corePlugins = { }, backgroundSize: createUtilityPlugin('backgroundSize', [['bg', ['background-size']]], { - type: ['lookup', 'length', 'percentage'], + type: ['lookup', ['length', { preferOnConflict: true }], 'percentage'], }), backgroundAttachment: ({ addUtilities }) => { @@ -1543,7 +1543,7 @@ export let corePlugins = { return { stroke: toColorValue(value) } }, }, - { values: flattenColorPalette(theme('stroke')), type: ['color', 'url'] } + { values: flattenColorPalette(theme('stroke')), type: ['color', 'url', 'any'] } ) }, @@ -1654,7 +1654,7 @@ export let corePlugins = { }, fontWeight: createUtilityPlugin('fontWeight', [['font', ['fontWeight']]], { - type: ['lookup', 'number'], + type: ['lookup', 'number', 'any'], }), textTransform: ({ addUtilities }) => { @@ -1750,7 +1750,7 @@ export let corePlugins = { }) }, }, - { values: flattenColorPalette(theme('textColor')), type: 'color' } + { values: flattenColorPalette(theme('textColor')), type: ['color', 'any'] } ) }, @@ -1772,7 +1772,7 @@ export let corePlugins = { return { 'text-decoration-color': toColorValue(value) } }, }, - { values: flattenColorPalette(theme('textDecorationColor')), type: ['color'] } + { values: flattenColorPalette(theme('textDecorationColor')), type: ['color', 'any'] } ) }, @@ -1795,7 +1795,7 @@ export let corePlugins = { textUnderlineOffset: createUtilityPlugin( 'textUnderlineOffset', [['underline-offset', ['text-underline-offset']]], - { type: ['length', 'percentage'] } + { type: ['length', 'percentage', 'any'] } ), fontSmoothing: ({ addUtilities }) => { @@ -1968,7 +1968,7 @@ export let corePlugins = { } }, }, - { values: flattenColorPalette(theme('boxShadowColor')), type: ['color'] } + { values: flattenColorPalette(theme('boxShadowColor')), type: ['color', 'any'] } ) }, @@ -1990,7 +1990,7 @@ export let corePlugins = { }), outlineOffset: createUtilityPlugin('outlineOffset', [['outline-offset', ['outline-offset']]], { - type: ['length', 'number', 'percentage'], + type: ['length', 'number', 'percentage', 'any'], supportsNegativeValues: true, }), @@ -2001,7 +2001,7 @@ export let corePlugins = { return { 'outline-color': toColorValue(value) } }, }, - { values: flattenColorPalette(theme('outlineColor')), type: ['color'] } + { values: flattenColorPalette(theme('outlineColor')), type: ['color', 'any'] } ) }, @@ -2081,7 +2081,7 @@ export let corePlugins = { ([modifier]) => modifier !== 'DEFAULT' ) ), - type: 'color', + type: ['color', 'any'], } ) }, @@ -2108,7 +2108,7 @@ export let corePlugins = { } }, }, - { values: flattenColorPalette(theme('ringOffsetColor')), type: 'color' } + { values: flattenColorPalette(theme('ringOffsetColor')), type: ['color', 'any'] } ) }, diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index c8efcf8245c3..a78557f204da 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -3,7 +3,7 @@ import selectorParser from 'postcss-selector-parser' import parseObjectStyles from '../util/parseObjectStyles' import isPlainObject from '../util/isPlainObject' import prefixSelector from '../util/prefixSelector' -import { updateAllClasses } from '../util/pluginUtils' +import { updateAllClasses, typeMap } from '../util/pluginUtils' import log from '../util/log' import * as sharedState from './sharedState' import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector' @@ -539,68 +539,135 @@ function* resolveMatches(candidate, context, original = candidate) { } if (matchesPerPlugin.length > 0) { - typesByMatches.set(matchesPerPlugin, sort.options?.type) + let matchingTypes = (sort.options?.types ?? []) + .map(({ type }) => type) + // Only track the types for this plugin that resulted in some result + .filter((type) => { + return Boolean( + typeMap[type](modifier, sort.options, { + tailwindConfig: context.tailwindConfig, + }) + ) + }) + + if (matchingTypes.length > 0) { + typesByMatches.set(matchesPerPlugin, matchingTypes) + } + matches.push(matchesPerPlugin) } } 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) ?? [])])) + // Partition plugins in 2 categories so that we can start searching in the plugins that + // don't have `any` as a type first. + let [withAny, withoutAny] = matches.reduce( + (group, plugin) => { + let hasAnyType = plugin.some(([{ options }]) => + options.types.some(({ type }) => type === 'any') + ) - // 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 + if (hasAnyType) { + group[0].push(plugin) + } else { + group[1].push(plugin) + } + return group + }, + [[], []] + ) - for (let otherGroup of typesPerPlugin) { - if (pluginTypes === otherGroup) continue + function findFallback(matches) { + // If only a single plugin matches, let's take that one + if (matches.length === 1) { + return matches[0] + } - if (otherGroup.has(type)) { - otherGroup.delete(type) - removeFromOwnGroup = true + // Otherwise, find the plugin that creates a valid rule given the arbitrary value, and + // also has the correct type which preferOnConflicts the plugin in case of clashes. + return matches.find((rules) => { + let matchingTypes = typesByMatches.get(rules) + return rules.some(([{ options }, rule]) => { + if (!isParsableNode(rule)) { + return false } - } - if (removeFromOwnGroup) pluginTypes.delete(type) - } + return options.types.some( + ({ type, preferOnConflict }) => matchingTypes.includes(type) && preferOnConflict + ) + }) + }) } - 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') + // Try to find a fallback plugin, because we already know that multiple plugins matched for + // the given arbitrary value. + let fallback = findFallback(withoutAny) ?? findFallback(withAny) + if (fallback) { + matches = [fallback] + } - messages.push( - ` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\`` - ) - break + // We couldn't find a fallback plugin which means that there are now multiple plugins that + // generated css for the current candidate. This means that the result is ambiguous and this + // should not happen. We won't generate anything right now, so let's report this to the user + // by logging some options about what they can do. + else { + 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) + } } - } - 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 + 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( + ` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\`` + ) + 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 + } } matches = matches.map((list) => list.filter((match) => isParsableNode(match[1]))) diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index ae88e3a636d7..5a9e8a247e6e 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -30,6 +30,20 @@ function prefix(context, selector) { return typeof prefix === 'function' ? prefix(selector) : prefix + selector } +function normalizeOptionTypes({ type = 'any', ...options }) { + let types = [].concat(type) + + return { + ...options, + types: types.map((type) => { + if (Array.isArray(type)) { + return { type: type[0], ...type[1] } + } + return { type, preferOnConflict: false } + }), + } +} + function parseVariantFormatString(input) { if (input.includes('{')) { if (!isBalanced(input)) throw new Error(`Your { and } are unbalanced.`) @@ -346,7 +360,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs respectImportant: true, } - options = { ...defaultOptions, ...options } + options = normalizeOptionTypes({ ...defaultOptions, ...options }) let offset = offsets.create('utilities') @@ -357,16 +371,24 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs classList.add([prefixedIdentifier, options]) function wrapped(modifier, { isOnlyPlugin }) { - let { type = 'any' } = options - type = [].concat(type) - let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig) + let [value, coercedType] = coerceValue(options.types, modifier, options, tailwindConfig) if (value === undefined) { return [] } - if (!type.includes(coercedType) && !isOnlyPlugin) { - return [] + if (!options.types.some(({ type }) => type === coercedType)) { + if (isOnlyPlugin) { + log.warn([ + `Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`, + `You can safely update it to \`${identifier}-${modifier.replace( + coercedType + ':', + '' + )}\`.`, + ]) + } else { + return [] + } } if (!isValidArbitraryValue(value)) { @@ -398,7 +420,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs respectImportant: false, } - options = { ...defaultOptions, ...options } + options = normalizeOptionTypes({ ...defaultOptions, ...options }) let offset = offsets.create('components') @@ -409,15 +431,13 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs classList.add([prefixedIdentifier, options]) function wrapped(modifier, { isOnlyPlugin }) { - let { type = 'any' } = options - type = [].concat(type) - let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig) + let [value, coercedType] = coerceValue(options.types, modifier, options, tailwindConfig) if (value === undefined) { return [] } - if (!type.includes(coercedType)) { + if (!options.types.some(({ type }) => type === coercedType)) { if (isOnlyPlugin) { log.warn([ `Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`, @@ -738,7 +758,7 @@ function registerPlugins(plugins, context) { ] } - if ([].concat(options?.type).includes('color')) { + if (options.types.some(({ type }) => type === 'color')) { classes = [ ...classes, ...classes.flatMap((cls) => diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index 61a401822bf1..d056a78cee01 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -146,7 +146,7 @@ function guess(validate) { } } -let typeMap = { +export let typeMap = { any: asValue, color: asColor, url: guess(url), @@ -195,7 +195,7 @@ export function coerceValue(types, modifier, options, tailwindConfig) { } // Find first matching type - for (let type of [].concat(types)) { + for (let { type } of types) { let result = typeMap[type](modifier, options, { tailwindConfig }) if (result !== undefined) return [result, type] } diff --git a/tests/any-type.test.js b/tests/any-type.test.js new file mode 100644 index 000000000000..c7ffa8e03643 --- /dev/null +++ b/tests/any-type.test.js @@ -0,0 +1,761 @@ +import { run, html, css } from './util/run' + +// Hi there, so you are debugging this test because something failed... right? Well we can look into +// the future and guessed that this would happen. So basically it means that we (it was probably +// you, silly) introduced a new plugin that conflicts with an existing plugin that has (either +// implicit or explicit) an `any` type. +// +// Now it is your job to decide which one should win, and mark that one with +// ```diff +// - 'any' +// + ['any', { preferOnConflict: true }] +// ``` +// in the corePlugins.js file. +// +// You probably want to let the original one win for backwards compatible reasons. +// +// Good luck! +test('any types are set on correct plugins', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .inset-\[var\(--any-value\)\] { + top: var(--any-value); + right: var(--any-value); + bottom: var(--any-value); + left: var(--any-value); + } + .inset-x-\[var\(--any-value\)\] { + left: var(--any-value); + right: var(--any-value); + } + .inset-y-\[var\(--any-value\)\] { + top: var(--any-value); + bottom: var(--any-value); + } + .top-\[var\(--any-value\)\] { + top: var(--any-value); + } + .right-\[var\(--any-value\)\] { + right: var(--any-value); + } + .bottom-\[var\(--any-value\)\] { + bottom: var(--any-value); + } + .left-\[var\(--any-value\)\] { + left: var(--any-value); + } + .z-\[var\(--any-value\)\] { + z-index: var(--any-value); + } + .order-\[var\(--any-value\)\] { + order: var(--any-value); + } + .col-\[var\(--any-value\)\] { + grid-column: var(--any-value); + } + .col-start-\[var\(--any-value\)\] { + grid-column-start: var(--any-value); + } + .col-end-\[var\(--any-value\)\] { + grid-column-end: var(--any-value); + } + .row-\[var\(--any-value\)\] { + grid-row: var(--any-value); + } + .row-start-\[var\(--any-value\)\] { + grid-row-start: var(--any-value); + } + .row-end-\[var\(--any-value\)\] { + grid-row-end: var(--any-value); + } + .m-\[var\(--any-value\)\] { + margin: var(--any-value); + } + .mx-\[var\(--any-value\)\] { + margin-left: var(--any-value); + margin-right: var(--any-value); + } + .my-\[var\(--any-value\)\] { + margin-top: var(--any-value); + margin-bottom: var(--any-value); + } + .mt-\[var\(--any-value\)\] { + margin-top: var(--any-value); + } + .mr-\[var\(--any-value\)\] { + margin-right: var(--any-value); + } + .mb-\[var\(--any-value\)\] { + margin-bottom: var(--any-value); + } + .ml-\[var\(--any-value\)\] { + margin-left: var(--any-value); + } + .aspect-\[var\(--any-value\)\] { + aspect-ratio: var(--any-value); + } + .h-\[var\(--any-value\)\] { + height: var(--any-value); + } + .max-h-\[var\(--any-value\)\] { + max-height: var(--any-value); + } + .min-h-\[var\(--any-value\)\] { + min-height: var(--any-value); + } + .w-\[var\(--any-value\)\] { + width: var(--any-value); + } + .min-w-\[var\(--any-value\)\] { + min-width: var(--any-value); + } + .max-w-\[var\(--any-value\)\] { + max-width: var(--any-value); + } + .flex-\[var\(--any-value\)\] { + flex: var(--any-value); + } + .flex-shrink-\[var\(--any-value\)\] { + flex-shrink: var(--any-value); + } + .shrink-\[var\(--any-value\)\] { + flex-shrink: var(--any-value); + } + .flex-grow-\[var\(--any-value\)\] { + flex-grow: var(--any-value); + } + .grow-\[var\(--any-value\)\] { + flex-grow: var(--any-value); + } + .basis-\[var\(--any-value\)\] { + flex-basis: var(--any-value); + } + .border-spacing-\[var\(--any-value\)\] { + --tw-border-spacing-x: var(--any-value); + --tw-border-spacing-y: var(--any-value); + border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); + } + .border-spacing-x-\[var\(--any-value\)\] { + --tw-border-spacing-x: var(--any-value); + border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); + } + .border-spacing-y-\[var\(--any-value\)\] { + --tw-border-spacing-y: var(--any-value); + border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); + } + .origin-\[var\(--any-value\)\] { + transform-origin: var(--any-value); + } + .translate-x-\[var\(--any-value\)\] { + --tw-translate-x: var(--any-value); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); + } + .translate-y-\[var\(--any-value\)\] { + --tw-translate-y: var(--any-value); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); + } + .rotate-\[var\(--any-value\)\] { + --tw-rotate: var(--any-value); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); + } + .scale-\[var\(--any-value\)\] { + --tw-scale-x: var(--any-value); + --tw-scale-y: var(--any-value); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); + } + .scale-x-\[var\(--any-value\)\] { + --tw-scale-x: var(--any-value); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); + } + .scale-y-\[var\(--any-value\)\] { + --tw-scale-y: var(--any-value); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); + } + .animate-\[var\(--any-value\)\] { + animation: var(--any-value); + } + .cursor-\[var\(--any-value\)\] { + cursor: var(--any-value); + } + .scroll-m-\[var\(--any-value\)\] { + scroll-margin: var(--any-value); + } + .scroll-mx-\[var\(--any-value\)\] { + scroll-margin-left: var(--any-value); + scroll-margin-right: var(--any-value); + } + .scroll-my-\[var\(--any-value\)\] { + scroll-margin-top: var(--any-value); + scroll-margin-bottom: var(--any-value); + } + .scroll-mt-\[var\(--any-value\)\] { + scroll-margin-top: var(--any-value); + } + .scroll-mr-\[var\(--any-value\)\] { + scroll-margin-right: var(--any-value); + } + .scroll-mb-\[var\(--any-value\)\] { + scroll-margin-bottom: var(--any-value); + } + .scroll-ml-\[var\(--any-value\)\] { + scroll-margin-left: var(--any-value); + } + .scroll-p-\[var\(--any-value\)\] { + scroll-padding: var(--any-value); + } + .scroll-px-\[var\(--any-value\)\] { + scroll-padding-left: var(--any-value); + scroll-padding-right: var(--any-value); + } + .scroll-py-\[var\(--any-value\)\] { + scroll-padding-top: var(--any-value); + scroll-padding-bottom: var(--any-value); + } + .scroll-pt-\[var\(--any-value\)\] { + scroll-padding-top: var(--any-value); + } + .scroll-pr-\[var\(--any-value\)\] { + scroll-padding-right: var(--any-value); + } + .scroll-pb-\[var\(--any-value\)\] { + scroll-padding-bottom: var(--any-value); + } + .scroll-pl-\[var\(--any-value\)\] { + scroll-padding-left: var(--any-value); + } + .list-\[var\(--any-value\)\] { + list-style-type: var(--any-value); + } + .columns-\[var\(--any-value\)\] { + columns: var(--any-value); + } + .auto-cols-\[var\(--any-value\)\] { + grid-auto-columns: var(--any-value); + } + .auto-rows-\[var\(--any-value\)\] { + grid-auto-rows: var(--any-value); + } + .grid-cols-\[var\(--any-value\)\] { + grid-template-columns: var(--any-value); + } + .grid-rows-\[var\(--any-value\)\] { + grid-template-rows: var(--any-value); + } + .gap-\[var\(--any-value\)\] { + gap: var(--any-value); + } + .gap-x-\[var\(--any-value\)\] { + column-gap: var(--any-value); + } + .gap-y-\[var\(--any-value\)\] { + row-gap: var(--any-value); + } + .space-x-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(var(--any-value) * var(--tw-space-x-reverse)); + margin-left: calc(var(--any-value) * calc(1 - var(--tw-space-x-reverse))); + } + .space-y-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(var(--any-value) * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(var(--any-value) * var(--tw-space-y-reverse)); + } + .divide-y-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(var(--any-value) * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(var(--any-value) * var(--tw-divide-y-reverse)); + } + .divide-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { + border-color: var(--any-value); + } + .divide-opacity-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: var(--any-value); + } + .rounded-\[var\(--any-value\)\] { + border-radius: var(--any-value); + } + .rounded-t-\[var\(--any-value\)\] { + border-top-left-radius: var(--any-value); + border-top-right-radius: var(--any-value); + } + .rounded-r-\[var\(--any-value\)\] { + border-top-right-radius: var(--any-value); + border-bottom-right-radius: var(--any-value); + } + .rounded-b-\[var\(--any-value\)\] { + border-bottom-right-radius: var(--any-value); + border-bottom-left-radius: var(--any-value); + } + .rounded-l-\[var\(--any-value\)\] { + border-top-left-radius: var(--any-value); + border-bottom-left-radius: var(--any-value); + } + .rounded-tl-\[var\(--any-value\)\] { + border-top-left-radius: var(--any-value); + } + .rounded-tr-\[var\(--any-value\)\] { + border-top-right-radius: var(--any-value); + } + .rounded-br-\[var\(--any-value\)\] { + border-bottom-right-radius: var(--any-value); + } + .rounded-bl-\[var\(--any-value\)\] { + border-bottom-left-radius: var(--any-value); + } + .border-\[var\(--any-value\)\] { + border-color: var(--any-value); + } + .border-x-\[var\(--any-value\)\] { + border-left-color: var(--any-value); + border-right-color: var(--any-value); + } + .border-y-\[var\(--any-value\)\] { + border-top-color: var(--any-value); + border-bottom-color: var(--any-value); + } + .border-t-\[var\(--any-value\)\] { + border-top-color: var(--any-value); + } + .border-r-\[var\(--any-value\)\] { + border-right-color: var(--any-value); + } + .border-b-\[var\(--any-value\)\] { + border-bottom-color: var(--any-value); + } + .border-l-\[var\(--any-value\)\] { + border-left-color: var(--any-value); + } + .border-opacity-\[var\(--any-value\)\] { + --tw-border-opacity: var(--any-value); + } + .bg-\[var\(--any-value\)\] { + background-color: var(--any-value); + } + .bg-opacity-\[var\(--any-value\)\] { + --tw-bg-opacity: var(--any-value); + } + .from-\[var\(--any-value\)\] { + --tw-gradient-from: var(--any-value); + --tw-gradient-to: rgb(255 255 255 / 0); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + } + .via-\[var\(--any-value\)\] { + --tw-gradient-to: rgb(255 255 255 / 0); + --tw-gradient-stops: var(--tw-gradient-from), var(--any-value), var(--tw-gradient-to); + } + .to-\[var\(--any-value\)\] { + --tw-gradient-to: var(--any-value); + } + .fill-\[var\(--any-value\)\] { + fill: var(--any-value); + } + .stroke-\[var\(--any-value\)\] { + stroke: var(--any-value); + } + .object-\[var\(--any-value\)\] { + object-position: var(--any-value); + } + .p-\[var\(--any-value\)\] { + padding: var(--any-value); + } + .px-\[var\(--any-value\)\] { + padding-left: var(--any-value); + padding-right: var(--any-value); + } + .py-\[var\(--any-value\)\] { + padding-top: var(--any-value); + padding-bottom: var(--any-value); + } + .pt-\[var\(--any-value\)\] { + padding-top: var(--any-value); + } + .pr-\[var\(--any-value\)\] { + padding-right: var(--any-value); + } + .pb-\[var\(--any-value\)\] { + padding-bottom: var(--any-value); + } + .pl-\[var\(--any-value\)\] { + padding-left: var(--any-value); + } + .indent-\[var\(--any-value\)\] { + text-indent: var(--any-value); + } + .align-\[var\(--any-value\)\] { + vertical-align: var(--any-value); + } + .font-\[var\(--any-value\)\] { + font-weight: var(--any-value); + } + .leading-\[var\(--any-value\)\] { + line-height: var(--any-value); + } + .tracking-\[var\(--any-value\)\] { + letter-spacing: var(--any-value); + } + .text-\[var\(--any-value\)\] { + color: var(--any-value); + } + .text-opacity-\[var\(--any-value\)\] { + --tw-text-opacity: var(--any-value); + } + .decoration-\[var\(--any-value\)\] { + text-decoration-color: var(--any-value); + } + .underline-offset-\[var\(--any-value\)\] { + text-underline-offset: var(--any-value); + } + .placeholder-\[var\(--any-value\)\]::placeholder { + color: var(--any-value); + } + .placeholder-opacity-\[var\(--any-value\)\]::placeholder { + --tw-placeholder-opacity: var(--any-value); + } + .caret-\[var\(--any-value\)\] { + caret-color: var(--any-value); + } + .accent-\[var\(--any-value\)\] { + accent-color: var(--any-value); + } + .opacity-\[var\(--any-value\)\] { + opacity: var(--any-value); + } + .shadow-\[var\(--any-value\)\] { + --tw-shadow-color: var(--any-value); + --tw-shadow: var(--tw-shadow-colored); + } + .outline-offset-\[var\(--any-value\)\] { + outline-offset: var(--any-value); + } + .outline-\[var\(--any-value\)\] { + outline-color: var(--any-value); + } + .ring-\[var\(--any-value\)\] { + --tw-ring-color: var(--any-value); + } + .ring-opacity-\[var\(--any-value\)\] { + --tw-ring-opacity: var(--any-value); + } + .ring-offset-\[var\(--any-value\)\] { + --tw-ring-offset-color: var(--any-value); + } + .blur-\[var\(--any-value\)\] { + --tw-blur: blur(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .brightness-\[var\(--any-value\)\] { + --tw-brightness: brightness(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .contrast-\[var\(--any-value\)\] { + --tw-contrast: contrast(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .drop-shadow-\[var\(--any-value\)\] { + --tw-drop-shadow: drop-shadow(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .grayscale-\[var\(--any-value\)\] { + --tw-grayscale: grayscale(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .hue-rotate-\[var\(--any-value\)\] { + --tw-hue-rotate: hue-rotate(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .invert-\[var\(--any-value\)\] { + --tw-invert: invert(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .saturate-\[var\(--any-value\)\] { + --tw-saturate: saturate(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .sepia-\[var\(--any-value\)\] { + --tw-sepia: sepia(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .backdrop-blur-\[var\(--any-value\)\] { + --tw-backdrop-blur: blur(var(--any-value)); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) + var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) + var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) + var(--tw-backdrop-sepia); + } + .backdrop-brightness-\[var\(--any-value\)\] { + --tw-backdrop-brightness: brightness(var(--any-value)); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) + var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) + var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) + var(--tw-backdrop-sepia); + } + .backdrop-contrast-\[var\(--any-value\)\] { + --tw-backdrop-contrast: contrast(var(--any-value)); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) + var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) + var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) + var(--tw-backdrop-sepia); + } + .backdrop-grayscale-\[var\(--any-value\)\] { + --tw-backdrop-grayscale: grayscale(var(--any-value)); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) + var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) + var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) + var(--tw-backdrop-sepia); + } + .backdrop-hue-rotate-\[var\(--any-value\)\] { + --tw-backdrop-hue-rotate: hue-rotate(var(--any-value)); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) + var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) + var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) + var(--tw-backdrop-sepia); + } + .backdrop-invert-\[var\(--any-value\)\] { + --tw-backdrop-invert: invert(var(--any-value)); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) + var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) + var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) + var(--tw-backdrop-sepia); + } + .backdrop-opacity-\[var\(--any-value\)\] { + --tw-backdrop-opacity: opacity(var(--any-value)); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) + var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) + var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) + var(--tw-backdrop-sepia); + } + .backdrop-saturate-\[var\(--any-value\)\] { + --tw-backdrop-saturate: saturate(var(--any-value)); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) + var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) + var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) + var(--tw-backdrop-sepia); + } + .backdrop-sepia-\[var\(--any-value\)\] { + --tw-backdrop-sepia: sepia(var(--any-value)); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) + var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) + var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) + var(--tw-backdrop-sepia); + } + .transition-\[var\(--any-value\)\] { + transition-property: var(--any-value); + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + } + .delay-\[var\(--any-value\)\] { + transition-delay: var(--any-value); + } + .duration-\[var\(--any-value\)\] { + transition-duration: var(--any-value); + } + .ease-\[var\(--any-value\)\] { + transition-timing-function: var(--any-value); + } + .will-change-\[var\(--any-value\)\] { + will-change: var(--any-value); + } + .content-\[var\(--any-value\)\] { + --tw-content: var(--any-value); + content: var(--tw-content); + } + `) + }) +}) diff --git a/tests/arbitrary-values.test.css b/tests/arbitrary-values.test.css index df4e216c9d5e..6968de7258c5 100644 --- a/tests/arbitrary-values.test.css +++ b/tests/arbitrary-values.test.css @@ -499,6 +499,9 @@ --tw-divide-opacity: 1; border-color: rgb(0 0 0 / var(--tw-divide-opacity)); } +.divide-\[var\(--value\)\] > :not([hidden]) ~ :not([hidden]) { + border-color: var(--value); +} .divide-opacity-\[0\.8\] > :not([hidden]) ~ :not([hidden]) { --tw-divide-opacity: 0.8; } @@ -652,9 +655,15 @@ .bg-\[\#0f0_var\(--value\)\] { background-color: #0f0 var(--value); } +.bg-\[var\(--value1\)_var\(--value2\)\] { + background-color: var(--value1) var(--value2); +} .bg-\[color\:var\(--value1\)_var\(--value2\)\] { background-color: var(--value1) var(--value2); } +.bg-\[var\(--value\)\2c var\(--value\)\] { + background-color: var(--value), var(--value); +} .bg-opacity-\[0\.11\] { --tw-bg-opacity: 0.11; } diff --git a/tests/arbitrary-values.test.html b/tests/arbitrary-values.test.html index eb011386437c..083c76def893 100644 --- a/tests/arbitrary-values.test.html +++ b/tests/arbitrary-values.test.html @@ -278,8 +278,6 @@
-
-
diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js index baea4ddef9c5..a4dbf6fe990a 100644 --- a/tests/arbitrary-values.test.js +++ b/tests/arbitrary-values.test.js @@ -262,11 +262,58 @@ it('should not convert escaped underscores with spaces', () => { }) }) -it('should warn and not generate if arbitrary values are ambiguous', () => { - // If we don't protect against this, then `bg-[200px_100px]` would both - // generate the background-size as well as the background-position utilities. +it('should pick the fallback plugin when arbitrary values collide', () => { let config = { - content: [{ raw: html`
` }], + content: [ + { + raw: html` +
+ +
+ +
+
+ `, + }, + ], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .bg-\[var\(--unknown\)\] { + background-color: var(--unknown); + } + + .bg-\[200px_100px\] { + background-size: 200px 100px; + } + `) + }) +}) + +it('should pick the fallback plugin when arbitrary values collide and can not be inferred', () => { + let config = { + content: [{ raw: html`
` }], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .bg-\[var\(--tw-unknown\)\] { + background-color: var(--tw-unknown); + } + `) + }) +}) + +it('should warn and not generate if arbitrary values are ambiguous (without fallback)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [ + function ({ matchUtilities }) { + matchUtilities({ foo: (value) => ({ value }) }, { type: ['position'] }) + matchUtilities({ foo: (value) => ({ value }) }, { type: ['length'] }) + }, + ], } return run('@tailwind utilities', config).then((result) => { diff --git a/tests/evaluateTailwindFunctions.test.js b/tests/evaluateTailwindFunctions.test.js index 8fa950b16e45..ef3a4d1ca184 100644 --- a/tests/evaluateTailwindFunctions.test.js +++ b/tests/evaluateTailwindFunctions.test.js @@ -1268,8 +1268,11 @@ describe('context dependent', () => { `) // 2. But we get a warning in the console - expect(warn).toHaveBeenCalledTimes(1) - expect(warn.mock.calls.map((x) => x[0])).toEqual(['invalid-theme-key-in-class']) + expect(warn).toHaveBeenCalledTimes(2) + expect(warn.mock.calls.map((x) => x[0])).toEqual([ + 'invalid-theme-key-in-class', + 'invalid-theme-key-in-class', + ]) // 3. The second run should work fine because it's been removed from the class cache result = await runFull('@tailwind utilities', configPath) @@ -1281,7 +1284,10 @@ describe('context dependent', () => { `) // 4. But we've not received any further logs about it - expect(warn).toHaveBeenCalledTimes(1) - expect(warn.mock.calls.map((x) => x[0])).toEqual(['invalid-theme-key-in-class']) + expect(warn).toHaveBeenCalledTimes(2) + expect(warn.mock.calls.map((x) => x[0])).toEqual([ + 'invalid-theme-key-in-class', + 'invalid-theme-key-in-class', + ]) }) })