@planningcenter/tapestry-migration-cli 2.2.0-rc.5 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,642 @@
1
+ import jscodeshift, { StringLiteral } from "jscodeshift"
2
+ import {
3
+ ImportSpecifier,
4
+ JSXAttribute,
5
+ JSXElement,
6
+ JSXIdentifier,
7
+ } from "jscodeshift"
8
+ import { describe, expect, it } from "vitest"
9
+
10
+ import { createWrapper } from "./createWrapper"
11
+
12
+ const j = jscodeshift.withParser("tsx")
13
+
14
+ function createSource(code: string) {
15
+ return j(code)
16
+ }
17
+
18
+ function createElementFromCode(code: string) {
19
+ const source = createSource(`<div>${code}</div>`)
20
+ const element = source.find(j.JSXElement).at(0).get().value.children?.[0]
21
+ return element
22
+ }
23
+
24
+ describe("createWrapper", () => {
25
+ describe("basic wrapper creation", () => {
26
+ it("should wrap element with simple wrapper", () => {
27
+ const source = createSource('import React from "react"')
28
+ const element = createElementFromCode("<Button>Save</Button>")
29
+
30
+ const result = createWrapper({
31
+ conflictAlias: "TRTooltip",
32
+ element,
33
+ j,
34
+ source,
35
+ wrapperName: "Tooltip",
36
+ wrapperPackage: "@planningcenter/tapestry",
37
+ })
38
+
39
+ expect(result.type).toBe("JSXElement")
40
+ expect((result.openingElement.name as JSXIdentifier).name).toBe("Tooltip")
41
+ expect((result.closingElement?.name as JSXIdentifier)?.name).toBe(
42
+ "Tooltip"
43
+ )
44
+ expect(result.children).toHaveLength(1)
45
+ const wrappedElement = result.children?.[0] as JSXElement
46
+ expect(wrappedElement.type).toBe("JSXElement")
47
+ expect((wrappedElement.openingElement.name as JSXIdentifier).name).toBe(
48
+ "Button"
49
+ )
50
+
51
+ // Check that import was added
52
+ const imports = source.find(j.ImportDeclaration, {
53
+ source: { value: "@planningcenter/tapestry" },
54
+ })
55
+ expect(imports).toHaveLength(1)
56
+ expect(imports.at(0).get().value.specifiers?.[0].imported?.name).toBe(
57
+ "Tooltip"
58
+ )
59
+ })
60
+
61
+ it("should wrap element with wrapper props", () => {
62
+ const source = createSource('import React from "react"')
63
+ const element = createElementFromCode("<Button>Save</Button>")
64
+
65
+ const wrapperProps = [
66
+ j.jsxAttribute(j.jsxIdentifier("title"), j.stringLiteral("Click me")),
67
+ j.jsxAttribute(j.jsxIdentifier("placement"), j.stringLiteral("top")),
68
+ ]
69
+
70
+ const result = createWrapper({
71
+ conflictAlias: "TRTooltip",
72
+ element,
73
+ j,
74
+ source,
75
+ wrapperName: "Tooltip",
76
+ wrapperPackage: "@planningcenter/tapestry",
77
+ wrapperProps,
78
+ })
79
+
80
+ expect(result.openingElement.attributes).toHaveLength(2)
81
+
82
+ const titleAttr = result.openingElement.attributes?.find(
83
+ (attr) =>
84
+ attr.type === "JSXAttribute" &&
85
+ (attr.name as JSXIdentifier)?.name === "title"
86
+ ) as JSXAttribute
87
+ expect((titleAttr?.value as StringLiteral)?.value).toBe("Click me")
88
+
89
+ const placementAttr = result.openingElement.attributes?.find(
90
+ (attr) =>
91
+ attr.type === "JSXAttribute" &&
92
+ (attr.name as JSXIdentifier)?.name === "placement"
93
+ ) as JSXAttribute
94
+ expect((placementAttr?.value as StringLiteral)?.value).toBe("top")
95
+ })
96
+
97
+ it("should handle spread props", () => {
98
+ const source = createSource('import React from "react"')
99
+ const element = createElementFromCode("<Button>Save</Button>")
100
+
101
+ const wrapperProps = [
102
+ j.jsxSpreadAttribute(j.identifier("tooltipProps")),
103
+ j.jsxAttribute(j.jsxIdentifier("title"), j.stringLiteral("Click me")),
104
+ ]
105
+
106
+ const result = createWrapper({
107
+ conflictAlias: "TRTooltip",
108
+ element,
109
+ j,
110
+ source,
111
+ wrapperName: "Tooltip",
112
+ wrapperPackage: "@planningcenter/tapestry",
113
+ wrapperProps,
114
+ })
115
+
116
+ expect(result.openingElement.attributes).toHaveLength(2)
117
+ expect(result.openingElement.attributes?.[0].type).toBe(
118
+ "JSXSpreadAttribute"
119
+ )
120
+ expect(result.openingElement.attributes?.[1].type).toBe("JSXAttribute")
121
+ })
122
+ })
123
+
124
+ describe("import handling", () => {
125
+ it("should add import when wrapper not already imported", () => {
126
+ const source = createSource(
127
+ 'import { Button } from "@planningcenter/tapestry"'
128
+ )
129
+ const element = createElementFromCode("<Button>Save</Button>")
130
+
131
+ const result = createWrapper({
132
+ conflictAlias: "TRTooltip",
133
+ element,
134
+ j,
135
+ source,
136
+ wrapperName: "Tooltip",
137
+ wrapperPackage: "@planningcenter/tapestry",
138
+ })
139
+
140
+ expect((result.openingElement.name as JSXIdentifier).name).toBe("Tooltip")
141
+
142
+ // Should merge with existing import
143
+ const imports = source.find(j.ImportDeclaration, {
144
+ source: { value: "@planningcenter/tapestry" },
145
+ })
146
+ expect(imports).toHaveLength(1)
147
+ expect(imports.at(0).get().value.specifiers).toHaveLength(2)
148
+
149
+ const specifierNames = imports
150
+ .at(0)
151
+ .get()
152
+ .value.specifiers?.map((spec: ImportSpecifier) =>
153
+ spec.type === "ImportSpecifier" ? spec.imported?.name : null
154
+ )
155
+ expect(specifierNames).toContain("Button")
156
+ expect(specifierNames).toContain("Tooltip")
157
+ })
158
+
159
+ it("should use existing import when wrapper already imported", () => {
160
+ const source = createSource(
161
+ 'import { Button, Tooltip } from "@planningcenter/tapestry"'
162
+ )
163
+ const element = createElementFromCode("<Button>Save</Button>")
164
+
165
+ const result = createWrapper({
166
+ conflictAlias: "TRTooltip",
167
+ element,
168
+ j,
169
+ source,
170
+ wrapperName: "Tooltip",
171
+ wrapperPackage: "@planningcenter/tapestry",
172
+ })
173
+
174
+ expect((result.openingElement.name as JSXIdentifier).name).toBe("Tooltip")
175
+
176
+ // Should not modify existing import
177
+ const imports = source.find(j.ImportDeclaration, {
178
+ source: { value: "@planningcenter/tapestry" },
179
+ })
180
+ expect(imports).toHaveLength(1)
181
+ expect(imports.at(0).get().value.specifiers).toHaveLength(2)
182
+ })
183
+
184
+ it("should handle import conflicts with auto-generated alias", () => {
185
+ const source = createSource(`
186
+ import { Tooltip } from "@some/other-package"
187
+ import { Button } from "@planningcenter/tapestry"
188
+ `)
189
+ const element = createElementFromCode("<Button>Save</Button>")
190
+
191
+ const result = createWrapper({
192
+ conflictAlias: "TRTooltip",
193
+ element,
194
+ j,
195
+ source,
196
+ wrapperName: "Tooltip",
197
+ wrapperPackage: "@planningcenter/tapestry",
198
+ })
199
+
200
+ expect((result.openingElement.name as JSXIdentifier).name).toBe(
201
+ "TRTooltip"
202
+ )
203
+ expect((result.closingElement?.name as JSXIdentifier)?.name).toBe(
204
+ "TRTooltip"
205
+ )
206
+
207
+ const tapestryImport = source.find(j.ImportDeclaration, {
208
+ source: { value: "@planningcenter/tapestry" },
209
+ })
210
+ const tooltipSpec = tapestryImport
211
+ .at(0)
212
+ .get()
213
+ .value.specifiers?.find(
214
+ (spec: ImportSpecifier) =>
215
+ spec.type === "ImportSpecifier" && spec.imported?.name === "Tooltip"
216
+ ) as ImportSpecifier
217
+ expect(tooltipSpec?.local?.name).toBe("TRTooltip")
218
+ })
219
+
220
+ it("should handle import conflicts with custom alias", () => {
221
+ const source = createSource(`
222
+ import { Tooltip } from "@some/other-package"
223
+ import { Button } from "@planningcenter/tapestry"
224
+ `)
225
+ const element = createElementFromCode("<Button>Save</Button>")
226
+
227
+ const result = createWrapper({
228
+ conflictAlias: "TapestryTooltip",
229
+ element,
230
+ j,
231
+ source,
232
+ wrapperName: "Tooltip",
233
+ wrapperPackage: "@planningcenter/tapestry",
234
+ })
235
+
236
+ expect((result.openingElement.name as JSXIdentifier).name).toBe(
237
+ "TapestryTooltip"
238
+ )
239
+ expect((result.closingElement?.name as JSXIdentifier)?.name).toBe(
240
+ "TapestryTooltip"
241
+ )
242
+
243
+ const tapestryImport = source.find(j.ImportDeclaration, {
244
+ source: { value: "@planningcenter/tapestry" },
245
+ })
246
+ const tooltipSpec = tapestryImport
247
+ .at(0)
248
+ .get()
249
+ .value.specifiers?.find(
250
+ (spec: ImportSpecifier) =>
251
+ spec.type === "ImportSpecifier" && spec.imported?.name === "Tooltip"
252
+ ) as ImportSpecifier
253
+ expect(tooltipSpec?.local?.name).toBe("TapestryTooltip")
254
+ })
255
+ })
256
+
257
+ describe("element preservation", () => {
258
+ it("should preserve element structure", () => {
259
+ const source = createSource('import React from "react"')
260
+ const element = createElementFromCode(
261
+ '<Button variant="primary" onClick={handleClick}>Save</Button>'
262
+ )
263
+
264
+ const result = createWrapper({
265
+ conflictAlias: "TRTooltip",
266
+ element,
267
+ j,
268
+ source,
269
+ wrapperName: "Tooltip",
270
+ wrapperPackage: "@planningcenter/tapestry",
271
+ })
272
+
273
+ const wrappedButton = result.children?.[0] as JSXElement
274
+ expect((wrappedButton?.openingElement.name as JSXIdentifier).name).toBe(
275
+ "Button"
276
+ )
277
+ expect(wrappedButton?.openingElement.attributes).toHaveLength(2)
278
+
279
+ const variantAttr = wrappedButton?.openingElement.attributes?.find(
280
+ (attr) =>
281
+ attr.type === "JSXAttribute" &&
282
+ (attr.name as JSXIdentifier)?.name === "variant"
283
+ ) as JSXAttribute
284
+ expect((variantAttr?.value as StringLiteral)?.value).toBe("primary")
285
+
286
+ const onClickAttr = wrappedButton?.openingElement.attributes?.find(
287
+ (attr) =>
288
+ attr.type === "JSXAttribute" &&
289
+ (attr.name as JSXIdentifier)?.name === "onClick"
290
+ ) as JSXAttribute
291
+ expect(onClickAttr?.value?.type).toBe("JSXExpressionContainer")
292
+ })
293
+
294
+ it("should preserve self-closing elements", () => {
295
+ const source = createSource('import React from "react"')
296
+ const element = createElementFromCode('<Button variant="primary" />')
297
+
298
+ const result = createWrapper({
299
+ conflictAlias: "TRTooltip",
300
+ element,
301
+ j,
302
+ source,
303
+ wrapperName: "Tooltip",
304
+ wrapperPackage: "@planningcenter/tapestry",
305
+ })
306
+
307
+ const wrappedButton = result.children?.[0] as JSXElement
308
+ expect((wrappedButton?.openingElement.name as JSXIdentifier).name).toBe(
309
+ "Button"
310
+ )
311
+ expect(wrappedButton?.openingElement.selfClosing).toBe(true)
312
+ expect(wrappedButton?.closingElement).toBeNull()
313
+ })
314
+
315
+ it("should preserve complex nested elements", () => {
316
+ const source = createSource('import React from "react"')
317
+ const element = createElementFromCode(
318
+ '<Button><Icon name="save" /><span>Save Changes</span></Button>'
319
+ )
320
+
321
+ const result = createWrapper({
322
+ conflictAlias: "TRTooltip",
323
+ element,
324
+ j,
325
+ source,
326
+ wrapperName: "Tooltip",
327
+ wrapperPackage: "@planningcenter/tapestry",
328
+ })
329
+
330
+ const wrappedButton = result.children?.[0] as JSXElement
331
+ expect(wrappedButton?.type).toBe("JSXElement")
332
+ expect((wrappedButton?.openingElement.name as JSXIdentifier).name).toBe(
333
+ "Button"
334
+ )
335
+
336
+ // Check that the button has children
337
+ expect(wrappedButton?.children).toBeDefined()
338
+ expect(Array.isArray(wrappedButton?.children)).toBe(true)
339
+ expect(wrappedButton?.children?.length).toBeGreaterThan(0)
340
+
341
+ const iconElement = wrappedButton?.children?.find(
342
+ (child) =>
343
+ child?.type === "JSXElement" &&
344
+ (child.openingElement?.name as JSXIdentifier)?.name === "Icon"
345
+ )
346
+ expect(iconElement).toBeDefined()
347
+
348
+ const spanElement = wrappedButton?.children?.find(
349
+ (child) =>
350
+ child?.type === "JSXElement" &&
351
+ (child.openingElement?.name as JSXIdentifier)?.name === "span"
352
+ )
353
+ expect(spanElement).toBeDefined()
354
+ })
355
+ })
356
+
357
+ describe("edge cases", () => {
358
+ it("should handle empty wrapperProps", () => {
359
+ const source = createSource('import React from "react"')
360
+ const element = createElementFromCode("<Button>Save</Button>")
361
+
362
+ const result = createWrapper({
363
+ conflictAlias: "TRTooltip",
364
+ element,
365
+ j,
366
+ source,
367
+ wrapperName: "Tooltip",
368
+ wrapperPackage: "@planningcenter/tapestry",
369
+ wrapperProps: [],
370
+ })
371
+
372
+ expect(result.openingElement.attributes).toHaveLength(0)
373
+ })
374
+
375
+ it("should handle undefined wrapperProps", () => {
376
+ const source = createSource('import React from "react"')
377
+ const element = createElementFromCode("<Button>Save</Button>")
378
+
379
+ const result = createWrapper({
380
+ conflictAlias: "TRTooltip",
381
+ element,
382
+ j,
383
+ source,
384
+ wrapperName: "Tooltip",
385
+ wrapperPackage: "@planningcenter/tapestry",
386
+ })
387
+
388
+ expect(result.openingElement.attributes).toHaveLength(0)
389
+ })
390
+
391
+ it("should create proper JSX structure", () => {
392
+ const source = createSource('import React from "react"')
393
+ const element = createElementFromCode("<Button>Save</Button>")
394
+
395
+ const result = createWrapper({
396
+ conflictAlias: "TRTooltip",
397
+ element,
398
+ j,
399
+ source,
400
+ wrapperName: "Tooltip",
401
+ wrapperPackage: "@planningcenter/tapestry",
402
+ })
403
+
404
+ // Verify the JSX structure is valid
405
+ expect(result.type).toBe("JSXElement")
406
+ expect(result.openingElement.type).toBe("JSXOpeningElement")
407
+ expect(result.closingElement?.type).toBe("JSXClosingElement")
408
+ expect(result.openingElement.selfClosing).toBe(false)
409
+ expect(result.children).toHaveLength(1)
410
+ })
411
+
412
+ it("should not add parentheses around wrapped elements", () => {
413
+ const source = createSource('import React from "react"')
414
+ const element = createElementFromCode("<Button>Save</Button>")
415
+
416
+ createWrapper({
417
+ conflictAlias: "TRTooltip",
418
+ element,
419
+ j,
420
+ source,
421
+ wrapperName: "Tooltip",
422
+ wrapperPackage: "@planningcenter/tapestry",
423
+ })
424
+
425
+ // Check that the output doesn't contain parentheses around the Button
426
+ const result = source.toSource()
427
+ expect(result).not.toContain("(<Button>Save</Button>)")
428
+ expect(result).not.toContain("(<Button")
429
+ expect(result).not.toContain("Button>)")
430
+ })
431
+ })
432
+
433
+ describe("key attribute handling", () => {
434
+ it("should move key attribute from element to wrapper", () => {
435
+ const source = createSource('import React from "react"')
436
+ const element = createElementFromCode(
437
+ '<Button key="test-key">Save</Button>'
438
+ )
439
+
440
+ const result = createWrapper({
441
+ conflictAlias: "TRTooltip",
442
+ element,
443
+ j,
444
+ source,
445
+ wrapperName: "Tooltip",
446
+ wrapperPackage: "@planningcenter/tapestry",
447
+ })
448
+
449
+ // Wrapper should have the key attribute
450
+ const keyAttr = result.openingElement.attributes?.find(
451
+ (attr) =>
452
+ attr.type === "JSXAttribute" &&
453
+ (attr.name as JSXIdentifier)?.name === "key"
454
+ ) as JSXAttribute
455
+ expect(keyAttr).toBeDefined()
456
+ expect((keyAttr?.value as StringLiteral)?.value).toBe("test-key")
457
+
458
+ // Inner element should not have key attribute
459
+ const innerElement = result.children?.[0] as JSXElement
460
+ const innerKeyAttr = innerElement.openingElement.attributes?.find(
461
+ (attr) =>
462
+ attr.type === "JSXAttribute" &&
463
+ (attr.name as JSXIdentifier)?.name === "key"
464
+ )
465
+ expect(innerKeyAttr).toBeUndefined()
466
+ })
467
+
468
+ it("should move key attribute and preserve wrapper props", () => {
469
+ const source = createSource('import React from "react"')
470
+ const element = createElementFromCode(
471
+ '<Button key="item-1">Save</Button>'
472
+ )
473
+
474
+ const wrapperProps = [
475
+ j.jsxAttribute(j.jsxIdentifier("title"), j.stringLiteral("Click me")),
476
+ j.jsxAttribute(j.jsxIdentifier("placement"), j.stringLiteral("top")),
477
+ ]
478
+
479
+ const result = createWrapper({
480
+ conflictAlias: "TRTooltip",
481
+ element,
482
+ j,
483
+ source,
484
+ wrapperName: "Tooltip",
485
+ wrapperPackage: "@planningcenter/tapestry",
486
+ wrapperProps,
487
+ })
488
+
489
+ expect(result.openingElement.attributes).toHaveLength(3)
490
+
491
+ // Key should be first
492
+ const keyAttr = result.openingElement.attributes?.[0] as JSXAttribute
493
+ expect((keyAttr.name as JSXIdentifier)?.name).toBe("key")
494
+ expect((keyAttr?.value as StringLiteral)?.value).toBe("item-1")
495
+
496
+ // Other props should follow
497
+ const titleAttr = result.openingElement.attributes?.find(
498
+ (attr) =>
499
+ attr.type === "JSXAttribute" &&
500
+ (attr.name as JSXIdentifier)?.name === "title"
501
+ ) as JSXAttribute
502
+ expect((titleAttr?.value as StringLiteral)?.value).toBe("Click me")
503
+ })
504
+
505
+ it("should handle key attribute with JSX expression", () => {
506
+ const source = createSource('import React from "react"')
507
+ const element = createElementFromCode(
508
+ "<Button key={item.id}>Save</Button>"
509
+ )
510
+
511
+ const result = createWrapper({
512
+ conflictAlias: "TRTooltip",
513
+ element,
514
+ j,
515
+ source,
516
+ wrapperName: "Tooltip",
517
+ wrapperPackage: "@planningcenter/tapestry",
518
+ })
519
+
520
+ const keyAttr = result.openingElement.attributes?.find(
521
+ (attr) =>
522
+ attr.type === "JSXAttribute" &&
523
+ (attr.name as JSXIdentifier)?.name === "key"
524
+ ) as JSXAttribute
525
+ expect(keyAttr).toBeDefined()
526
+ expect(keyAttr.value?.type).toBe("JSXExpressionContainer")
527
+ })
528
+
529
+ it("should preserve other attributes when moving key", () => {
530
+ const source = createSource('import React from "react"')
531
+ const element = createElementFromCode(
532
+ '<Button key="btn-1" variant="primary" disabled onClick={handler}>Save</Button>'
533
+ )
534
+
535
+ const result = createWrapper({
536
+ conflictAlias: "TRTooltip",
537
+ element,
538
+ j,
539
+ source,
540
+ wrapperName: "Tooltip",
541
+ wrapperPackage: "@planningcenter/tapestry",
542
+ })
543
+
544
+ // Wrapper should have key
545
+ const wrapperKeyAttr = result.openingElement.attributes?.find(
546
+ (attr) =>
547
+ attr.type === "JSXAttribute" &&
548
+ (attr.name as JSXIdentifier)?.name === "key"
549
+ )
550
+ expect(wrapperKeyAttr).toBeDefined()
551
+
552
+ // Inner element should have other attributes but not key
553
+ const innerElement = result.children?.[0] as JSXElement
554
+ expect(innerElement.openingElement.attributes).toHaveLength(3)
555
+
556
+ const variantAttr = innerElement.openingElement.attributes?.find(
557
+ (attr) =>
558
+ attr.type === "JSXAttribute" &&
559
+ (attr.name as JSXIdentifier)?.name === "variant"
560
+ )
561
+ expect(variantAttr).toBeDefined()
562
+
563
+ const disabledAttr = innerElement.openingElement.attributes?.find(
564
+ (attr) =>
565
+ attr.type === "JSXAttribute" &&
566
+ (attr.name as JSXIdentifier)?.name === "disabled"
567
+ )
568
+ expect(disabledAttr).toBeDefined()
569
+
570
+ const onClickAttr = innerElement.openingElement.attributes?.find(
571
+ (attr) =>
572
+ attr.type === "JSXAttribute" &&
573
+ (attr.name as JSXIdentifier)?.name === "onClick"
574
+ )
575
+ expect(onClickAttr).toBeDefined()
576
+ })
577
+
578
+ it("should work normally when no key attribute exists", () => {
579
+ const source = createSource('import React from "react"')
580
+ const element = createElementFromCode(
581
+ '<Button variant="primary">Save</Button>'
582
+ )
583
+
584
+ const result = createWrapper({
585
+ conflictAlias: "TRTooltip",
586
+ element,
587
+ j,
588
+ source,
589
+ wrapperName: "Tooltip",
590
+ wrapperPackage: "@planningcenter/tapestry",
591
+ })
592
+
593
+ // Wrapper should not have key attribute
594
+ const wrapperKeyAttr = result.openingElement.attributes?.find(
595
+ (attr) =>
596
+ attr.type === "JSXAttribute" &&
597
+ (attr.name as JSXIdentifier)?.name === "key"
598
+ )
599
+ expect(wrapperKeyAttr).toBeUndefined()
600
+
601
+ // Inner element should preserve its attributes
602
+ const innerElement = result.children?.[0] as JSXElement
603
+ expect(innerElement.openingElement.attributes).toHaveLength(1)
604
+
605
+ const variantAttr = innerElement.openingElement.attributes?.find(
606
+ (attr) =>
607
+ attr.type === "JSXAttribute" &&
608
+ (attr.name as JSXIdentifier)?.name === "variant"
609
+ )
610
+ expect(variantAttr).toBeDefined()
611
+ })
612
+
613
+ it("should generate correct source code with key attribute", () => {
614
+ const source = createSource(`
615
+ import React from "react"
616
+ const Test = () => <Button key="test">Save</Button>
617
+ `)
618
+
619
+ // Find the Button element that's actually in the source
620
+ const element = source
621
+ .find(j.JSXElement, {
622
+ openingElement: { name: { name: "Button" } },
623
+ })
624
+ .at(0)
625
+ .get().value
626
+
627
+ createWrapper({
628
+ conflictAlias: "TRTooltip",
629
+ element,
630
+ j,
631
+ source,
632
+ wrapperName: "Tooltip",
633
+ wrapperPackage: "@planningcenter/tapestry",
634
+ })
635
+
636
+ const result = source.toSource()
637
+ expect(result).toContain('<Tooltip key="test">')
638
+ expect(result).toContain("<Button>Save</Button>")
639
+ expect(result).not.toContain('<Button key="test">')
640
+ })
641
+ })
642
+ })
@@ -0,0 +1,70 @@
1
+ import {
2
+ Collection,
3
+ JSCodeshift,
4
+ JSXAttribute,
5
+ JSXElement,
6
+ JSXSpreadAttribute,
7
+ } from "jscodeshift"
8
+
9
+ import { addImport } from "../transformFactories/helpers/manageImports"
10
+ import { getAttribute } from "./getAttribute"
11
+
12
+ export function createWrapper({
13
+ conflictAlias,
14
+ element,
15
+ j,
16
+ source,
17
+ wrapperName,
18
+ wrapperPackage,
19
+ wrapperProps = [],
20
+ }: {
21
+ conflictAlias: string
22
+ element: JSXElement
23
+ j: JSCodeshift
24
+ source: Collection
25
+ wrapperName: string
26
+ wrapperPackage: string
27
+ wrapperProps?: (JSXAttribute | JSXSpreadAttribute)[]
28
+ }): JSXElement {
29
+ const actualWrapperName = addImport({
30
+ component: wrapperName,
31
+ conflictAlias,
32
+ j,
33
+ pkg: wrapperPackage,
34
+ source,
35
+ })
36
+
37
+ const keyAttribute = getAttribute({ element, name: "key" })
38
+ const cleanAttributes = (element.openingElement.attributes || []).filter(
39
+ (attr) => !(attr.type === "JSXAttribute" && attr.name.name === "key")
40
+ )
41
+
42
+ const cleanElement = j.jsxElement(
43
+ j.jsxOpeningElement(
44
+ element.openingElement.name,
45
+ cleanAttributes,
46
+ element.openingElement.selfClosing
47
+ ),
48
+ element.closingElement,
49
+ element.children
50
+ )
51
+
52
+ const wrapperElement = j.jsxElement(
53
+ j.jsxOpeningElement(
54
+ j.jsxIdentifier(actualWrapperName),
55
+ keyAttribute ? [keyAttribute, ...wrapperProps] : wrapperProps,
56
+ false
57
+ ),
58
+ j.jsxClosingElement(j.jsxIdentifier(actualWrapperName)),
59
+ [cleanElement]
60
+ )
61
+
62
+ source
63
+ .find(j.JSXElement)
64
+ .filter((path) => path.value === element)
65
+ .forEach((path) => {
66
+ path.replace(wrapperElement)
67
+ })
68
+
69
+ return wrapperElement
70
+ }