@planningcenter/tapestry-migration-cli 2.4.0-rc.9 → 2.4.1-rc.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.
Files changed (42) hide show
  1. package/README.md +100 -6
  2. package/dist/tapestry-react-shim.cjs +1 -1
  3. package/package.json +2 -2
  4. package/src/components/button/index.ts +3 -3
  5. package/src/components/button/transforms/innerRefToRef.test.ts +170 -0
  6. package/src/components/button/transforms/innerRefToRef.ts +14 -0
  7. package/src/components/button/transforms/unsupportedProps.ts +8 -31
  8. package/src/components/checkbox/index.ts +38 -0
  9. package/src/components/checkbox/transforms/childrenToLabel.test.ts +146 -0
  10. package/src/components/checkbox/transforms/childrenToLabel.ts +54 -0
  11. package/src/components/checkbox/transforms/convertStyleProps.test.ts +161 -0
  12. package/src/components/checkbox/transforms/convertStyleProps.ts +10 -0
  13. package/src/components/checkbox/transforms/innerRefToRef.test.ts +161 -0
  14. package/src/components/checkbox/transforms/innerRefToRef.ts +14 -0
  15. package/src/components/checkbox/transforms/moveCheckboxImport.test.ts +182 -0
  16. package/src/components/checkbox/transforms/moveCheckboxImport.ts +13 -0
  17. package/src/components/checkbox/transforms/sizeMapping.test.ts +193 -0
  18. package/src/components/checkbox/transforms/sizeMapping.ts +45 -0
  19. package/src/components/checkbox/transforms/unsupportedProps.test.ts +243 -0
  20. package/src/components/checkbox/transforms/unsupportedProps.ts +47 -0
  21. package/src/components/link/index.ts +14 -4
  22. package/src/components/link/transforms/auditSpreadProps.test.ts +351 -0
  23. package/src/components/link/transforms/auditSpreadProps.ts +24 -0
  24. package/src/components/link/transforms/innerRefToRef.test.ts +170 -0
  25. package/src/components/link/transforms/innerRefToRef.ts +14 -0
  26. package/src/components/link/transforms/moveLinkImport.test.ts +295 -0
  27. package/src/components/{button/transforms/linkToButton.ts → link/transforms/moveLinkImport.ts} +4 -5
  28. package/src/components/link/transforms/{inlineMemberToKind.test.ts → removeInlineMember.test.ts} +13 -28
  29. package/src/components/link/transforms/removeInlineMember.ts +11 -0
  30. package/src/components/link/transforms/{inlinePropToKind.test.ts → removeInlineProp.test.ts} +12 -29
  31. package/src/components/link/transforms/{inlinePropToKind.ts → removeInlineProp.ts} +0 -9
  32. package/src/components/link/transforms/tooltipToWrapper.test.ts +392 -0
  33. package/src/components/link/transforms/tooltipToWrapper.ts +35 -0
  34. package/src/components/link/transforms/unsupportedProps.test.ts +265 -0
  35. package/src/components/link/transforms/unsupportedProps.ts +58 -0
  36. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +35 -0
  37. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +1 -0
  38. package/src/index.ts +18 -7
  39. package/src/jscodeshiftRunner.ts +9 -1
  40. package/src/reportGenerator.ts +23 -2
  41. package/src/components/button/transforms/linkToButton.test.ts +0 -426
  42. package/src/components/link/transforms/inlineMemberToKind.ts +0 -49
@@ -0,0 +1,193 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./sizeMapping"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string): string {
9
+ const fileInfo = { path: "test.tsx", source }
10
+ const result = transform(
11
+ fileInfo,
12
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
13
+ {}
14
+ ) as string | null
15
+ return result || source
16
+ }
17
+
18
+ describe("sizeMapping transform", () => {
19
+ describe("size value transformations", () => {
20
+ it("should transform lg to md", () => {
21
+ const input = `
22
+ import { Checkbox } from "@planningcenter/tapestry-react"
23
+
24
+ function Test() {
25
+ return <Checkbox size="lg" label="Large checkbox" />
26
+ }
27
+ `.trim()
28
+
29
+ const result = applyTransform(input)
30
+ expect(result).toContain('size="md"')
31
+ expect(result).not.toContain('size="lg"')
32
+ expect(result).toContain(
33
+ 'TODO: tapestry-migration (size): Size "lg" was mapped to "md"'
34
+ )
35
+ })
36
+
37
+ it("should transform xl to md", () => {
38
+ const input = `
39
+ import { Checkbox } from "@planningcenter/tapestry-react"
40
+
41
+ function Test() {
42
+ return <Checkbox size="xl" label="Extra large checkbox" />
43
+ }
44
+ `.trim()
45
+
46
+ const result = applyTransform(input)
47
+ expect(result).toContain('size="md"')
48
+ expect(result).not.toContain('size="xl"')
49
+ expect(result).toContain(
50
+ 'TODO: tapestry-migration (size): Size "xl" was mapped to "md"'
51
+ )
52
+ })
53
+
54
+ it("should not transform xs size", () => {
55
+ const input = `
56
+ import { Checkbox } from "@planningcenter/tapestry-react"
57
+
58
+ function Test() {
59
+ return <Checkbox size="xs" label="Extra small checkbox" />
60
+ }
61
+ `.trim()
62
+
63
+ const result = applyTransform(input)
64
+ expect(result).toContain('size="xs"')
65
+ expect(result).not.toContain("TODO: tapestry-migration")
66
+ })
67
+
68
+ it("should not transform supported sizes", () => {
69
+ const input = `
70
+ import { Checkbox } from "@planningcenter/tapestry-react"
71
+
72
+ function Test() {
73
+ return (
74
+ <div>
75
+ <Checkbox size="md" label="Medium checkbox" />
76
+ <Checkbox size="sm" label="Small checkbox" />
77
+ <Checkbox size="xs" label="Extra small checkbox" />
78
+ </div>
79
+ )
80
+ }
81
+ `.trim()
82
+
83
+ const result = applyTransform(input)
84
+ expect(result).toContain('size="md"')
85
+ expect(result).toContain('size="sm"')
86
+ expect(result).toContain('size="xs"')
87
+ expect(result).not.toContain("TODO: tapestry-migration")
88
+ })
89
+ })
90
+
91
+ describe("edge cases", () => {
92
+ it("should not affect checkbox without size prop", () => {
93
+ const input = `
94
+ import { Checkbox } from "@planningcenter/tapestry-react"
95
+
96
+ function Test() {
97
+ return <Checkbox label="No size" />
98
+ }
99
+ `.trim()
100
+
101
+ const result = applyTransform(input)
102
+ expect(result).toBe(input)
103
+ })
104
+
105
+ it("should not affect other components", () => {
106
+ const input = `
107
+ import { Button, Checkbox } from "@planningcenter/tapestry-react"
108
+
109
+ function Test() {
110
+ return (
111
+ <div>
112
+ <Button size="lg">Large button</Button>
113
+ <Checkbox size="md" label="Medium checkbox" />
114
+ </div>
115
+ )
116
+ }
117
+ `.trim()
118
+
119
+ const result = applyTransform(input)
120
+ expect(result).toContain('size="lg"')
121
+ expect(result).toContain('size="md"')
122
+ })
123
+
124
+ it("should handle multiple checkboxes with different sizes", () => {
125
+ const input = `
126
+ import { Checkbox } from "@planningcenter/tapestry-react"
127
+
128
+ function Test() {
129
+ return (
130
+ <div>
131
+ <Checkbox size="lg" label="Large" />
132
+ <Checkbox size="md" label="Medium" />
133
+ <Checkbox size="xl" label="Extra large" />
134
+ <Checkbox size="xs" label="Extra small" />
135
+ </div>
136
+ )
137
+ }
138
+ `.trim()
139
+
140
+ const result = applyTransform(input)
141
+ expect(result).toContain('size="md"')
142
+ expect(result).toContain('size="xs"')
143
+ expect(result).not.toContain('size="lg"')
144
+ expect(result).not.toContain('size="xl"')
145
+ // Should have 2 TODO comments (one for lg->md, one for xl->md)
146
+ const todoMatches = result.match(/TODO: tapestry-migration \(size\)/g)
147
+ expect(todoMatches).toHaveLength(2)
148
+ })
149
+
150
+ it("should preserve other props", () => {
151
+ const input = `
152
+ import { Checkbox } from "@planningcenter/tapestry-react"
153
+
154
+ function Test() {
155
+ return (
156
+ <Checkbox
157
+ size="lg"
158
+ label="Test"
159
+ checked
160
+ disabled
161
+ onChange={() => {}}
162
+ />
163
+ )
164
+ }
165
+ `.trim()
166
+
167
+ const result = applyTransform(input)
168
+ expect(result).toContain('size="md"')
169
+ expect(result).toContain('label="Test"')
170
+ expect(result).toContain("checked")
171
+ expect(result).toContain("disabled")
172
+ expect(result).toContain("onChange={() => {}}")
173
+ expect(result).toContain(
174
+ 'TODO: tapestry-migration (size): Size "lg" was mapped to "md"'
175
+ )
176
+ })
177
+
178
+ it("should not transform expression values", () => {
179
+ const input = `
180
+ import { Checkbox } from "@planningcenter/tapestry-react"
181
+
182
+ function Test() {
183
+ const size = "lg"
184
+ return <Checkbox size={size} label="Variable size" />
185
+ }
186
+ `.trim()
187
+
188
+ const result = applyTransform(input)
189
+ expect(result).toContain("size={size}")
190
+ expect(result).not.toContain("TODO: tapestry-migration")
191
+ })
192
+ })
193
+ })
@@ -0,0 +1,45 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
4
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
5
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
6
+
7
+ const SIZE_MAPPING = {
8
+ lg: "md",
9
+ xl: "md",
10
+ }
11
+
12
+ const transform: Transform = attributeTransformFactory({
13
+ condition: hasAttribute("size"),
14
+ targetComponent: "Checkbox",
15
+ targetPackage: "@planningcenter/tapestry-react",
16
+ transform: (element, { j }) => {
17
+ let hasChanges = false
18
+
19
+ const sizeAttr = element.openingElement.attributes?.find(
20
+ (attr) => attr.type === "JSXAttribute" && attr.name.name === "size"
21
+ )
22
+
23
+ if (sizeAttr && sizeAttr.type === "JSXAttribute") {
24
+ if (sizeAttr.value?.type === "StringLiteral") {
25
+ const sizeValue = sizeAttr.value.value as string
26
+ const mappedSize = SIZE_MAPPING[sizeValue as keyof typeof SIZE_MAPPING]
27
+
28
+ if (mappedSize && mappedSize !== sizeValue) {
29
+ sizeAttr.value.value = mappedSize
30
+ hasChanges = true
31
+
32
+ addCommentToAttribute({
33
+ attribute: sizeAttr,
34
+ j,
35
+ text: `Size "${sizeValue}" was mapped to "${mappedSize}". Verify visual appearance as sizes may differ slightly.`,
36
+ })
37
+ }
38
+ }
39
+ }
40
+
41
+ return hasChanges
42
+ },
43
+ })
44
+
45
+ export default transform
@@ -0,0 +1,243 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./unsupportedProps"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string): string {
9
+ const fileInfo = { path: "test.tsx", source }
10
+ const result = transform(
11
+ fileInfo,
12
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
13
+ {}
14
+ ) as string | null
15
+ return result || source
16
+ }
17
+
18
+ describe("unsupportedProps transform", () => {
19
+ describe("unsupported prop detection", () => {
20
+ it("should add comment for css prop", () => {
21
+ const input = `
22
+ import { Checkbox } from "@planningcenter/tapestry-react"
23
+
24
+ function Test() {
25
+ return <Checkbox css={{ color: 'red' }} label="Test" />
26
+ }
27
+ `.trim()
28
+
29
+ const result = applyTransform(input)
30
+ expect(result).toContain(
31
+ "/* TODO: tapestry-migration (css): 'css' is not supported, please migrate as needed."
32
+ )
33
+ expect(result).toContain(
34
+ "CSS prop is not supported. Use className or style prop instead."
35
+ )
36
+ expect(result).toContain("css={{ color: 'red' }}")
37
+ })
38
+
39
+ it("should add comment for custom unsupported prop", () => {
40
+ const input = `
41
+ import { Checkbox } from "@planningcenter/tapestry-react"
42
+
43
+ function Test() {
44
+ return <Checkbox customProp="value" label="Test" />
45
+ }
46
+ `.trim()
47
+
48
+ const result = applyTransform(input)
49
+ expect(result).toContain(
50
+ "/* TODO: tapestry-migration (customProp): 'customProp' is not supported, please migrate as needed."
51
+ )
52
+ expect(result).toContain('customProp="value"')
53
+ })
54
+
55
+ it("should add comments for multiple unsupported props", () => {
56
+ const input = `
57
+ import { Checkbox } from "@planningcenter/tapestry-react"
58
+
59
+ function Test() {
60
+ return (
61
+ <Checkbox
62
+ css={{ color: 'red' }}
63
+ ref={ref}
64
+ customProp="value"
65
+ label="Test"
66
+ />
67
+ )
68
+ }
69
+ `.trim()
70
+
71
+ const result = applyTransform(input)
72
+ expect(result).toContain("/* TODO: tapestry-migration (css):")
73
+ expect(result).toContain("/* TODO: tapestry-migration (customProp):")
74
+ })
75
+ })
76
+
77
+ describe("supported props", () => {
78
+ it("should not add comments for supported checkbox props", () => {
79
+ const input = `
80
+ import { Checkbox } from "@planningcenter/tapestry-react"
81
+
82
+ function Test() {
83
+ const ref = React.useRef()
84
+ return (
85
+ <Checkbox
86
+ checked
87
+ disabled
88
+ indeterminate
89
+ ref={ref}
90
+ label="Test"
91
+ name="checkbox"
92
+ onChange={() => {}}
93
+ value="test"
94
+ />
95
+ )
96
+ }
97
+ `.trim()
98
+
99
+ const result = applyTransform(input)
100
+ expect(result).not.toContain("TODO: tapestry-migration")
101
+ expect(result).toContain("checked")
102
+ expect(result).toContain("disabled")
103
+ expect(result).toContain("indeterminate")
104
+ expect(result).toContain("ref={ref}")
105
+ expect(result).toContain('label="Test"')
106
+ expect(result).toContain('name="checkbox"')
107
+ expect(result).toContain("onChange={() => {}}")
108
+ expect(result).toContain('value="test"')
109
+ })
110
+
111
+ it("should not add comments for common props", () => {
112
+ const input = `
113
+ import { Checkbox } from "@planningcenter/tapestry-react"
114
+
115
+ function Test() {
116
+ return (
117
+ <Checkbox
118
+ className="test"
119
+ id="checkbox"
120
+ key="key"
121
+ style={{ color: 'red' }}
122
+ tabIndex={0}
123
+ label="Test"
124
+ />
125
+ )
126
+ }
127
+ `.trim()
128
+
129
+ const result = applyTransform(input)
130
+ expect(result).not.toContain("TODO: tapestry-migration")
131
+ expect(result).toContain('className="test"')
132
+ expect(result).toContain('id="checkbox"')
133
+ expect(result).toContain('key="key"')
134
+ expect(result).toContain("style={{ color: 'red' }}")
135
+ expect(result).toContain("tabIndex={0}")
136
+ })
137
+
138
+ it("should not add comments for aria props", () => {
139
+ const input = `
140
+ import { Checkbox } from "@planningcenter/tapestry-react"
141
+
142
+ function Test() {
143
+ return (
144
+ <Checkbox
145
+ aria-label="Test checkbox"
146
+ aria-describedby="description"
147
+ label="Test"
148
+ />
149
+ )
150
+ }
151
+ `.trim()
152
+
153
+ const result = applyTransform(input)
154
+ expect(result).not.toContain("TODO: tapestry-migration")
155
+ expect(result).toContain('aria-label="Test checkbox"')
156
+ expect(result).toContain('aria-describedby="description"')
157
+ })
158
+
159
+ it("should not add comments for data props", () => {
160
+ const input = `
161
+ import { Checkbox } from "@planningcenter/tapestry-react"
162
+
163
+ function Test() {
164
+ return (
165
+ <Checkbox
166
+ data-testid="checkbox"
167
+ data-cy="test-checkbox"
168
+ label="Test"
169
+ />
170
+ )
171
+ }
172
+ `.trim()
173
+
174
+ const result = applyTransform(input)
175
+ expect(result).not.toContain("TODO: tapestry-migration")
176
+ expect(result).toContain('data-testid="checkbox"')
177
+ expect(result).toContain('data-cy="test-checkbox"')
178
+ })
179
+ })
180
+
181
+ describe("edge cases", () => {
182
+ it("should not affect checkbox without unsupported props", () => {
183
+ const input = `
184
+ import { Checkbox } from "@planningcenter/tapestry-react"
185
+
186
+ function Test() {
187
+ return <Checkbox label="Test" />
188
+ }
189
+ `.trim()
190
+
191
+ const result = applyTransform(input)
192
+ expect(result).not.toContain("TODO: tapestry-migration")
193
+ expect(result).toBe(input)
194
+ })
195
+
196
+ it("should not affect other components", () => {
197
+ const input = `
198
+ import { Button, Checkbox } from "@planningcenter/tapestry-react"
199
+
200
+ function Test() {
201
+ return (
202
+ <div>
203
+ <Button css={{ color: 'red' }}>Click me</Button>
204
+ <Checkbox label="Test" />
205
+ </div>
206
+ )
207
+ }
208
+ `.trim()
209
+
210
+ const result = applyTransform(input)
211
+ expect(result).not.toContain("TODO: tapestry-migration")
212
+ expect(result).toContain("css={{ color: 'red' }}")
213
+ })
214
+
215
+ it("should handle mixed supported and unsupported props", () => {
216
+ const input = `
217
+ import { Checkbox } from "@planningcenter/tapestry-react"
218
+
219
+ function Test() {
220
+ return (
221
+ <Checkbox
222
+ checked
223
+ css={{ color: 'red' }}
224
+ disabled
225
+ customProp="value"
226
+ label="Test"
227
+ />
228
+ )
229
+ }
230
+ `.trim()
231
+
232
+ const result = applyTransform(input)
233
+ expect(result).toContain("/* TODO: tapestry-migration (css):")
234
+ expect(result).toContain("/* TODO: tapestry-migration (customProp):")
235
+ expect(result).not.toContain("TODO: tapestry-migration.*checked")
236
+ expect(result).not.toContain("TODO: tapestry-migration.*disabled")
237
+ expect(result).not.toContain("TODO: tapestry-migration.*label")
238
+ expect(result).toContain("checked")
239
+ expect(result).toContain("disabled")
240
+ expect(result).toContain('label="Test"')
241
+ })
242
+ })
243
+ })
@@ -0,0 +1,47 @@
1
+ import { JSXAttribute, Transform } from "jscodeshift"
2
+
3
+ import { addCommentToUnsupportedProps } from "../../shared/actions/addCommentToUnsupportedProps"
4
+ import { SUPPORTED_PROPS_BASE } from "../../shared/helpers/unsupportedPropsHelpers"
5
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
6
+
7
+ const CHECKBOX_SPECIFIC_PROPS = [
8
+ "checked",
9
+ "disabled",
10
+ "indeterminate",
11
+ "label",
12
+ "name",
13
+ "onChange",
14
+ "value",
15
+ ]
16
+
17
+ const SUPPORTED_PROPS = [...SUPPORTED_PROPS_BASE, ...CHECKBOX_SPECIFIC_PROPS]
18
+
19
+ const transform: Transform = attributeTransformFactory({
20
+ targetComponent: "Checkbox",
21
+ targetPackage: "@planningcenter/tapestry-react",
22
+ transform: (element, { j }) => {
23
+ const UNSUPPORTED_PROPS = (element.openingElement.attributes || [])
24
+ .filter(
25
+ (attr) =>
26
+ attr.type === "JSXAttribute" &&
27
+ !SUPPORTED_PROPS.includes(attr.name.name as string) &&
28
+ !(attr.name.name as string).startsWith("aria-") &&
29
+ !(attr.name.name as string).startsWith("data-")
30
+ )
31
+ .map((attr) => (attr as JSXAttribute).name.name as string)
32
+
33
+ return addCommentToUnsupportedProps({
34
+ element,
35
+ j,
36
+ messageSuffix: (prop) => {
37
+ if (prop === "css") {
38
+ return "\n * CSS prop is not supported. Use className or style prop instead.\n"
39
+ }
40
+ return ""
41
+ },
42
+ props: UNSUPPORTED_PROPS,
43
+ })
44
+ },
45
+ })
46
+
47
+ export default transform
@@ -1,27 +1,37 @@
1
1
  import { Transform } from "jscodeshift"
2
2
 
3
+ import auditSpreadProps from "./transforms/auditSpreadProps"
3
4
  import childrenToLabel from "./transforms/childrenToLabel"
4
5
  import convertStyleProps from "./transforms/convertStyleProps"
5
- import inlineMemberToKind from "./transforms/inlineMemberToKind"
6
- import inlinePropToKind from "./transforms/inlinePropToKind"
6
+ import innerRefToRef from "./transforms/innerRefToRef"
7
+ import moveLinkImport from "./transforms/moveLinkImport"
7
8
  import removeAs from "./transforms/removeAs"
9
+ import removeInlineMember from "./transforms/removeInlineMember"
10
+ import removeInlineProp from "./transforms/removeInlineProp"
8
11
  import reviewStyles from "./transforms/reviewStyles"
9
12
  import targetBlankToExternal from "./transforms/targetBlankToExternal"
13
+ import tooltipToWrapper from "./transforms/tooltipToWrapper"
10
14
  import toToHref from "./transforms/toToHref"
15
+ import unsupportedProps from "./transforms/unsupportedProps"
11
16
 
12
17
  const transform: Transform = (fileInfo, api, options) => {
13
18
  let currentSource = fileInfo.source
14
19
  let hasAnyChanges = false
15
20
 
16
21
  const transforms: Transform[] = [
17
- inlineMemberToKind,
18
- inlinePropToKind,
22
+ removeInlineMember,
23
+ removeInlineProp,
24
+ tooltipToWrapper,
19
25
  toToHref,
20
26
  targetBlankToExternal,
27
+ innerRefToRef,
21
28
  removeAs,
22
29
  childrenToLabel,
30
+ auditSpreadProps,
23
31
  reviewStyles,
24
32
  convertStyleProps,
33
+ unsupportedProps,
34
+ moveLinkImport,
25
35
  ]
26
36
 
27
37
  for (const individualTransform of transforms) {