diff --git a/CHANGELOG.md b/CHANGELOG.md index ca87e3b8396a..a4b615ca08d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing yet! +### Fixed + +- Show warning when using unsupported bare value data type in `--value(…)` ([#17464](https://github.com/tailwindlabs/tailwindcss/pull/17464)) ## [4.1.2] - 2025-04-03 diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 6544129c4cf7..c0286428f2d7 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from 'vitest' +import { describe, expect, test, vi } from 'vitest' import { compile } from '.' import { compileCss, optimizeCss, run } from './test-utils/run' @@ -26530,6 +26530,34 @@ describe('custom utilities', () => { expect(await compileCss(input, ['tab-foo'])).toEqual('') }) + test('bare values with unsupported data types should result in a warning', async () => { + let spy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + let input = css` + @utility paint-* { + paint: --value([color], color); + } + + @tailwind utilities; + ` + + expect(await compileCss(input, ['paint-#0088cc', 'paint-red'])).toMatchInlineSnapshot(`""`) + expect(spy.mock.calls).toMatchInlineSnapshot(` + [ + [ + "Unsupported bare value data type: "color". + Only valid data types are: "number", "integer", "ratio", "percentage". + ", + ], + [ + "\`\`\`css + --value([color],color) + ^^^^^ + \`\`\`", + ], + ] + `) + }) + test('resolve literal values', async () => { let input = css` @utility tab-* { diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index e2f35382284c..a5254865095b 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -5692,6 +5692,16 @@ export function createUtilities(theme: Theme) { return utilities } +// Only allowed bare value data types, to prevent creating new syntax that we +// typically don't support right now. E.g.: `--value(color)` would allow you to +// use `text-#0088cc` as a valid utility, which is not what we want. +export const BARE_VALUE_DATA_TYPES = [ + 'number', // 2.5 + 'integer', // 8 + 'ratio', // 2/3 + 'percentage', // 25% +] + export function createCssUtility(node: AtRule) { let name = node.params @@ -5824,7 +5834,6 @@ export function createCssUtility(node: AtRule) { } fn.nodes = ValueParser.parse(args.join(',')) - // Track information for suggestions for (let node of fn.nodes) { // Track literal values if ( @@ -5841,6 +5850,36 @@ export function createCssUtility(node: AtRule) { let value = node.value.replace(/-\*.*$/g, '') as `--${string}` storage[fn.value].themeKeys.add(value) } + + // Validate bare value data types + else if ( + node.kind === 'word' && + !(node.value[0] === '[' && node.value[node.value.length - 1] === ']') && // Ignore arbitrary values + !BARE_VALUE_DATA_TYPES.includes(node.value) + ) { + console.warn( + `Unsupported bare value data type: "${node.value}".\nOnly valid data types are: ${BARE_VALUE_DATA_TYPES.map((x) => `"${x}"`).join(', ')}.\n`, + ) + // TODO: Once we properly track the location of the node, we can + // clean this up in a better way. + let dataType = node.value + let copy = structuredClone(fn) + let sentinelValue = '¶' + ValueParser.walk(copy.nodes, (node, { replaceWith }) => { + if (node.kind === 'word' && node.value === dataType) { + replaceWith({ kind: 'word', value: sentinelValue }) + } + }) + let underline = '^'.repeat(ValueParser.toCss([node]).length) + let offset = ValueParser.toCss([copy]).indexOf(sentinelValue) + let output = [ + '```css', + ValueParser.toCss([fn]), + ' '.repeat(offset) + underline, + '```', + ].join('\n') + console.warn(output) + } } }) @@ -6084,12 +6123,7 @@ function resolveValueFunction( // Limit the bare value types, to prevent new syntax that we // don't want to support. E.g.: `text-#000` is something we // don't want to support, but could be built this way. - if ( - arg.value !== 'number' && - arg.value !== 'integer' && - arg.value !== 'ratio' && - arg.value !== 'percentage' - ) { + if (!BARE_VALUE_DATA_TYPES.includes(arg.value)) { continue }