Skip to content

Commit e57a2f5

Browse files
Change casing of utilities with named values to kebab-case to match u… (#18017)
Fixes #16156 ## Summary This PR adds a new 3 -> 4 template migration that changes the casing of in both utility values and modifier values from camelCase to kebab-case to match the updated CSS variable names. ## Test plan - Added integration test, see the diff in the PR.
1 parent 4db711d commit e57a2f5

File tree

5 files changed

+93
-3
lines changed

5 files changed

+93
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- `lightningcss` now statically links Visual Studio redistributables ([#17979](https://github.com/tailwindlabs/tailwindcss/pull/17979))
1717
- Ensure that running the Standalone build does not leave temporary files behind ([#17981](https://github.com/tailwindlabs/tailwindcss/pull/17981))
1818
- Fix `-rotate-*` utilities with arbitrary values ([#18014](https://github.com/tailwindlabs/tailwindcss/pull/18014))
19+
- Upgrade: Change casing of utilities with named values to kebab-case to match updated theme variables ([#18017](https://github.com/tailwindlabs/tailwindcss/pull/18017))
1920

2021
### Added
2122

integrations/upgrade/js-config.test.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,13 @@ test(
3030
400: '#f87171',
3131
500: 'red',
3232
},
33+
superRed: '#ff0000',
3334
steel: 'rgb(70 130 180 / <alpha-value>)',
3435
smoke: 'rgba(245, 245, 245, var(--smoke-alpha, <alpha-value>))',
3536
},
37+
opacity: {
38+
superOpaque: '0.95',
39+
},
3640
fontSize: {
3741
xs: ['0.75rem', { lineHeight: '1rem' }],
3842
sm: ['0.875rem', { lineHeight: '1.5rem' }],
@@ -144,9 +148,10 @@ test(
144148
}
145149
`,
146150
'src/index.html': html`
147-
<div
151+
<div
148152
class="[letter-spacing:theme(letterSpacing.superWide)] [line-height:theme(lineHeight.superLoose)]"
149153
></div>
154+
<div class="text-red-superRed/superOpaque leading-superLoose"></div>
150155
`,
151156
'node_modules/my-external-lib/src/template.html': html`
152157
<div class="text-red-500">
@@ -162,8 +167,9 @@ test(
162167
"
163168
--- src/index.html ---
164169
<div
165-
class="[letter-spacing:var(--tracking-super-wide)] [line-height:var(--leading-super-loose)]"
166-
></div>
170+
class="[letter-spacing:var(--tracking-super-wide)] [line-height:var(--leading-super-loose)]"
171+
></div>
172+
<div class="text-red-super-red/super-opaque leading-super-loose"></div>
167173
168174
--- src/input.css ---
169175
@import 'tailwindcss';
@@ -181,9 +187,13 @@ test(
181187
--color-red-500: #ef4444;
182188
--color-red-600: #dc2626;
183189
190+
--color-super-red: #ff0000;
184191
--color-steel: rgb(70 130 180);
185192
--color-smoke: rgba(245, 245, 245, var(--smoke-alpha, 1));
186193
194+
--opacity-*: initial;
195+
--opacity-super-opaque: 95%;
196+
187197
--text-*: initial;
188198
--text-xs: 0.75rem;
189199
--text-xs--line-height: 1rem;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
2+
import { expect, test, vi } from 'vitest'
3+
import * as versions from '../../utils/version'
4+
import { migrateCamelcaseInNamedValue } from './migrate-camelcase-in-named-value'
5+
vi.spyOn(versions, 'isMajor').mockReturnValue(true)
6+
7+
test.each([
8+
['text-superRed', 'text-super-red'],
9+
['text-red/superOpaque', 'text-red/super-opaque'],
10+
['text-superRed/superOpaque', 'text-super-red/super-opaque'],
11+
12+
// Should not migrate named values in modifiers
13+
['group-hover/superGroup:underline', 'group-hover/superGroup:underline'],
14+
15+
['hover:text-superRed', 'hover:text-super-red'],
16+
['hover:text-red/superOpaque', 'hover:text-red/super-opaque'],
17+
['hover:text-superRed/superOpaque', 'hover:text-super-red/super-opaque'],
18+
])('%s => %s', async (candidate, result) => {
19+
let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', {
20+
base: __dirname,
21+
})
22+
23+
expect(migrateCamelcaseInNamedValue(designSystem, {}, candidate)).toEqual(result)
24+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type { Config } from '../../../../tailwindcss/src/compat/plugin-api'
2+
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
3+
import * as version from '../../utils/version'
4+
5+
// Converts named values to use kebab-case. This is necessary because the
6+
// upgrade tool also renames the theme values to kebab-case, so `text-superRed`
7+
// will have its theme value renamed to `--color-super-red` and thus the utility
8+
// will be renamed to `text-super-red`.
9+
export function migrateCamelcaseInNamedValue(
10+
designSystem: DesignSystem,
11+
_userConfig: Config | null,
12+
rawCandidate: string,
13+
): string {
14+
if (!version.isMajor(3)) return rawCandidate
15+
16+
for (let candidate of designSystem.parseCandidate(rawCandidate)) {
17+
if (candidate.kind !== 'functional') continue
18+
let clone = structuredClone(candidate)
19+
let didChange = false
20+
21+
if (
22+
candidate.value &&
23+
clone.value &&
24+
candidate.value.kind === 'named' &&
25+
clone.value.kind === 'named' &&
26+
candidate.value.value.match(/[A-Z]/)
27+
) {
28+
clone.value.value = camelToKebab(candidate.value.value)
29+
didChange = true
30+
}
31+
32+
if (
33+
candidate.modifier &&
34+
clone.modifier &&
35+
candidate.modifier.kind === 'named' &&
36+
clone.modifier.kind === 'named' &&
37+
candidate.modifier.value.match(/[A-Z]/)
38+
) {
39+
clone.modifier.value = camelToKebab(candidate.modifier.value)
40+
didChange = true
41+
}
42+
43+
if (didChange) {
44+
return designSystem.printCandidate(clone)
45+
}
46+
}
47+
48+
return rawCandidate
49+
}
50+
51+
function camelToKebab(str: string): string {
52+
return str.replace(/([A-Z])/g, '-$1').toLowerCase()
53+
}

packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { migrateArbitraryVariants } from './migrate-arbitrary-variants'
1111
import { migrateAutomaticVarInjection } from './migrate-automatic-var-injection'
1212
import { migrateBareValueUtilities } from './migrate-bare-utilities'
1313
import { migrateBgGradient } from './migrate-bg-gradient'
14+
import { migrateCamelcaseInNamedValue } from './migrate-camelcase-in-named-value'
1415
import { migrateDropUnnecessaryDataTypes } from './migrate-drop-unnecessary-data-types'
1516
import { migrateEmptyArbitraryValues } from './migrate-handle-empty-arbitrary-values'
1617
import { migrateImportant } from './migrate-important'
@@ -41,6 +42,7 @@ export const DEFAULT_MIGRATIONS: Migration[] = [
4142
migrateImportant,
4243
migrateBgGradient,
4344
migrateSimpleLegacyClasses,
45+
migrateCamelcaseInNamedValue,
4446
migrateLegacyClasses,
4547
migrateMaxWidthScreen,
4648
migrateThemeToVar,

0 commit comments

Comments
 (0)