Skip to content

Commit 2b13547

Browse files
authored
Migrate arbitrary values to bare values (#14669)
This PR adds a codemod that can convert arbitrary values to the cleaner bare values if we can. For example, some classes use arbitrary values such as `col-start-[16]`, but in v4 we have bare values for some plugins that don't really need to adhere to your design system. In this case, we can convert `col-start-[16]` to just `col-start-16`. Another use case is for utilities that use fractions. For example the `aspect-*` plugin. A custom aspect ratio such as `aspect-[16/9]` can be converted to `aspect-16/9`. There are some rules attached to this migration: 1. We can only migrate arbitrary values that is a single positive integer, or an arbitrary value that is a fraction where the numerator and denominator are both positive integers. 2. We make sure that some CSS can be generated once its converted to a bare value.
1 parent 36acad0 commit 2b13547

File tree

3 files changed

+92
-0
lines changed

3 files changed

+92
-0
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- _Upgrade (experimental)_: Migrate `@media screen(…)` when running codemods ([#14603](https://github.com/tailwindlabs/tailwindcss/pull/14603))
2121
- _Upgrade (experimental)_: Inject `@config "…"` when a `tailwind.config.{js,ts,…}` is detected ([#14635](https://github.com/tailwindlabs/tailwindcss/pull/14635))
2222
- _Upgrade (experimental)_: Migrate `aria-*`, `data-*`, and `supports-*` variants from arbitrary values to bare values ([#14644](https://github.com/tailwindlabs/tailwindcss/pull/14644))
23+
- _Upgrade (experimental)_: Migrate arbitrary values to bare values ([#14669](https://github.com/tailwindlabs/tailwindcss/pull/14669))
2324

2425
### Fixed
2526

Diff for: packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@ import { expect, test } from 'vitest'
33
import { arbitraryValueToBareValue } from './arbitrary-value-to-bare-value'
44

55
test.each([
6+
['aspect-[12/34]', 'aspect-12/34'],
7+
['aspect-[1.2/34]', 'aspect-[1.2/34]'],
8+
['col-start-[7]', 'col-start-7'],
9+
['flex-[2]', 'flex-2'], // `flex` is implemented as static and functional utilities
10+
11+
// Only 50-200% (inclusive) are valid:
12+
// https://developer.mozilla.org/en-US/docs/Web/CSS/font-stretch#percentage
13+
['font-stretch-[50%]', 'font-stretch-50%'],
14+
['font-stretch-[50.5%]', 'font-stretch-[50.5%]'],
15+
['font-stretch-[201%]', 'font-stretch-[201%]'],
16+
['font-stretch-[49%]', 'font-stretch-[49%]'],
17+
// Should stay as-is
18+
['font-stretch-[1/2]', 'font-stretch-[1/2]'],
19+
20+
// This test in itself is a bit flawed because `text-[1/2]` currently
21+
// generates something. Converting it to `text-1/2` doesn't produce anything.
22+
['text-[1/2]', 'text-[1/2]'],
23+
624
['data-[selected]:flex', 'data-selected:flex'],
725
['data-[foo=bar]:flex', 'data-[foo=bar]:flex'],
826

@@ -22,6 +40,10 @@ test.each([
2240
['group-has-aria-[selected]:flex', 'group-has-aria-[selected]:flex'],
2341

2442
['max-lg:hover:data-[selected]:flex!', 'max-lg:hover:data-selected:flex!'],
43+
[
44+
'data-[selected]:aria-[selected="true"]:aspect-[12/34]',
45+
'data-selected:aria-selected:aspect-12/34',
46+
],
2547
])('%s => %s', async (candidate, result) => {
2648
let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', {
2749
base: __dirname,

Diff for: packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts

+69
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Config } from 'tailwindcss'
22
import { parseCandidate, type Candidate, type Variant } from '../../../../tailwindcss/src/candidate'
33
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
4+
import { isPositiveInteger } from '../../../../tailwindcss/src/utils/infer-data-type'
45
import { segment } from '../../../../tailwindcss/src/utils/segment'
56
import { printCandidate } from '../candidates'
67

@@ -12,6 +13,74 @@ export function arbitraryValueToBareValue(
1213
for (let candidate of parseCandidate(rawCandidate, designSystem)) {
1314
let clone = structuredClone(candidate)
1415
let changed = false
16+
17+
// Convert font-stretch-* utilities
18+
if (
19+
clone.kind === 'functional' &&
20+
clone.value?.kind === 'arbitrary' &&
21+
clone.value.dataType === null &&
22+
clone.root === 'font-stretch'
23+
) {
24+
if (clone.value.value.endsWith('%') && isPositiveInteger(clone.value.value.slice(0, -1))) {
25+
let percentage = parseInt(clone.value.value)
26+
if (percentage >= 50 && percentage <= 200) {
27+
changed = true
28+
clone.value = {
29+
kind: 'named',
30+
value: clone.value.value,
31+
fraction: null,
32+
}
33+
}
34+
}
35+
}
36+
37+
// Convert arbitrary values with positive integers to bare values
38+
// Convert arbitrary values with fractions to bare values
39+
else if (
40+
clone.kind === 'functional' &&
41+
clone.value?.kind === 'arbitrary' &&
42+
clone.value.dataType === null
43+
) {
44+
let parts = segment(clone.value.value, '/')
45+
if (parts.every((part) => isPositiveInteger(part))) {
46+
changed = true
47+
48+
let currentValue = clone.value
49+
let currentModifier = clone.modifier
50+
51+
// E.g.: `col-start-[12]`
52+
// ^^
53+
if (parts.length === 1) {
54+
clone.value = {
55+
kind: 'named',
56+
value: clone.value.value,
57+
fraction: null,
58+
}
59+
}
60+
61+
// E.g.: `aspect-[12/34]`
62+
// ^^ ^^
63+
else {
64+
clone.value = {
65+
kind: 'named',
66+
value: parts[0],
67+
fraction: clone.value.value,
68+
}
69+
clone.modifier = {
70+
kind: 'named',
71+
value: parts[1],
72+
}
73+
}
74+
75+
// Double check that the new value compiles correctly
76+
if (designSystem.compileAstNodes(clone).length === 0) {
77+
clone.value = currentValue
78+
clone.modifier = currentModifier
79+
changed = false
80+
}
81+
}
82+
}
83+
1584
for (let variant of variants(clone)) {
1685
// Convert `data-[selected]` to `data-selected`
1786
if (

0 commit comments

Comments
 (0)