Improve upgrade migrations (#18184)

This PR fixes 2 issues with the migration tool where certain classes
weren't migrated. This PR fixes those 2 scenarios:

### Scenario 1

When you have an arbitrary opacity modifier that doesn't use `%`, but is
just a number typically between `0` and `1` then this was not converted
to the bare value equivalent before.

E.g.:

```html
<div class="bg-[#f00]/[0.16]"></dv>
```

Will now be converted to:

```html
<div class="bg-[#f00]/16"></dv>
```

### Scenario 2

Fixes a bug when a CSS function was used in a fallback value in the CSS
variable shorthand syntax. In that case we didn't migrate the class to
the new syntax.

This was because we assumed that a `(` was found, that we are dealing
with a CSS function.

E.g.: 
```html
<div class="w-[--spacing(1)]"></div>
                        ^  This indicates a CSS function, we should not be 
                           converting this to `w-(--spacing(1))`
```

But if a function was used as a fallback value, for example:

```html
<div class="bg-[--my-color,theme(colors.red.500)]"></dv>
```

Then we also didn't migrate it, but since the function call is in the
fallback, we can still migrate it.

Will now properly be converted to:

```html
<div class="bg-(--my-color,var(--color-red-500))"></dv>
```


## Test plan

1. Added a test for the first case
2. Added a test for the second case
3. Also added an integration-like test that runs all the migration steps
to make sure that the `theme(…)` in the fallback also gets updated to
`var(…)`. This one caught an issue because the `var(…)` wasn't handling
prefixes correctly.
This commit is contained in:
Robin Malfait 2025-05-30 15:32:59 +02:00 • committed by GitHub
parent 3c629dec86
commit 31c0a21452
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 56 additions and 4 deletions

View file

@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
- Nothing yet!
### Fixed
- Upgrade: migrate arbitrary modifiers with values without percentage sign to bare values `/[0.16]` -> `/16` ([#18184](https://github.com/tailwindlabs/tailwindcss/pull/18184))
- Upgrade: migrate CSS variable shorthand if fallback value contains function call ([#18184](https://github.com/tailwindlabs/tailwindcss/pull/18184))
## [4.1.8] - 2025-05-27

View file

@ -40,6 +40,11 @@ test.each([
['w-[--spacing(5)]', 'w-[--spacing(5)]'],
['bg-[--theme(--color-red-500)]', 'bg-[--theme(--color-red-500)]'],
// Fallback values should be included inside the `var(…)` function
['bg-[--my-color,red]', 'bg-(--my-color,red)'],
// Fallback values can contain CSS functions
['bg-[--my-color,theme(spacing.1)]', 'bg-(--my-color,theme(spacing.1))'],
// Some properties never had var() injection in v3.
['[scroll-timeline-name:--myTimeline]', '[scroll-timeline-name:--myTimeline]'],
['[timeline-scope:--myScope]', '[timeline-scope:--myScope]'],

View file

@ -2,6 +2,7 @@ import { walk, WalkAction } from '../../../../tailwindcss/src/ast'
import { type Candidate, type Variant } from '../../../../tailwindcss/src/candidate'
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import * as ValueParser from '../../../../tailwindcss/src/value-parser'
export function migrateAutomaticVarInjection(
designSystem: DesignSystem,
@ -73,9 +74,23 @@ export function migrateAutomaticVarInjection(
function injectVar(value: string): { value: string; didChange: boolean } {
let didChange = false
if (value.startsWith('--') && !value.includes('(')) {
value = `var(${value})`
didChange = true
if (value.startsWith('--')) {
// E.g.:
//
// - `--my-color` → `var(--my-color)` Convert variable
// - `--my-color,red` → `var(--my-color,red)` Convert variable with fallback
// - `--theme(color.red)` → `--theme(color.red)` Do not convert functions
//
if (
// No `(` definitely means there is no function
!value.includes('(') ||
// There could be a function call in the fallback value, but it cannot be
// top-level, so we can safely check the first part
ValueParser.parse(value)[0]?.kind !== 'function'
) {
value = `var(${value})`
didChange = true
}
} else if (value.startsWith(' --')) {
value = value.slice(1)
didChange = true

View file

@ -37,6 +37,9 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
// Use a bare value modifier
['bg-red-500/[25%]', 'bg-red-500/25'],
// Convert 0-1 values to bare values
['bg-[#f00]/[0.16]', 'bg-[#f00]/16'],
// Drop unnecessary modifiers
['bg-red-500/[100%]', 'bg-red-500'],
['bg-red-500/100', 'bg-red-500'],

View file

@ -55,6 +55,23 @@ export function migrateOptimizeModifier(
}
}
// 3. Try to remove the square brackets, but multiply by 100. E.g.: `[0.16]` -> `16`
if (!changed) {
let newModifier: NamedUtilityValue = {
kind: 'named',
value: `${parseFloat(modifier.value) * 100}`,
fraction: null,
}
if (
targetSignature ===
signatures.get(designSystem.printCandidate({ ...candidate, modifier: newModifier }))
) {
changed = true
candidate.modifier = newModifier
}
}
return changed ? designSystem.printCandidate(candidate) : rawCandidate
}
}

View file

@ -193,6 +193,10 @@ export function createConverter(designSystem: DesignSystem, { prettyPrint = fals
let variable = `--${keyPathToCssProperty(toKeyPath(path))}` as const
if (!designSystem.theme.get([variable])) return null
if (designSystem.theme.prefix) {
return `--${designSystem.theme.prefix}-${variable.slice(2)}`
}
return variable
}

View file

@ -64,6 +64,11 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
// non-negative version first so we can replace `-mt-[0px]` with `mt-[0px]`.
['mt-[0px]', 'mt-[0px]'],
['-mt-[0px]', 'mt-[0px]'],
// Shorthand CSS Variables should be converted to the new syntax, even if
// the fallback contains functions. The fallback should also be migrated to
// the newest syntax.
['bg-[--my-color,theme(colors.red.500)]', 'bg-(--my-color,var(--color-red-500))'],
])(testName, async (candidate, result) => {
if (strategy === 'with-variant') {
candidate = `focus:${candidate}`