Skip to content

Commit f29f84a

Browse files
committed
implement migrate-at-layer-utilities as PostCSS codemod
1 parent 78253ea commit f29f84a

File tree

5 files changed

+373
-197
lines changed

5 files changed

+373
-197
lines changed

packages/@tailwindcss-upgrade/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
"picocolors": "^1.0.1",
3434
"postcss": "^8.4.41",
3535
"postcss-import": "^16.1.0",
36+
"postcss-selector-parser": "^6.1.2",
37+
"prettier": "^3.3.3",
3638
"tailwindcss": "workspace:^"
3739
},
3840
"devDependencies": {

packages/@tailwindcss-upgrade/src/codemods/migrate-at-layer-utilities.test.ts

Lines changed: 125 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import dedent from 'dedent'
2+
import postcss from 'postcss'
23
import { expect, it } from 'vitest'
3-
import { toCss } from '../../ast'
4-
import * as CSS from '../../css-parser'
54
import { migrateAtLayerUtilities } from './migrate-at-layer-utilities'
65

76
const css = dedent
87

98
function migrate(input: string) {
10-
let ast = CSS.parse(input)
11-
migrateAtLayerUtilities(ast)
12-
return toCss(ast).trim()
9+
return postcss()
10+
.use(migrateAtLayerUtilities())
11+
.process(input, { from: expect.getState().testPath })
12+
.then((result) => result.css)
1313
}
1414

15-
it('should migrate simple `@layer utilities` to `@utility`', () => {
15+
it('should migrate simple `@layer utilities` to `@utility`', async () => {
1616
expect(
17-
migrate(css`
17+
await migrate(css`
1818
@layer utilities {
1919
.foo {
2020
color: red;
@@ -24,13 +24,14 @@ it('should migrate simple `@layer utilities` to `@utility`', () => {
2424
).toMatchInlineSnapshot(`
2525
"@utility foo {
2626
color: red;
27-
}"
27+
}
28+
"
2829
`)
2930
})
3031

31-
it('should migrate simple `@layer utilities` with nesting to `@utility`', () => {
32+
it('should migrate simple `@layer utilities` with nesting to `@utility`', async () => {
3233
expect(
33-
migrate(css`
34+
await migrate(css`
3435
@layer utilities {
3536
.foo {
3637
color: red;
@@ -48,19 +49,22 @@ it('should migrate simple `@layer utilities` with nesting to `@utility`', () =>
4849
).toMatchInlineSnapshot(`
4950
"@utility foo {
5051
color: red;
52+
5153
&:hover {
5254
color: blue;
5355
}
56+
5457
&:focus {
5558
color: green;
5659
}
57-
}"
60+
}
61+
"
5862
`)
5963
})
6064

61-
it('should migrate multiple simple `@layer utilities` to `@utility`', () => {
65+
it('should migrate multiple simple `@layer utilities` to `@utility`', async () => {
6266
expect(
63-
migrate(css`
67+
await migrate(css`
6468
@layer utilities {
6569
.foo {
6670
color: red;
@@ -77,13 +81,46 @@ it('should migrate multiple simple `@layer utilities` to `@utility`', () => {
7781
}
7882
@utility bar {
7983
color: blue;
80-
}"
84+
}
85+
"
8186
`)
8287
})
8388

84-
it('should invert at-rules to make them migrate-able', () => {
89+
it('should not migrate Rules inside of Rules to a `@utility`', async () => {
8590
expect(
86-
migrate(css`
91+
await migrate(css`
92+
@layer utilities {
93+
.foo {
94+
color: red;
95+
}
96+
97+
.bar {
98+
color: blue;
99+
100+
.baz {
101+
color: green;
102+
}
103+
}
104+
}
105+
`),
106+
).toMatchInlineSnapshot(`
107+
"@utility foo {
108+
color: red;
109+
}
110+
@utility bar {
111+
color: blue;
112+
113+
.baz {
114+
color: green;
115+
}
116+
}
117+
"
118+
`)
119+
})
120+
121+
it('should invert at-rules to make them migrate-able', async () => {
122+
expect(
123+
await migrate(css`
87124
@layer utilities {
88125
@media (min-width: 640px) {
89126
.foo {
@@ -97,19 +134,24 @@ it('should invert at-rules to make them migrate-able', () => {
97134
@media (min-width: 640px) {
98135
color: red;
99136
}
100-
}"
137+
}
138+
"
101139
`)
102140
})
103141

104-
it('should migrate at-rules with multiple utilities and invert them', () => {
142+
it('should migrate at-rules with multiple utilities and invert them', async () => {
105143
expect(
106-
migrate(css`
144+
await migrate(css`
107145
@layer utilities {
108146
@media (min-width: 640px) {
109147
.foo {
110148
color: red;
111149
}
150+
}
151+
}
112152
153+
@layer utilities {
154+
@media (min-width: 640px) {
113155
.bar {
114156
color: blue;
115157
}
@@ -122,17 +164,20 @@ it('should migrate at-rules with multiple utilities and invert them', () => {
122164
color: red;
123165
}
124166
}
167+
168+
125169
@utility bar {
126170
@media (min-width: 640px) {
127171
color: blue;
128172
}
129-
}"
173+
}
174+
"
130175
`)
131176
})
132177

133-
it.skip('should migrate deeply nested at-rules with multiple utilities and invert them', () => {
178+
it('should migrate deeply nested at-rules with multiple utilities and invert them', async () => {
134179
expect(
135-
migrate(css`
180+
await migrate(css`
136181
@layer utilities {
137182
@media (min-width: 640px) {
138183
.foo {
@@ -169,19 +214,28 @@ it.skip('should migrate deeply nested at-rules with multiple utilities and inver
169214
}
170215
}
171216
@utility baz {
172-
@media (min-width: 1024px) {
173-
@media (min-width: 640px) {
217+
@media (min-width: 640px) {
218+
@media (min-width: 1024px) {
174219
color: green;
175220
}
176221
}
177222
}
223+
@utility qux {
224+
@media (min-width: 640px) {
225+
@media (min-width: 1024px) {
226+
@media (min-width: 1280px) {
227+
color: yellow;
228+
}
229+
}
230+
}
231+
}
178232
"
179233
`)
180234
})
181235

182-
it('should migrate classes with pseudo elements', () => {
236+
it('should migrate classes with pseudo elements', async () => {
183237
expect(
184-
migrate(css`
238+
await migrate(css`
185239
@layer utilities {
186240
.no-scrollbar::-webkit-scrollbar {
187241
display: none;
@@ -193,13 +247,14 @@ it('should migrate classes with pseudo elements', () => {
193247
&::-webkit-scrollbar {
194248
display: none;
195249
}
196-
}"
250+
}
251+
"
197252
`)
198253
})
199254

200-
it.skip('should migrate classes with attribute selectors', () => {
255+
it('should migrate classes with attribute selectors', async () => {
201256
expect(
202-
migrate(css`
257+
await migrate(css`
203258
@layer utilities {
204259
.no-scrollbar[data-checked=''] {
205260
display: none;
@@ -208,16 +263,17 @@ it.skip('should migrate classes with attribute selectors', () => {
208263
`),
209264
).toMatchInlineSnapshot(`
210265
"@utility no-scrollbar {
211-
&[data-checked=''] {
266+
&[data-checked=""] {
212267
display: none;
213268
}
214-
}"
269+
}
270+
"
215271
`)
216272
})
217273

218-
it.skip('should migrate classes with element selectors', () => {
274+
it('should migrate classes with element selectors', async () => {
219275
expect(
220-
migrate(css`
276+
await migrate(css`
221277
@layer utilities {
222278
.no-scrollbar main {
223279
display: none;
@@ -229,13 +285,14 @@ it.skip('should migrate classes with element selectors', () => {
229285
& main {
230286
display: none;
231287
}
232-
}"
288+
}
289+
"
233290
`)
234291
})
235292

236-
it.skip('should migrate classes with id selectors', () => {
293+
it('should migrate classes with id selectors', async () => {
237294
expect(
238-
migrate(css`
295+
await migrate(css`
239296
@layer utilities {
240297
.no-scrollbar#main {
241298
display: none;
@@ -247,13 +304,14 @@ it.skip('should migrate classes with id selectors', () => {
247304
&#main {
248305
display: none;
249306
}
250-
}"
307+
}
308+
"
251309
`)
252310
})
253311

254-
it.skip('should migrate classes with another attached class', () => {
312+
it('should migrate classes with another attached class', async () => {
255313
expect(
256-
migrate(css`
314+
await migrate(css`
257315
@layer utilities {
258316
.no-scrollbar.main {
259317
display: none;
@@ -265,13 +323,19 @@ it.skip('should migrate classes with another attached class', () => {
265323
&.main {
266324
display: none;
267325
}
268-
}"
326+
}
327+
@utility main {
328+
&.no-scrollbar {
329+
display: none;
330+
}
331+
}
332+
"
269333
`)
270334
})
271335

272-
it('should migrate a selector with multiple classes to multiple @utility definitions', () => {
336+
it('should migrate a selector with multiple classes to multiple @utility definitions', async () => {
273337
expect(
274-
migrate(css`
338+
await migrate(css`
275339
@layer utilities {
276340
.foo .bar:hover .baz:focus {
277341
display: none;
@@ -293,13 +357,14 @@ it('should migrate a selector with multiple classes to multiple @utility definit
293357
.foo .bar:hover &:focus {
294358
display: none;
295359
}
296-
}"
360+
}
361+
"
297362
`)
298363
})
299364

300-
it('should merge `@utility` definitions with the same name', () => {
365+
it('should merge `@utility` definitions with the same name', async () => {
301366
expect(
302-
migrate(css`
367+
await migrate(css`
303368
@layer utilities {
304369
.step {
305370
counter-increment: step;
@@ -320,29 +385,37 @@ it('should merge `@utility` definitions with the same name', () => {
320385
@apply ml-[-41px];
321386
content: counter(step);
322387
}
323-
}"
388+
}
389+
390+
"
324391
`)
325392
})
326393

327-
it('should not migrate nested classes inside a selector (e.g.: `:has(…)`)', () => {
394+
it('should not migrate nested classes inside a `:not(…)`', async () => {
328395
expect(
329-
migrate(css`
396+
await migrate(css`
330397
@layer utilities {
331-
.foo .bar:has(.baz) {
398+
.foo .bar:not(.qux):has(.baz) {
332399
display: none;
333400
}
334401
}
335402
`),
336403
).toMatchInlineSnapshot(`
337404
"@utility foo {
338-
& .bar:has(.baz) {
405+
& .bar:not(.qux):has(.baz) {
339406
display: none;
340407
}
341408
}
342409
@utility bar {
343-
.foo &:has(.baz) {
410+
.foo &:not(.qux):has(.baz) {
344411
display: none;
345412
}
346-
}"
413+
}
414+
@utility baz {
415+
.foo .bar:not(.qux):has(&) {
416+
display: none;
417+
}
418+
}
419+
"
347420
`)
348421
})

0 commit comments

Comments
 (0)