From 2fd7c8d9672347120f86bb446785aa2750c0a603 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 4 Apr 2025 14:20:46 +0200 Subject: [PATCH] Show warning when using unsupported bare value data type (#17464) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR will show a warning if you are using a bare value data type that is not supported. Let's say you want to create a new utility that allows `color` to be a bare value data type like this: ```css @utility paint-* { paint: --value([color], color); } ``` This means that this would enable new syntax that we don't support yet. E.g.: `paint-#0088cc`. The only supported data types for bare values are: - `number` — `2.5` - `integer` — `2` - `ratio` — `1/2` - `percentage` — `50%` All other data types are not supported in this position. This PR will now show a warning: ~~~ Unsupported bare value data type: "color". Only valid data types are: "number", "integer", "ratio", "percentage". ```css --value([color],color) ^^^^^ ``` ~~~ Once we have better sourcemap / location tracking support, this warning will point to the exact spot, but for now, only a re-print of the AST can be used. If you _do_ want to use other data types, then you will have to use arbitrary value syntax with `[…]` instead. ```css @utility paint-* { paint: --value([color]); } ``` This will allow for `paint-[#0088cc]` for example. Note: this is not a behavioral change, we already didn't support other data types, but we silently ignored them. This means that we have to do more parsing at runtime when evaluating the utility. With this change, a warning is shown when registering the `@utility`, not when using it. --- CHANGELOG.md | 4 +- packages/tailwindcss/src/utilities.test.ts | 30 +++++++++++++- packages/tailwindcss/src/utilities.ts | 48 ++++++++++++++++++---- 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca87e3b83..a4b615ca0 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 6544129c4..c0286428f 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 e2f353822..a52548650 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 }