diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts index 2f29c130fb67..152c6ffbd14b 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts @@ -3,6 +3,23 @@ import { expect, test } from 'vitest' import { arbitraryValueToBareValue } from './arbitrary-value-to-bare-value' test.each([ + ['aspect-[12/34]', 'aspect-12/34'], + ['aspect-[1.2/34]', 'aspect-[1.2/34]'], + ['col-start-[7]', 'col-start-7'], + ['flex-[2]', 'flex-2'], // `flex` is implemented as static and functional utilities + + // Only 50-200% (inclusive) are valid: + // https://developer.mozilla.org/en-US/docs/Web/CSS/font-stretch#percentage + ['font-stretch-[50%]', 'font-stretch-50%'], + ['font-stretch-[201%]', 'font-stretch-[201%]'], + ['font-stretch-[49%]', 'font-stretch-[49%]'], + // Should stay as-is + ['font-stretch-[1/2]', 'font-stretch-[1/2]'], + + // This test in itself is a bit flawed because `text-[1/2]` currently + // generates something. Converting it to `text-1/2` doesn't produce anything. + ['text-[1/2]', 'text-[1/2]'], + ['data-[selected]:flex', 'data-selected:flex'], ['data-[foo=bar]:flex', 'data-[foo=bar]:flex'], @@ -22,6 +39,10 @@ test.each([ ['group-has-aria-[selected]:flex', 'group-has-aria-[selected]:flex'], ['max-lg:hover:data-[selected]:flex!', 'max-lg:hover:data-selected:flex!'], + [ + 'data-[selected]:aria-[selected="true"]:aspect-[12/34]', + 'data-selected:aria-selected:aspect-12/34', + ], ])('%s => %s', async (candidate, result) => { let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { base: __dirname, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts b/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts index 065f10807326..bae2cd856dfa 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts @@ -1,6 +1,7 @@ import type { Config } from 'tailwindcss' import { parseCandidate, type Candidate, type Variant } from '../../../../tailwindcss/src/candidate' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { isPositiveInteger } from '../../../../tailwindcss/src/utils/infer-data-type' import { segment } from '../../../../tailwindcss/src/utils/segment' import { printCandidate } from '../candidates' @@ -12,6 +13,74 @@ export function arbitraryValueToBareValue( for (let candidate of parseCandidate(rawCandidate, designSystem)) { let clone = structuredClone(candidate) let changed = false + + // Convert font-stretch-* utilities + if ( + clone.kind === 'functional' && + clone.value?.kind === 'arbitrary' && + clone.value.dataType === null && + clone.root === 'font-stretch' + ) { + if (clone.value.value.endsWith('%')) { + let percentage = parseFloat(clone.value.value) + if (percentage >= 50 && percentage <= 200) { + changed = true + clone.value = { + kind: 'named', + value: clone.value.value, + fraction: null, + } + } + } + } + + // Convert arbitrary values with positive integers to bare values + // Convert arbitrary values with fractions to bare values + else if ( + clone.kind === 'functional' && + clone.value?.kind === 'arbitrary' && + clone.value.dataType === null + ) { + let parts = segment(clone.value.value, '/') + if (parts.every((part) => isPositiveInteger(part))) { + changed = true + + let currentValue = clone.value + let currentModifier = clone.modifier + + // E.g.: `col-start-[12]` + // ^^ + if (parts.length === 1) { + clone.value = { + kind: 'named', + value: clone.value.value, + fraction: null, + } + } + + // E.g.: `aspect-[12/34]` + // ^^ ^^ + else { + clone.value = { + kind: 'named', + value: parts[0], + fraction: clone.value.value, + } + clone.modifier = { + kind: 'named', + value: parts[1], + } + } + + // Double check that the new value compiles correctly + if (designSystem.compileAstNodes(clone).length === 0) { + clone.value = currentValue + clone.modifier = currentModifier + changed = false + } + } + } + for (let variant of variants(clone)) { // Convert `data-[selected]` to `data-selected` if (