From 666337c45befc9b351624ad148a2e1dd5d9be597 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 13 May 2025 13:04:48 +0200 Subject: [PATCH 1/7] allow migrating functional utilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This in theory can convert bare values to named values _if_ there exists a replacement. This is the minimal amount of work required to make that happen. But this also means that there is additional unnecessary work involved, such as: 1. Canonicalizing the value — we bare values don't even include whitespace-like characters so this is unnecessary work. 2. If we can't find a replacement using a named value, we will try arbitrary values... but it's already an arbitrary value. Will tackle these in the next few commits. --- .../src/codemods/template/migrate-arbitrary-utilities.test.ts | 4 ++++ .../src/codemods/template/migrate-arbitrary-utilities.ts | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.test.ts index 6a4c4166c080..2e5f066db8b5 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.test.ts @@ -43,6 +43,7 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', --*: initial; --spacing: 0.25rem; --color-red-500: red; + --aspect-video: 16 / 9; /* Equivalent of blue-500/50 */ --color-primary: color-mix(in oklab, oklch(62.3% 0.214 259.815) 50%, transparent); @@ -114,6 +115,9 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', // Arbitrary percentage value must be a whole number. Should not migrate to // a bare value. ['from-[2.5%]', 'from-[2.5%]'], + + // Bare values to named values + ['aspect-16/9', 'aspect-video'], ])(testName, async (candidate, result) => { if (strategy === 'with-variant') { candidate = `focus:${candidate}` diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts index 2f3ee7e434c7..b9bd28d470be 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts @@ -79,8 +79,8 @@ export function migrateArbitraryUtilities( if ( // Arbitrary property readonlyCandidate.kind !== 'arbitrary' && - // Arbitrary value - !(readonlyCandidate.kind === 'functional' && readonlyCandidate.value?.kind === 'arbitrary') + // Functional utility + readonlyCandidate.kind !== 'functional' ) { continue } From a75205bd7e30f1c17890c777626af74bcf88eb16 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 13 May 2025 13:21:26 +0200 Subject: [PATCH 2/7] move pre computed maps to the `signatures` This will allow us to reuse them later in other migrations, once we split the arbitrary-utilities migration to also handle the bare value to named value migration. --- .../template/migrate-arbitrary-utilities.ts | 37 +------------ .../template/migrate-arbitrary-variants.ts | 23 +------- .../src/codemods/template/signatures.ts | 55 +++++++++++++++++++ 3 files changed, 58 insertions(+), 57 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts index b9bd28d470be..89c36d1309e3 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts @@ -2,45 +2,10 @@ import { printModifier, type Candidate } from '../../../../tailwindcss/src/candi import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' -import { isValidSpacingMultiplier } from '../../../../tailwindcss/src/utils/infer-data-type' import * as ValueParser from '../../../../tailwindcss/src/value-parser' import { dimensions } from '../../utils/dimension' import type { Writable } from '../../utils/types' -import { computeUtilitySignature } from './signatures' - -// For all static utilities in the system, compute a lookup table that maps the -// utility signature to the utility name. This is used to find the utility name -// for a given utility signature. -// -// For all functional utilities, we can compute static-like utilities by -// essentially pre-computing the values and modifiers. This is a bit slow, but -// also only has to happen once per design system. -const preComputedUtilities = new DefaultMap>((ds) => { - let signatures = computeUtilitySignature.get(ds) - let lookup = new DefaultMap(() => []) - - for (let [className, meta] of ds.getClassList()) { - let signature = signatures.get(className) - if (typeof signature !== 'string') continue - lookup.get(signature).push(className) - - for (let modifier of meta.modifiers) { - // Modifiers representing numbers can be computed and don't need to be - // pre-computed. Doing the math and at the time of writing this, this - // would save you 250k additionally pre-computed utilities... - if (isValidSpacingMultiplier(modifier)) { - continue - } - - let classNameWithModifier = `${className}/${modifier}` - let signature = signatures.get(classNameWithModifier) - if (typeof signature !== 'string') continue - lookup.get(signature).push(classNameWithModifier) - } - } - - return lookup -}) +import { computeUtilitySignature, preComputedUtilities } from './signatures' const baseReplacementsCache = new DefaultMap>( () => new Map(), diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts index d2d78931e8a9..a210b03d48c0 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts @@ -1,28 +1,9 @@ import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' import { replaceObject } from '../../utils/replace-object' import type { Writable } from '../../utils/types' import { walkVariants } from '../../utils/walk-variants' -import { computeVariantSignature } from './signatures' - -const variantsLookup = new DefaultMap>( - (designSystem) => { - let signatures = computeVariantSignature.get(designSystem) - let lookup = new DefaultMap(() => []) - - // Actual static variants - for (let [root, variant] of designSystem.variants.entries()) { - if (variant.kind === 'static') { - let signature = signatures.get(root) - if (typeof signature !== 'string') continue - lookup.get(signature).push(root) - } - } - - return lookup - }, -) +import { computeVariantSignature, preComputedVariants } from './signatures' export function migrateArbitraryVariants( designSystem: DesignSystem, @@ -30,7 +11,7 @@ export function migrateArbitraryVariants( rawCandidate: string, ): string { let signatures = computeVariantSignature.get(designSystem) - let variants = variantsLookup.get(designSystem) + let variants = preComputedVariants.get(designSystem) for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) { // We are only interested in the variants diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/signatures.ts b/packages/@tailwindcss-upgrade/src/codemods/template/signatures.ts index f15cf35d4711..aacdfd256c00 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/signatures.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/signatures.ts @@ -5,6 +5,7 @@ import * as SelectorParser from '../../../../tailwindcss/src/compat/selector-par import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { ThemeOptions } from '../../../../tailwindcss/src/theme' import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import { isValidSpacingMultiplier } from '../../../../tailwindcss/src/utils/infer-data-type' import * as ValueParser from '../../../../tailwindcss/src/value-parser' import { dimensions } from '../../utils/dimension' @@ -236,6 +237,42 @@ export const computeUtilitySignature = new DefaultMap< }) }) +// For all static utilities in the system, compute a lookup table that maps the +// utility signature to the utility name. This is used to find the utility name +// for a given utility signature. +// +// For all functional utilities, we can compute static-like utilities by +// essentially pre-computing the values and modifiers. This is a bit slow, but +// also only has to happen once per design system. +export const preComputedUtilities = new DefaultMap>( + (ds) => { + let signatures = computeUtilitySignature.get(ds) + let lookup = new DefaultMap(() => []) + + for (let [className, meta] of ds.getClassList()) { + let signature = signatures.get(className) + if (typeof signature !== 'string') continue + lookup.get(signature).push(className) + + for (let modifier of meta.modifiers) { + // Modifiers representing numbers can be computed and don't need to be + // pre-computed. Doing the math and at the time of writing this, this + // would save you 250k additionally pre-computed utilities... + if (isValidSpacingMultiplier(modifier)) { + continue + } + + let classNameWithModifier = `${className}/${modifier}` + let signature = signatures.get(classNameWithModifier) + if (typeof signature !== 'string') continue + lookup.get(signature).push(classNameWithModifier) + } + } + + return lookup + }, +) + // Given a variant, compute a signature that represents the variant. The // signature will be a normalised form of the generated CSS for the variant, or // a unique symbol if the variant is not valid. The class in the selector will @@ -342,6 +379,24 @@ export const computeVariantSignature = new DefaultMap< }) }) +export const preComputedVariants = new DefaultMap>( + (designSystem) => { + let signatures = computeVariantSignature.get(designSystem) + let lookup = new DefaultMap(() => []) + + // Actual static variants + for (let [root, variant] of designSystem.variants.entries()) { + if (variant.kind === 'static') { + let signature = signatures.get(root) + if (typeof signature !== 'string') continue + lookup.get(signature).push(root) + } + } + + return lookup + }, +) + function temporarilyDisableThemeInline(designSystem: DesignSystem, cb: () => T): T { // Turn off `@theme inline` feature such that `@theme` and `@theme inline` are // considered the same. The biggest motivation for this is referencing From 5526698eb5b125799c05dce51a7f14061e10a749 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 13 May 2025 15:40:36 +0200 Subject: [PATCH 3/7] extract `baseCandidate` --- .../src/codemods/template/candidates.ts | 11 +++++++++++ .../codemods/template/migrate-arbitrary-utilities.ts | 5 ++--- .../src/codemods/template/migrate-legacy-classes.ts | 7 +++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/candidates.ts b/packages/@tailwindcss-upgrade/src/codemods/template/candidates.ts index e4f8c3720f0f..2f4fc35ea50a 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/candidates.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/candidates.ts @@ -1,4 +1,5 @@ import { Scanner } from '@tailwindcss/oxide' +import type { Candidate } from '../../../../tailwindcss/src/candidate' export async function extractRawCandidates( content: string, @@ -13,3 +14,13 @@ export async function extractRawCandidates( } return candidates } + +// Create a basic stripped candidate without variants or important flag +export function baseCandidate(candidate: T) { + let base = structuredClone(candidate) + + base.important = false + base.variants = [] + + return base +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts index 89c36d1309e3..3b03b0c35363 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts @@ -5,6 +5,7 @@ import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' import * as ValueParser from '../../../../tailwindcss/src/value-parser' import { dimensions } from '../../utils/dimension' import type { Writable } from '../../utils/types' +import { baseCandidate } from './candidates' import { computeUtilitySignature, preComputedUtilities } from './signatures' const baseReplacementsCache = new DefaultMap>( @@ -79,9 +80,7 @@ export function migrateArbitraryUtilities( // will re-add those later but they are irrelevant for what we are trying to // do here (and will increase cache hits because we only have to deal with // the base utility, nothing more). - let targetCandidate = structuredClone(candidate) - targetCandidate.important = false - targetCandidate.variants = [] + let targetCandidate = baseCandidate(candidate) let targetCandidateString = designSystem.printCandidate(targetCandidate) if (baseReplacementsCache.get(designSystem).has(targetCandidateString)) { diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts index 20b30fe0c779..4fcb18c2fecd 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts @@ -6,6 +6,7 @@ import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' import * as version from '../../utils/version' +import { baseCandidate } from './candidates' import { isSafeMigration } from './is-safe-migration' const __filename = url.fileURLToPath(import.meta.url) @@ -92,10 +93,8 @@ export async function migrateLegacyClasses( for (let candidate of designSystem.parseCandidate(rawCandidate)) { // Create a base candidate string from the candidate. // E.g.: `hover:blur!` -> `blur` - let baseCandidate = structuredClone(candidate) as Candidate - baseCandidate.variants = [] - baseCandidate.important = false - let baseCandidateString = designSystem.printCandidate(baseCandidate) + let base = baseCandidate(candidate) + let baseCandidateString = designSystem.printCandidate(base) // Find the new base candidate string. `blur` -> `blur-sm` let newBaseCandidateString = LEGACY_CLASS_MAP.get(baseCandidateString) From 83f9a1c4fbe6ca0d2903074b8052f83dd0b1a51c Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 13 May 2025 16:12:16 +0200 Subject: [PATCH 4/7] add `migrateBareValueUtilities` step --- .../template/migrate-bare-utilities.test.ts | 72 ++++++++++ .../template/migrate-bare-utilities.ts | 130 ++++++++++++++++++ .../src/codemods/template/migrate.ts | 2 + 3 files changed, 204 insertions(+) create mode 100644 packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.test.ts create mode 100644 packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.ts diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.test.ts new file mode 100644 index 000000000000..2a75b9208a43 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.test.ts @@ -0,0 +1,72 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { describe, expect, test } from 'vitest' +import type { UserConfig } from '../../../../tailwindcss/src/compat/config/types' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import { migrateBareValueUtilities } from './migrate-bare-utilities' + +const css = String.raw + +const designSystems = new DefaultMap((base: string) => { + return new DefaultMap((input: string) => { + return __unstable__loadDesignSystem(input, { base }) + }) +}) + +function migrate(designSystem: DesignSystem, userConfig: UserConfig | null, rawCandidate: string) { + for (let migration of [migrateBareValueUtilities]) { + rawCandidate = migration(designSystem, userConfig, rawCandidate) + } + return rawCandidate +} + +describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', (strategy) => { + let testName = '%s => %s (%#)' + if (strategy === 'with-variant') { + testName = testName.replaceAll('%s', 'focus:%s') + } else if (strategy === 'important') { + testName = testName.replaceAll('%s', '%s!') + } else if (strategy === 'prefix') { + testName = testName.replaceAll('%s', 'tw:%s') + } + + // Basic input with minimal design system to keep the tests fast + let input = css` + @import 'tailwindcss' ${strategy === 'prefix' ? 'prefix(tw)' : ''}; + @theme { + --*: initial; + --spacing: 0.25rem; + --aspect-video: 16 / 9; + --tab-size-github: 8; + } + + @utility tab-* { + tab-size: --value(--tab-size, integer); + } + ` + + test.each([ + // Built-in utility with bare value fraction + ['aspect-16/9', 'aspect-video'], + + // Custom utility with bare value integer + ['tab-8', 'tab-github'], + ])(testName, async (candidate, result) => { + if (strategy === 'with-variant') { + candidate = `focus:${candidate}` + result = `focus:${result}` + } else if (strategy === 'important') { + candidate = `${candidate}!` + result = `${result}!` + } else if (strategy === 'prefix') { + // Not only do we need to prefix the candidate, we also have to make + // sure that we prefix all CSS variables. + candidate = `tw:${candidate.replaceAll('var(--', 'var(--tw-')}` + result = `tw:${result.replaceAll('var(--', 'var(--tw-')}` + } + + let designSystem = await designSystems.get(__dirname).get(input) + let migrated = migrate(designSystem, {}, candidate) + expect(migrated).toEqual(result) + }) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.ts new file mode 100644 index 000000000000..c429d387c958 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.ts @@ -0,0 +1,130 @@ +import { type Candidate } from '../../../../tailwindcss/src/candidate' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import type { Writable } from '../../utils/types' +import { baseCandidate } from './candidates' +import { computeUtilitySignature, preComputedUtilities } from './signatures' + +const baseReplacementsCache = new DefaultMap>( + () => new Map(), +) + +export function migrateBareValueUtilities( + designSystem: DesignSystem, + _userConfig: Config | null, + rawCandidate: string, +): string { + let utilities = preComputedUtilities.get(designSystem) + let signatures = computeUtilitySignature.get(designSystem) + + for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) { + // We are only interested in bare value utilities + if (readonlyCandidate.kind !== 'functional' || readonlyCandidate.value?.kind !== 'named') { + continue + } + + // The below logic makes use of mutation. Since candidates in the + // DesignSystem are cached, we can't mutate them directly. + let candidate = structuredClone(readonlyCandidate) as Writable + + // Create a basic stripped candidate without variants or important flag. We + // will re-add those later but they are irrelevant for what we are trying to + // do here (and will increase cache hits because we only have to deal with + // the base utility, nothing more). + let targetCandidate = baseCandidate(candidate) + + let targetCandidateString = designSystem.printCandidate(targetCandidate) + if (baseReplacementsCache.get(designSystem).has(targetCandidateString)) { + let target = structuredClone( + baseReplacementsCache.get(designSystem).get(targetCandidateString)!, + ) + // Re-add the variants and important flag from the original candidate + target.variants = candidate.variants + target.important = candidate.important + + return designSystem.printCandidate(target) + } + + // Compute the signature for the target candidate + let targetSignature = signatures.get(targetCandidateString) + if (typeof targetSignature !== 'string') continue + + // Try a few options to find a suitable replacement utility + for (let replacementCandidate of tryReplacements(targetSignature, targetCandidate)) { + let replacementString = designSystem.printCandidate(replacementCandidate) + let replacementSignature = signatures.get(replacementString) + if (replacementSignature !== targetSignature) { + continue + } + + replacementCandidate = structuredClone(replacementCandidate) + + // Cache the result so we can re-use this work later + baseReplacementsCache.get(designSystem).set(targetCandidateString, replacementCandidate) + + // Re-add the variants and important flag from the original candidate + replacementCandidate.variants = candidate.variants + replacementCandidate.important = candidate.important + + // Update the candidate with the new value + Object.assign(candidate, replacementCandidate) + + // We will re-print the candidate to get the migrated candidate out + return designSystem.printCandidate(candidate) + } + } + + return rawCandidate + + function* tryReplacements( + targetSignature: string, + candidate: Extract, + ): Generator { + // Find a corresponding utility for the same signature + let replacements = utilities.get(targetSignature) + + // Multiple utilities can map to the same signature. Not sure how to migrate + // this one so let's just skip it for now. + // + // TODO: Do we just migrate to the first one? + if (replacements.length > 1) return + + // If we didn't find any replacement utilities, let's try to strip the + // modifier and find a replacement then. If we do, we can try to re-add the + // modifier later and verify if we have a valid migration. + // + // This is necessary because `text-red-500/50` will not be pre-computed, + // only `text-red-500` will. + if (replacements.length === 0 && candidate.modifier) { + let candidateWithoutModifier = { ...candidate, modifier: null } + let targetSignatureWithoutModifier = signatures.get( + designSystem.printCandidate(candidateWithoutModifier), + ) + if (typeof targetSignatureWithoutModifier === 'string') { + for (let replacementCandidate of tryReplacements( + targetSignatureWithoutModifier, + candidateWithoutModifier, + )) { + yield Object.assign({}, replacementCandidate, { modifier: candidate.modifier }) + } + } + } + + // If only a single utility maps to the signature, we can use that as the + // replacement. + if (replacements.length === 1) { + for (let replacementCandidate of parseCandidate(designSystem, replacements[0])) { + yield replacementCandidate + } + } + } +} + +function parseCandidate(designSystem: DesignSystem, input: string) { + return designSystem.parseCandidate( + designSystem.theme.prefix && !input.startsWith(`${designSystem.theme.prefix}:`) + ? `${designSystem.theme.prefix}:${input}` + : input, + ) +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts index 029a2a97bd98..594342a3f58f 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts @@ -9,6 +9,7 @@ import { migrateArbitraryUtilities } from './migrate-arbitrary-utilities' import { migrateArbitraryValueToBareValue } from './migrate-arbitrary-value-to-bare-value' import { migrateArbitraryVariants } from './migrate-arbitrary-variants' import { migrateAutomaticVarInjection } from './migrate-automatic-var-injection' +import { migrateBareValueUtilities } from './migrate-bare-utilities' import { migrateBgGradient } from './migrate-bg-gradient' import { migrateDropUnnecessaryDataTypes } from './migrate-drop-unnecessary-data-types' import { migrateEmptyArbitraryValues } from './migrate-handle-empty-arbitrary-values' @@ -47,6 +48,7 @@ export const DEFAULT_MIGRATIONS: Migration[] = [ migrateAutomaticVarInjection, migrateLegacyArbitraryValues, migrateArbitraryUtilities, + migrateBareValueUtilities, migrateModernizeArbitraryValues, migrateArbitraryVariants, migrateDropUnnecessaryDataTypes, From 9591df7cadf72afe935c1627643082b53894f1ef Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 13 May 2025 16:13:11 +0200 Subject: [PATCH 5/7] remove bare value handling from arbitrary migration This is now moved to its own dedicated migration file --- .../src/codemods/template/migrate-arbitrary-utilities.test.ts | 4 ---- .../src/codemods/template/migrate-arbitrary-utilities.ts | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.test.ts index 2e5f066db8b5..6a4c4166c080 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.test.ts @@ -43,7 +43,6 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', --*: initial; --spacing: 0.25rem; --color-red-500: red; - --aspect-video: 16 / 9; /* Equivalent of blue-500/50 */ --color-primary: color-mix(in oklab, oklch(62.3% 0.214 259.815) 50%, transparent); @@ -115,9 +114,6 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', // Arbitrary percentage value must be a whole number. Should not migrate to // a bare value. ['from-[2.5%]', 'from-[2.5%]'], - - // Bare values to named values - ['aspect-16/9', 'aspect-video'], ])(testName, async (candidate, result) => { if (strategy === 'with-variant') { candidate = `focus:${candidate}` diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts index 3b03b0c35363..17c538e4d40e 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts @@ -45,8 +45,8 @@ export function migrateArbitraryUtilities( if ( // Arbitrary property readonlyCandidate.kind !== 'arbitrary' && - // Functional utility - readonlyCandidate.kind !== 'functional' + // Arbitrary value + !(readonlyCandidate.kind === 'functional' && readonlyCandidate.value?.kind === 'arbitrary') ) { continue } From 61eba773fb11cf6a8070b7c2ecc9c189eb20ddaa Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 13 May 2025 16:23:00 +0200 Subject: [PATCH 6/7] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40c85b5c38d9..7648d7fa4597 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `lightningcss` now statically links Visual Studio redistributables ([#17979](https://github.com/tailwindlabs/tailwindcss/pull/17979)) - Ensure that running the Standalone build does not leave temporary files behind ([#17981](https://github.com/tailwindlabs/tailwindcss/pull/17981)) +### Added + +- Upgrade: Migrate bare values to named values ([#18000](https://github.com/tailwindlabs/tailwindcss/pull/18000)) + ## [4.1.6] - 2025-05-09 ### Added From 98a7483e5373d13e091b3d68e6e87475370624ee Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 13 May 2025 17:14:41 +0200 Subject: [PATCH 7/7] move prefix-aware `parseCandidate` to `candidates.ts` file --- .../src/codemods/template/candidates.ts | 9 +++++++++ .../codemods/template/migrate-arbitrary-utilities.ts | 10 +--------- .../src/codemods/template/migrate-bare-utilities.ts | 10 +--------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/candidates.ts b/packages/@tailwindcss-upgrade/src/codemods/template/candidates.ts index 2f4fc35ea50a..e33c67e18404 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/candidates.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/candidates.ts @@ -1,5 +1,6 @@ import { Scanner } from '@tailwindcss/oxide' import type { Candidate } from '../../../../tailwindcss/src/candidate' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' export async function extractRawCandidates( content: string, @@ -24,3 +25,11 @@ export function baseCandidate(candidate: T) { return base } + +export function parseCandidate(designSystem: DesignSystem, input: string) { + return designSystem.parseCandidate( + designSystem.theme.prefix && !input.startsWith(`${designSystem.theme.prefix}:`) + ? `${designSystem.theme.prefix}:${input}` + : input, + ) +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts index 17c538e4d40e..1e71dbf930c9 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts @@ -5,7 +5,7 @@ import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' import * as ValueParser from '../../../../tailwindcss/src/value-parser' import { dimensions } from '../../utils/dimension' import type { Writable } from '../../utils/types' -import { baseCandidate } from './candidates' +import { baseCandidate, parseCandidate } from './candidates' import { computeUtilitySignature, preComputedUtilities } from './signatures' const baseReplacementsCache = new DefaultMap>( @@ -239,14 +239,6 @@ export function migrateArbitraryUtilities( } } -function parseCandidate(designSystem: DesignSystem, input: string) { - return designSystem.parseCandidate( - designSystem.theme.prefix && !input.startsWith(`${designSystem.theme.prefix}:`) - ? `${designSystem.theme.prefix}:${input}` - : input, - ) -} - // Let's make sure that all variables used in the value are also all used in the // found replacement. If not, then we are dealing with a different namespace or // we could lose functionality in case the variable was changed higher up in the diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.ts index c429d387c958..475720191ae6 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bare-utilities.ts @@ -3,7 +3,7 @@ import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' import type { Writable } from '../../utils/types' -import { baseCandidate } from './candidates' +import { baseCandidate, parseCandidate } from './candidates' import { computeUtilitySignature, preComputedUtilities } from './signatures' const baseReplacementsCache = new DefaultMap>( @@ -120,11 +120,3 @@ export function migrateBareValueUtilities( } } } - -function parseCandidate(designSystem: DesignSystem, input: string) { - return designSystem.parseCandidate( - designSystem.theme.prefix && !input.startsWith(`${designSystem.theme.prefix}:`) - ? `${designSystem.theme.prefix}:${input}` - : input, - ) -}