@planningcenter/tapestry-migration-cli 2.8.0-rc.14 → 2.8.0-rc.15
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.
- package/package.json +2 -2
- package/src/components/button/transforms/themeVariantToKind.test.ts +1 -1
- package/src/components/radio/index.ts +38 -0
- package/src/components/radio/transforms/auditSpreadProps.test.ts +356 -0
- package/src/components/radio/transforms/auditSpreadProps.ts +10 -0
- package/src/components/radio/transforms/convertStyleProps.test.ts +161 -0
- package/src/components/radio/transforms/convertStyleProps.ts +10 -0
- package/src/components/radio/transforms/moveRadioImport.test.ts +152 -0
- package/src/components/radio/transforms/moveRadioImport.ts +13 -0
- package/src/components/radio/transforms/setDefaultSize.test.ts +287 -0
- package/src/components/radio/transforms/setDefaultSize.ts +28 -0
- package/src/components/radio/transforms/sizeMapping.test.ts +201 -0
- package/src/components/radio/transforms/sizeMapping.ts +49 -0
- package/src/components/radio/transforms/unsupportedProps.test.ts +241 -0
- package/src/components/radio/transforms/unsupportedProps.ts +35 -0
- package/src/components/shared/actions/getAttributeValue.test.ts +18 -0
- package/src/components/shared/actions/getAttributeValue.ts +18 -5
- package/src/components/shared/helpers/unsupportedPropsHelpers.ts +24 -9
- package/src/components/shared/transformFactories/commentOnSpreadPropsFactory.test.ts +84 -0
- package/src/components/shared/transformFactories/commentOnSpreadPropsFactory.ts +26 -0
- package/src/reportGenerator.ts +2 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
|
|
4
|
+
import { getAttributeValue } from "../../shared/actions/getAttributeValue"
|
|
5
|
+
import { hasAttribute } from "../../shared/conditions/hasAttribute"
|
|
6
|
+
import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
|
|
7
|
+
|
|
8
|
+
const SIZE_MAPPING = {
|
|
9
|
+
lg: "md",
|
|
10
|
+
xl: "md",
|
|
11
|
+
xs: "sm",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const transform: Transform = attributeTransformFactory({
|
|
15
|
+
condition: hasAttribute("size"),
|
|
16
|
+
targetComponent: "Radio",
|
|
17
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
18
|
+
transform: (element, { j }) => {
|
|
19
|
+
let hasChanges = false
|
|
20
|
+
|
|
21
|
+
const sizeAttr = element.openingElement.attributes?.find(
|
|
22
|
+
(attr) => attr.type === "JSXAttribute" && attr.name.name === "size"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if (sizeAttr && sizeAttr.type === "JSXAttribute") {
|
|
26
|
+
const sizeValue = getAttributeValue({ attribute: sizeAttr, j })
|
|
27
|
+
|
|
28
|
+
if (sizeValue) {
|
|
29
|
+
const mappedSize = SIZE_MAPPING[sizeValue as keyof typeof SIZE_MAPPING]
|
|
30
|
+
|
|
31
|
+
if (mappedSize && mappedSize !== sizeValue) {
|
|
32
|
+
// Normalize to a plain string attribute value (size="md")
|
|
33
|
+
sizeAttr.value = j.stringLiteral(mappedSize)
|
|
34
|
+
hasChanges = true
|
|
35
|
+
|
|
36
|
+
addCommentToAttribute({
|
|
37
|
+
attribute: sizeAttr,
|
|
38
|
+
j,
|
|
39
|
+
text: `Size "${sizeValue}" was mapped to "${mappedSize}". Verify visual appearance as sizes may differ slightly.`,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return hasChanges
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export default transform
|
|
@@ -0,0 +1,241 @@
|
|
|
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 { Radio } from "@planningcenter/tapestry-react"
|
|
23
|
+
|
|
24
|
+
function Test() {
|
|
25
|
+
return <Radio 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 { Radio } from "@planningcenter/tapestry-react"
|
|
42
|
+
|
|
43
|
+
function Test() {
|
|
44
|
+
return <Radio 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 { Radio } from "@planningcenter/tapestry-react"
|
|
58
|
+
|
|
59
|
+
function Test() {
|
|
60
|
+
return (
|
|
61
|
+
<Radio
|
|
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 radio props", () => {
|
|
79
|
+
const input = `
|
|
80
|
+
import { Radio } from "@planningcenter/tapestry-react"
|
|
81
|
+
|
|
82
|
+
function Test() {
|
|
83
|
+
const ref = React.useRef()
|
|
84
|
+
return (
|
|
85
|
+
<Radio
|
|
86
|
+
checked
|
|
87
|
+
disabled
|
|
88
|
+
ref={ref}
|
|
89
|
+
label="Test"
|
|
90
|
+
name="radio"
|
|
91
|
+
onChange={() => {}}
|
|
92
|
+
value="test"
|
|
93
|
+
/>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
`.trim()
|
|
97
|
+
|
|
98
|
+
const result = applyTransform(input)
|
|
99
|
+
expect(result).not.toContain("TODO: tapestry-migration")
|
|
100
|
+
expect(result).toContain("checked")
|
|
101
|
+
expect(result).toContain("disabled")
|
|
102
|
+
expect(result).toContain("ref={ref}")
|
|
103
|
+
expect(result).toContain('label="Test"')
|
|
104
|
+
expect(result).toContain('name="radio"')
|
|
105
|
+
expect(result).toContain("onChange={() => {}}")
|
|
106
|
+
expect(result).toContain('value="test"')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it("should not add comments for common props", () => {
|
|
110
|
+
const input = `
|
|
111
|
+
import { Radio } from "@planningcenter/tapestry-react"
|
|
112
|
+
|
|
113
|
+
function Test() {
|
|
114
|
+
return (
|
|
115
|
+
<Radio
|
|
116
|
+
className="test"
|
|
117
|
+
id="radio"
|
|
118
|
+
key="key"
|
|
119
|
+
style={{ color: 'red' }}
|
|
120
|
+
tabIndex={0}
|
|
121
|
+
label="Test"
|
|
122
|
+
/>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
`.trim()
|
|
126
|
+
|
|
127
|
+
const result = applyTransform(input)
|
|
128
|
+
expect(result).not.toContain("TODO: tapestry-migration")
|
|
129
|
+
expect(result).toContain('className="test"')
|
|
130
|
+
expect(result).toContain('id="radio"')
|
|
131
|
+
expect(result).toContain('key="key"')
|
|
132
|
+
expect(result).toContain("style={{ color: 'red' }}")
|
|
133
|
+
expect(result).toContain("tabIndex={0}")
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it("should not add comments for aria props", () => {
|
|
137
|
+
const input = `
|
|
138
|
+
import { Radio } from "@planningcenter/tapestry-react"
|
|
139
|
+
|
|
140
|
+
function Test() {
|
|
141
|
+
return (
|
|
142
|
+
<Radio
|
|
143
|
+
aria-label="Test radio"
|
|
144
|
+
aria-describedby="description"
|
|
145
|
+
label="Test"
|
|
146
|
+
/>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
`.trim()
|
|
150
|
+
|
|
151
|
+
const result = applyTransform(input)
|
|
152
|
+
expect(result).not.toContain("TODO: tapestry-migration")
|
|
153
|
+
expect(result).toContain('aria-label="Test radio"')
|
|
154
|
+
expect(result).toContain('aria-describedby="description"')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it("should not add comments for data props", () => {
|
|
158
|
+
const input = `
|
|
159
|
+
import { Radio } from "@planningcenter/tapestry-react"
|
|
160
|
+
|
|
161
|
+
function Test() {
|
|
162
|
+
return (
|
|
163
|
+
<Radio
|
|
164
|
+
data-testid="radio"
|
|
165
|
+
data-cy="test-radio"
|
|
166
|
+
label="Test"
|
|
167
|
+
/>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
`.trim()
|
|
171
|
+
|
|
172
|
+
const result = applyTransform(input)
|
|
173
|
+
expect(result).not.toContain("TODO: tapestry-migration")
|
|
174
|
+
expect(result).toContain('data-testid="radio"')
|
|
175
|
+
expect(result).toContain('data-cy="test-radio"')
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
describe("edge cases", () => {
|
|
180
|
+
it("should not affect radio without unsupported props", () => {
|
|
181
|
+
const input = `
|
|
182
|
+
import { Radio } from "@planningcenter/tapestry-react"
|
|
183
|
+
|
|
184
|
+
function Test() {
|
|
185
|
+
return <Radio label="Test" />
|
|
186
|
+
}
|
|
187
|
+
`.trim()
|
|
188
|
+
|
|
189
|
+
const result = applyTransform(input)
|
|
190
|
+
expect(result).not.toContain("TODO: tapestry-migration")
|
|
191
|
+
expect(result).toBe(input)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it("should not affect other components", () => {
|
|
195
|
+
const input = `
|
|
196
|
+
import { Button, Radio } from "@planningcenter/tapestry-react"
|
|
197
|
+
|
|
198
|
+
function Test() {
|
|
199
|
+
return (
|
|
200
|
+
<div>
|
|
201
|
+
<Button css={{ color: 'red' }}>Click me</Button>
|
|
202
|
+
<Radio label="Test" />
|
|
203
|
+
</div>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
`.trim()
|
|
207
|
+
|
|
208
|
+
const result = applyTransform(input)
|
|
209
|
+
expect(result).not.toContain("TODO: tapestry-migration")
|
|
210
|
+
expect(result).toContain("css={{ color: 'red' }}")
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it("should handle mixed supported and unsupported props", () => {
|
|
214
|
+
const input = `
|
|
215
|
+
import { Radio } from "@planningcenter/tapestry-react"
|
|
216
|
+
|
|
217
|
+
function Test() {
|
|
218
|
+
return (
|
|
219
|
+
<Radio
|
|
220
|
+
checked
|
|
221
|
+
css={{ color: 'red' }}
|
|
222
|
+
disabled
|
|
223
|
+
customProp="value"
|
|
224
|
+
label="Test"
|
|
225
|
+
/>
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
`.trim()
|
|
229
|
+
|
|
230
|
+
const result = applyTransform(input)
|
|
231
|
+
expect(result).toContain("/* TODO: tapestry-migration (css):")
|
|
232
|
+
expect(result).toContain("/* TODO: tapestry-migration (customProp):")
|
|
233
|
+
expect(result).not.toContain("TODO: tapestry-migration.*checked")
|
|
234
|
+
expect(result).not.toContain("TODO: tapestry-migration.*disabled")
|
|
235
|
+
expect(result).not.toContain("TODO: tapestry-migration.*label")
|
|
236
|
+
expect(result).toContain("checked")
|
|
237
|
+
expect(result).toContain("disabled")
|
|
238
|
+
expect(result).toContain('label="Test"')
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { JSXAttribute, Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { addCommentToUnsupportedProps } from "../../shared/actions/addCommentToUnsupportedProps"
|
|
4
|
+
import { CHECKBOX_RADIO_SUPPORTED_PROPS } from "../../shared/helpers/unsupportedPropsHelpers"
|
|
5
|
+
import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
|
|
6
|
+
|
|
7
|
+
const transform: Transform = attributeTransformFactory({
|
|
8
|
+
targetComponent: "Radio",
|
|
9
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
10
|
+
transform: (element, { j }) => {
|
|
11
|
+
const UNSUPPORTED_PROPS = (element.openingElement.attributes || [])
|
|
12
|
+
.filter(
|
|
13
|
+
(attr) =>
|
|
14
|
+
attr.type === "JSXAttribute" &&
|
|
15
|
+
!CHECKBOX_RADIO_SUPPORTED_PROPS.includes(attr.name.name as string) &&
|
|
16
|
+
!(attr.name.name as string).startsWith("aria-") &&
|
|
17
|
+
!(attr.name.name as string).startsWith("data-")
|
|
18
|
+
)
|
|
19
|
+
.map((attr) => (attr as JSXAttribute).name.name as string)
|
|
20
|
+
|
|
21
|
+
return addCommentToUnsupportedProps({
|
|
22
|
+
element,
|
|
23
|
+
j,
|
|
24
|
+
messageSuffix: (prop) => {
|
|
25
|
+
if (prop === "css") {
|
|
26
|
+
return "\n * CSS prop is not supported. Use className or style prop instead.\n"
|
|
27
|
+
}
|
|
28
|
+
return ""
|
|
29
|
+
},
|
|
30
|
+
props: UNSUPPORTED_PROPS,
|
|
31
|
+
})
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
export default transform
|
|
@@ -74,6 +74,24 @@ describe("getAttributeValue", () => {
|
|
|
74
74
|
})
|
|
75
75
|
|
|
76
76
|
describe("expression container values", () => {
|
|
77
|
+
it("should extract string literal from JSX expression container", () => {
|
|
78
|
+
const element = createElementFromCode('<Button size={"lg"}>Save</Button>')
|
|
79
|
+
const sizeAttribute = getAttributeFromElement(element, "size")
|
|
80
|
+
|
|
81
|
+
const value = getAttributeValue({ attribute: sizeAttribute, j })
|
|
82
|
+
|
|
83
|
+
expect(value).toBe("lg")
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it("should extract string literal with single quotes from JSX expression container", () => {
|
|
87
|
+
const element = createElementFromCode("<Button size={'md'}>Save</Button>")
|
|
88
|
+
const sizeAttribute = getAttributeFromElement(element, "size")
|
|
89
|
+
|
|
90
|
+
const value = getAttributeValue({ attribute: sizeAttribute, j })
|
|
91
|
+
|
|
92
|
+
expect(value).toBe("md")
|
|
93
|
+
})
|
|
94
|
+
|
|
77
95
|
it("should convert boolean expression to source code", () => {
|
|
78
96
|
const element = createElementFromCode(
|
|
79
97
|
"<Button disabled={true}>Save</Button>"
|
|
@@ -7,9 +7,22 @@ export function getAttributeValue({
|
|
|
7
7
|
attribute: JSXAttribute | null | undefined
|
|
8
8
|
j: JSCodeshift
|
|
9
9
|
}) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
if (!attribute || !attribute.value) {
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Handle string literal: attribute="value"
|
|
15
|
+
if (attribute.value.type === "StringLiteral") {
|
|
16
|
+
return attribute.value.value
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Handle JSX expression container with string literal: attribute={"value"}
|
|
20
|
+
if (attribute.value.type === "JSXExpressionContainer") {
|
|
21
|
+
if (attribute.value.expression.type === "StringLiteral") {
|
|
22
|
+
return attribute.value.expression.value
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// For all other cases, return the source code representation
|
|
27
|
+
return j(attribute.value).toSource()
|
|
15
28
|
}
|
|
@@ -8,8 +8,13 @@ export const COMMON_PROPS = [
|
|
|
8
8
|
"className",
|
|
9
9
|
"id",
|
|
10
10
|
"key",
|
|
11
|
-
"kind",
|
|
12
11
|
"label",
|
|
12
|
+
"ref",
|
|
13
|
+
"role",
|
|
14
|
+
"size",
|
|
15
|
+
"style",
|
|
16
|
+
"tabIndex",
|
|
17
|
+
"title",
|
|
13
18
|
"onBlur",
|
|
14
19
|
"onFocus",
|
|
15
20
|
"onClick",
|
|
@@ -19,17 +24,27 @@ export const COMMON_PROPS = [
|
|
|
19
24
|
"onMouseOut",
|
|
20
25
|
"onMouseOver",
|
|
21
26
|
"onMouseUp",
|
|
22
|
-
"prefix",
|
|
23
|
-
"ref",
|
|
24
|
-
"role",
|
|
25
|
-
"size",
|
|
26
|
-
"style",
|
|
27
|
-
"suffix",
|
|
28
|
-
"tabIndex",
|
|
29
|
-
"title",
|
|
30
27
|
]
|
|
31
28
|
|
|
29
|
+
export const BUTTON_LINK_SHARED_PROPS = ["kind", "prefix", "suffix"]
|
|
30
|
+
|
|
32
31
|
export const SUPPORTED_PROPS_BASE = [
|
|
32
|
+
...COMMON_PROPS,
|
|
33
|
+
...BUTTON_LINK_SHARED_PROPS,
|
|
33
34
|
...STYLE_PROP_NAMES_WITHOUT_CSS,
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
export const CHECKBOX_RADIO_SHARED_PROPS = [
|
|
38
|
+
"checked",
|
|
39
|
+
"disabled",
|
|
40
|
+
"name",
|
|
41
|
+
"onChange",
|
|
42
|
+
"required",
|
|
43
|
+
"value",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
export const CHECKBOX_RADIO_SUPPORTED_PROPS = [
|
|
34
47
|
...COMMON_PROPS,
|
|
48
|
+
...CHECKBOX_RADIO_SHARED_PROPS,
|
|
49
|
+
...STYLE_PROP_NAMES_WITHOUT_CSS,
|
|
35
50
|
]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import { commentOnSpreadPropsFactory } from "./commentOnSpreadPropsFactory"
|
|
5
|
+
|
|
6
|
+
const j = jscodeshift.withParser("tsx")
|
|
7
|
+
|
|
8
|
+
const COMMENT =
|
|
9
|
+
"Spread props can contain unsupported props, please explore usages and migrate as needed."
|
|
10
|
+
|
|
11
|
+
describe("commentOnSpreadPropsFactory", () => {
|
|
12
|
+
it("should add comments to spread props", () => {
|
|
13
|
+
const transform = commentOnSpreadPropsFactory({
|
|
14
|
+
targetComponent: "Button",
|
|
15
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const fileInfo = {
|
|
19
|
+
path: "test.tsx",
|
|
20
|
+
source: `import { Button } from "@planningcenter/tapestry-react";
|
|
21
|
+
<Button {...props} label="Save" />`,
|
|
22
|
+
}
|
|
23
|
+
const api = { j, jscodeshift: j, report: () => {}, stats: () => {} }
|
|
24
|
+
|
|
25
|
+
const result = transform(fileInfo, api, {})
|
|
26
|
+
expect(result).toContain(COMMENT)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("should only transform components from the specified package", () => {
|
|
30
|
+
const transform = commentOnSpreadPropsFactory({
|
|
31
|
+
targetComponent: "Button",
|
|
32
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const fileInfo = {
|
|
36
|
+
path: "test.tsx",
|
|
37
|
+
source: `import { Link } from "@planningcenter/tapestry-react";
|
|
38
|
+
<Link {...props} />`,
|
|
39
|
+
}
|
|
40
|
+
const api = { j, jscodeshift: j, report: () => {}, stats: () => {} }
|
|
41
|
+
|
|
42
|
+
const result = transform(fileInfo, api, {})
|
|
43
|
+
expect(result).toBe(null)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it("should return null when no spread props are present", () => {
|
|
47
|
+
const transform = commentOnSpreadPropsFactory({
|
|
48
|
+
targetComponent: "Button",
|
|
49
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const fileInfo = {
|
|
53
|
+
path: "test.tsx",
|
|
54
|
+
source: `import { Button } from "@planningcenter/tapestry-react";
|
|
55
|
+
<Button label="Save" />`,
|
|
56
|
+
}
|
|
57
|
+
const api = { j, jscodeshift: j, report: () => {}, stats: () => {} }
|
|
58
|
+
|
|
59
|
+
const result = transform(fileInfo, api, {})
|
|
60
|
+
expect(result).toBe(null)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("should handle multiple spread props", () => {
|
|
64
|
+
const transform = commentOnSpreadPropsFactory({
|
|
65
|
+
targetComponent: "Button",
|
|
66
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const fileInfo = {
|
|
70
|
+
path: "test.tsx",
|
|
71
|
+
source: `import { Button } from "@planningcenter/tapestry-react";
|
|
72
|
+
<Button {...props1} {...props2} label="Save" />`,
|
|
73
|
+
}
|
|
74
|
+
const api = { j, jscodeshift: j, report: () => {}, stats: () => {} }
|
|
75
|
+
|
|
76
|
+
const result = transform(fileInfo, api, {})
|
|
77
|
+
expect(result).toContain(COMMENT)
|
|
78
|
+
// Should have the comment twice (once per spread prop)
|
|
79
|
+
const matches = result?.match(
|
|
80
|
+
new RegExp(COMMENT.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g")
|
|
81
|
+
)
|
|
82
|
+
expect(matches).toHaveLength(2)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { addCommentToAttribute } from "../actions/addCommentToAttribute"
|
|
4
|
+
import { getSpreadProps } from "../actions/getSpreadProps"
|
|
5
|
+
import { hasSpreadProps } from "../actions/hasSpreadProps"
|
|
6
|
+
import { attributeTransformFactory } from "./attributeTransformFactory"
|
|
7
|
+
|
|
8
|
+
const COMMENT =
|
|
9
|
+
"Spread props can contain unsupported props, please explore usages and migrate as needed."
|
|
10
|
+
|
|
11
|
+
export function commentOnSpreadPropsFactory(options: {
|
|
12
|
+
targetComponent: string
|
|
13
|
+
targetPackage: string
|
|
14
|
+
}): Transform {
|
|
15
|
+
return attributeTransformFactory({
|
|
16
|
+
condition: hasSpreadProps,
|
|
17
|
+
transform: (element, { j }) => {
|
|
18
|
+
const spreadProps = getSpreadProps(element)
|
|
19
|
+
spreadProps.forEach((prop) =>
|
|
20
|
+
addCommentToAttribute({ attribute: prop, j, text: COMMENT })
|
|
21
|
+
)
|
|
22
|
+
return spreadProps.length > 0
|
|
23
|
+
},
|
|
24
|
+
...options,
|
|
25
|
+
})
|
|
26
|
+
}
|
package/src/reportGenerator.ts
CHANGED
|
@@ -30,6 +30,8 @@ export const GLOBAL_MESSAGES = {
|
|
|
30
30
|
"Buttons inside of Tapestry Group components are not supported and should not be migrated at this time.",
|
|
31
31
|
checkbox:
|
|
32
32
|
"Checkbox sizes have adjusted. Verify visual appearance as sizes may differ slightly.",
|
|
33
|
+
radio:
|
|
34
|
+
"Radio sizes have adjusted. Verify visual appearance as sizes may differ slightly.",
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export const WEIGHT_CONFIGS: { [key: string]: WeightConfig } = {
|