Skip to content

Commit 622c4b6

Browse files
committed
refactor: apply proper migrations on existing utilities in the system
Instead of creating an ad-hoc static utility for the candidate, we simplify the candidate to it's base form, then perform the change and then go back. E.g.: 1. Incoming string of `hover:blur!` 2. Is turned into the candidate AST for `hover:blur!` 3. Is simplified such that variants and important is stripped 4. Is printed as a string `blur` 5. Find replacement `blur-sm` 6. Parse into candidate AST 7. Re-apply the variants and important 8. Stringified `hover:blur-sm!`
1 parent 20031cf commit 622c4b6

1 file changed

Lines changed: 110 additions & 77 deletions

File tree

packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.ts

Lines changed: 110 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -11,43 +11,43 @@ import { isSafeMigration } from '../is-safe-migration'
1111
const __filename = url.fileURLToPath(import.meta.url)
1212
const __dirname = path.dirname(__filename)
1313

14-
const LEGACY_CLASS_MAP = {
15-
shadow: 'shadow-sm',
16-
'shadow-sm': 'shadow-xs',
17-
'shadow-xs': 'shadow-2xs',
14+
const LEGACY_CLASS_MAP = new Map([
15+
['shadow', 'shadow-sm'],
16+
['shadow-sm', 'shadow-xs'],
17+
['shadow-xs', 'shadow-2xs'],
1818

19-
'inset-shadow': 'inset-shadow-sm',
20-
'inset-shadow-sm': 'inset-shadow-xs',
21-
'inset-shadow-xs': 'inset-shadow-2xs',
19+
['inset-shadow', 'inset-shadow-sm'],
20+
['inset-shadow-sm', 'inset-shadow-xs'],
21+
['inset-shadow-xs', 'inset-shadow-2xs'],
2222

23-
'drop-shadow': 'drop-shadow-sm',
24-
'drop-shadow-sm': 'drop-shadow-xs',
23+
['drop-shadow', 'drop-shadow-sm'],
24+
['drop-shadow-sm', 'drop-shadow-xs'],
2525

26-
rounded: 'rounded-sm',
27-
'rounded-sm': 'rounded-xs',
26+
['rounded', 'rounded-sm'],
27+
['rounded-sm', 'rounded-xs'],
2828

29-
blur: 'blur-sm',
30-
'blur-sm': 'blur-xs',
31-
}
29+
['blur', 'blur-sm'],
30+
['blur-sm', 'blur-xs'],
31+
])
3232

33-
const THEME_KEYS = {
34-
shadow: '--shadow',
35-
'shadow-sm': '--shadow-sm',
36-
'shadow-xs': '--shadow-xs',
37-
'shadow-2xs': '--shadow-2xs',
33+
const THEME_KEYS = new Map([
34+
['shadow', '--shadow'],
35+
['shadow-sm', '--shadow-sm'],
36+
['shadow-xs', '--shadow-xs'],
37+
['shadow-2xs', '--shadow-2xs'],
3838

39-
'drop-shadow': '--drop-shadow',
40-
'drop-shadow-sm': '--drop-shadow-sm',
41-
'drop-shadow-xs': '--drop-shadow-xs',
39+
['drop-shadow', '--drop-shadow'],
40+
['drop-shadow-sm', '--drop-shadow-sm'],
41+
['drop-shadow-xs', '--drop-shadow-xs'],
4242

43-
rounded: '--radius',
44-
'rounded-sm': '--radius-sm',
45-
'rounded-xs': '--radius-xs',
43+
['rounded', '--radius'],
44+
['rounded-sm', '--radius-sm'],
45+
['rounded-xs', '--radius-xs'],
4646

47-
blur: '--blur',
48-
'blur-sm': '--blur-sm',
49-
'blur-xs': '--blur-xs',
50-
}
47+
['blur', '--blur'],
48+
['blur-sm', '--blur-sm'],
49+
['blur-xs', '--blur-xs'],
50+
])
5151

5252
const DESIGN_SYSTEMS = new DefaultMap((base) => {
5353
return __unstable__loadDesignSystem('@import "tailwindcss";', { base })
@@ -65,58 +65,91 @@ export async function legacyClasses(
6565
): Promise<string> {
6666
let defaultDesignSystem = await DESIGN_SYSTEMS.get(__dirname)
6767

68-
for (let candidate of designSystem.parseCandidate(rawCandidate)) {
69-
if (candidate.kind === 'functional') {
70-
let parts = [candidate.root]
71-
if (candidate.value?.kind === 'named') {
72-
parts.push(candidate.value.value)
68+
function* migrate(rawCandidate: string) {
69+
for (let candidate of designSystem.parseCandidate(rawCandidate)) {
70+
// Create a base candidate string from the candidate.
71+
// E.g.: `hover:blur!` -> `blur`
72+
let baseCandidate = structuredClone(candidate) as Candidate
73+
baseCandidate.variants = []
74+
baseCandidate.important = false
75+
let baseCandidateString = printCandidate(designSystem, baseCandidate)
76+
77+
// Find the new base candidate string. `blur` -> `blur-sm`
78+
let newBaseCandidateString = LEGACY_CLASS_MAP.get(baseCandidateString)
79+
if (!newBaseCandidateString) continue
80+
81+
// Parse the new base candidate string into an actual candidate AST.
82+
let [newBaseCandidate] = designSystem.parseCandidate(newBaseCandidateString)
83+
if (!newBaseCandidate) continue
84+
85+
// Re-apply the variants and important flag from the original candidate.
86+
// E.g.: `hover:blur!` -> `blur` -> `blur-sm` -> `hover:blur-sm!`
87+
let newCandidate = structuredClone(newBaseCandidate) as Candidate
88+
newCandidate.variants = candidate.variants
89+
newCandidate.important = candidate.important
90+
91+
yield [
92+
candidate,
93+
newCandidate,
94+
THEME_KEYS.get(baseCandidateString),
95+
THEME_KEYS.get(newBaseCandidateString),
96+
] as const
97+
}
98+
}
99+
100+
for (let [fromCandidate, toCandidate, fromThemeKey, toThemeKey] of migrate(rawCandidate)) {
101+
// Every utility that has a simple representation (e.g.: `blur`, `radius`,
102+
// etc.`) without variants or special characters _could_ be a potential
103+
// problem during the migration.
104+
let isPotentialProblematicClass = (() => {
105+
if (fromCandidate.variants.length > 0) {
106+
return false
73107
}
74108

75-
let root = parts.join('-')
76-
if (Object.hasOwn(LEGACY_CLASS_MAP, root)) {
77-
let newRoot = LEGACY_CLASS_MAP[root as keyof typeof LEGACY_CLASS_MAP]
78-
79-
if (location && !root.includes('-') && !isSafeMigration(location)) {
80-
continue
81-
}
82-
83-
let fromThemeKey = THEME_KEYS[root as keyof typeof THEME_KEYS]
84-
let toThemeKey = THEME_KEYS[newRoot as keyof typeof THEME_KEYS]
85-
86-
if (fromThemeKey && toThemeKey) {
87-
// Migrating something that resolves to a value in the theme.
88-
let customFrom = designSystem.resolveThemeValue(fromThemeKey)
89-
let defaultFrom = defaultDesignSystem.resolveThemeValue(fromThemeKey)
90-
let customTo = designSystem.resolveThemeValue(toThemeKey)
91-
let defaultTo = defaultDesignSystem.resolveThemeValue(toThemeKey)
92-
93-
// The new theme value is not defined, which means we can't safely
94-
// migrate the utility.
95-
if (customTo === undefined) {
96-
continue
97-
}
98-
99-
// The "from" theme value changed compared to the default theme value.
100-
if (customFrom !== defaultFrom) {
101-
continue
102-
}
103-
104-
// The "to" theme value changed compared to the default theme value.
105-
if (customTo !== defaultTo) {
106-
continue
107-
}
108-
}
109-
110-
for (let newCandidate of designSystem.parseCandidate(newRoot)) {
111-
let clone = structuredClone(newCandidate) as Candidate
112-
113-
clone.important = candidate.important
114-
clone.variants = candidate.variants
115-
116-
return printCandidate(designSystem, clone)
117-
}
109+
if (fromCandidate.kind === 'arbitrary') {
110+
return false
118111
}
112+
113+
if (fromCandidate.kind === 'static') {
114+
return !fromCandidate.root.includes('-')
115+
}
116+
117+
if (fromCandidate.kind === 'functional') {
118+
return fromCandidate.value === null || !fromCandidate.root.includes('-')
119+
}
120+
121+
return false
122+
})()
123+
124+
if (location && isPotentialProblematicClass && !isSafeMigration(location)) {
125+
continue
119126
}
127+
128+
if (fromThemeKey && toThemeKey) {
129+
// Migrating something that resolves to a value in the theme.
130+
let customFrom = designSystem.resolveThemeValue(fromThemeKey)
131+
let defaultFrom = defaultDesignSystem.resolveThemeValue(fromThemeKey)
132+
let customTo = designSystem.resolveThemeValue(toThemeKey)
133+
let defaultTo = defaultDesignSystem.resolveThemeValue(toThemeKey)
134+
135+
// The new theme value is not defined, which means we can't safely
136+
// migrate the utility.
137+
if (customTo === undefined) {
138+
continue
139+
}
140+
141+
// The "from" theme value changed compared to the default theme value.
142+
if (customFrom !== defaultFrom) {
143+
continue
144+
}
145+
146+
// The "to" theme value changed compared to the default theme value.
147+
if (customTo !== defaultTo) {
148+
continue
149+
}
150+
}
151+
152+
return printCandidate(designSystem, toCandidate)
120153
}
121154

122155
return rawCandidate

0 commit comments

Comments
 (0)