@planningcenter/tapestry-migration-cli 3.1.0-rc.2 → 3.1.0-rc.21
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/dist/tapestry-react-shim.cjs +7 -1
- package/package.json +3 -3
- package/src/components/button/transforms/convertStyleProps.test.ts +97 -0
- package/src/components/button/transforms/removeTypeButton.test.ts +0 -1
- package/src/components/checkbox/transforms/moveCheckboxImport.test.ts +3 -0
- package/src/components/input/index.ts +66 -0
- package/src/components/input/transformableInput.ts +49 -0
- package/src/components/input/transforms/auditSpreadProps.test.ts +192 -0
- package/src/components/input/transforms/auditSpreadProps.ts +26 -0
- package/src/components/input/transforms/autoWidthTransform.test.ts +172 -0
- package/src/components/input/transforms/autoWidthTransform.ts +41 -0
- package/src/components/input/transforms/convertStyleProps.test.ts +128 -0
- package/src/components/input/transforms/convertStyleProps.ts +12 -0
- package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.test.ts +186 -0
- package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.ts +27 -0
- package/src/components/input/transforms/inputLabelToLabelProp.test.ts +319 -0
- package/src/components/input/transforms/inputLabelToLabelProp.ts +203 -0
- package/src/components/input/transforms/mergeFieldIntoInput.test.ts +469 -0
- package/src/components/input/transforms/mergeFieldIntoInput.ts +7 -0
- package/src/components/input/transforms/mergeInputLabel.test.ts +458 -0
- package/src/components/input/transforms/mergeInputLabel.ts +204 -0
- package/src/components/input/transforms/moveInputImport.test.ts +166 -0
- package/src/components/input/transforms/moveInputImport.ts +14 -0
- package/src/components/input/transforms/numberFieldAddTypeNumber.test.ts +92 -0
- package/src/components/input/transforms/numberFieldAddTypeNumber.ts +14 -0
- package/src/components/input/transforms/numberFieldRenameToInput.test.ts +126 -0
- package/src/components/input/transforms/numberFieldRenameToInput.ts +9 -0
- package/src/components/input/transforms/removeAsInput.test.ts +139 -0
- package/src/components/input/transforms/removeAsInput.ts +20 -0
- package/src/components/input/transforms/removeDuplicateKeys.test.ts +302 -0
- package/src/components/input/transforms/removeDuplicateKeys.ts +10 -0
- package/src/components/input/transforms/removeInputBox.test.ts +352 -0
- package/src/components/input/transforms/removeInputBox.ts +109 -0
- package/src/components/input/transforms/removeRedundantAriaLabel.test.ts +128 -0
- package/src/components/input/transforms/removeRedundantAriaLabel.ts +21 -0
- package/src/components/input/transforms/removeTypeInput.test.ts +212 -0
- package/src/components/input/transforms/removeTypeInput.ts +22 -0
- package/src/components/input/transforms/removeTypeText.test.ts +160 -0
- package/src/components/input/transforms/removeTypeText.ts +17 -0
- package/src/components/input/transforms/sizeMapping.test.ts +198 -0
- package/src/components/input/transforms/sizeMapping.ts +17 -0
- package/src/components/input/transforms/skipRenderSideProps.test.ts +236 -0
- package/src/components/input/transforms/skipRenderSideProps.ts +27 -0
- package/src/components/input/transforms/stateToInvalid.test.ts +208 -0
- package/src/components/input/transforms/stateToInvalid.ts +59 -0
- package/src/components/input/transforms/stateToInvalidTernary.test.ts +159 -0
- package/src/components/input/transforms/stateToInvalidTernary.ts +13 -0
- package/src/components/input/transforms/unsupportedProps.test.ts +566 -0
- package/src/components/input/transforms/unsupportedProps.ts +84 -0
- package/src/components/link/transforms/reviewStyles.test.ts +0 -1
- package/src/components/select/index.ts +58 -0
- package/src/components/select/transformableSelect.ts +7 -0
- package/src/components/select/transforms/auditSpreadProps.test.ts +103 -0
- package/src/components/select/transforms/auditSpreadProps.ts +26 -0
- package/src/components/select/transforms/childrenToOptions.test.ts +367 -0
- package/src/components/select/transforms/childrenToOptions.ts +295 -0
- package/src/components/select/transforms/convertLegacyOptions.test.ts +150 -0
- package/src/components/select/transforms/convertLegacyOptions.ts +105 -0
- package/src/components/select/transforms/convertStyleProps.test.ts +73 -0
- package/src/components/select/transforms/convertStyleProps.ts +12 -0
- package/src/components/select/transforms/emptyValueToPlaceholder.test.ts +122 -0
- package/src/components/select/transforms/emptyValueToPlaceholder.ts +22 -0
- package/src/components/select/transforms/innerRefToRef.test.ts +89 -0
- package/src/components/select/transforms/innerRefToRef.ts +18 -0
- package/src/components/select/transforms/mapChildrenToOptions.test.ts +521 -0
- package/src/components/select/transforms/mapChildrenToOptions.ts +312 -0
- package/src/components/select/transforms/mergeFieldIntoSelect.test.ts +506 -0
- package/src/components/select/transforms/mergeFieldIntoSelect.ts +7 -0
- package/src/components/select/transforms/mergeSelectLabel.test.ts +458 -0
- package/src/components/select/transforms/mergeSelectLabel.ts +225 -0
- package/src/components/select/transforms/moveSelectImport.test.ts +148 -0
- package/src/components/select/transforms/moveSelectImport.ts +14 -0
- package/src/components/select/transforms/removeDefaultProps.test.ts +249 -0
- package/src/components/select/transforms/removeDefaultProps.ts +112 -0
- package/src/components/select/transforms/sizeMapping.test.ts +188 -0
- package/src/components/select/transforms/sizeMapping.ts +17 -0
- package/src/components/select/transforms/skipMultipleSelect.test.ts +148 -0
- package/src/components/select/transforms/skipMultipleSelect.ts +23 -0
- package/src/components/select/transforms/stateToInvalid.test.ts +217 -0
- package/src/components/select/transforms/stateToInvalid.ts +59 -0
- package/src/components/select/transforms/stateToInvalidTernary.test.ts +146 -0
- package/src/components/select/transforms/stateToInvalidTernary.ts +13 -0
- package/src/components/select/transforms/unsupportedProps.test.ts +252 -0
- package/src/components/select/transforms/unsupportedProps.ts +44 -0
- package/src/components/shared/helpers/getAttributeExpression.ts +26 -0
- package/src/components/shared/helpers/unsupportedPropsHelpers.ts +102 -0
- package/src/components/shared/transformFactories/helpers/manageImports.ts +14 -12
- package/src/components/shared/transformFactories/mergeFieldFactory.ts +244 -0
- package/src/components/shared/transformFactories/sizeMappingFactory.ts +9 -2
- package/src/components/shared/transformFactories/stylePropTransformFactory.ts +56 -17
- package/src/components/shared/transformFactories/ternaryConditionalToPropFactory.ts +65 -0
- package/src/components/text-area/index.ts +48 -0
- package/src/components/text-area/transforms/auditSpreadProps.test.ts +139 -0
- package/src/components/text-area/transforms/auditSpreadProps.ts +10 -0
- package/src/components/text-area/transforms/convertStyleProps.test.ts +158 -0
- package/src/components/text-area/transforms/convertStyleProps.ts +10 -0
- package/src/components/text-area/transforms/innerRefToRef.test.ts +206 -0
- package/src/components/text-area/transforms/innerRefToRef.ts +14 -0
- package/src/components/text-area/transforms/mergeFieldIntoTextArea.test.ts +477 -0
- package/src/components/text-area/transforms/mergeFieldIntoTextArea.ts +5 -0
- package/src/components/text-area/transforms/moveTextAreaImport.test.ts +168 -0
- package/src/components/text-area/transforms/moveTextAreaImport.ts +13 -0
- package/src/components/text-area/transforms/removeDuplicateKeys.test.ts +129 -0
- package/src/components/text-area/transforms/removeDuplicateKeys.ts +8 -0
- package/src/components/text-area/transforms/removeRedundantAriaLabel.test.ts +183 -0
- package/src/components/text-area/transforms/removeRedundantAriaLabel.ts +59 -0
- package/src/components/text-area/transforms/sizeMapping.test.ts +199 -0
- package/src/components/text-area/transforms/sizeMapping.ts +15 -0
- package/src/components/text-area/transforms/stateToInvalid.test.ts +204 -0
- package/src/components/text-area/transforms/stateToInvalid.ts +57 -0
- package/src/components/text-area/transforms/stateToInvalidTernary.test.ts +133 -0
- package/src/components/text-area/transforms/stateToInvalidTernary.ts +11 -0
- package/src/components/text-area/transforms/unsupportedProps.test.ts +275 -0
- package/src/components/text-area/transforms/unsupportedProps.ts +35 -0
- package/src/index.ts +2 -1
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./inputLabelToLabelProp"
|
|
5
|
+
|
|
6
|
+
const j = jscodeshift.withParser("tsx")
|
|
7
|
+
|
|
8
|
+
function applyTransform(source: string): string | null {
|
|
9
|
+
const fileInfo = { path: "test.tsx", source }
|
|
10
|
+
return transform(
|
|
11
|
+
fileInfo,
|
|
12
|
+
{ j, jscodeshift: j, report: () => {}, stats: () => {} },
|
|
13
|
+
{}
|
|
14
|
+
) as string | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("inputLabelToLabelProp transform", () => {
|
|
18
|
+
describe("successful conversions", () => {
|
|
19
|
+
it("InputLabel before Input → label prop added, InputLabel removed", () => {
|
|
20
|
+
const input = `
|
|
21
|
+
import { Input, StackView } from "@planningcenter/tapestry-react"
|
|
22
|
+
|
|
23
|
+
function Test() {
|
|
24
|
+
return (
|
|
25
|
+
<StackView>
|
|
26
|
+
<Input.InputLabel htmlFor="f1">Name</Input.InputLabel>
|
|
27
|
+
<Input id="f1" />
|
|
28
|
+
</StackView>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
`.trim()
|
|
32
|
+
|
|
33
|
+
const result = applyTransform(input)
|
|
34
|
+
expect(result).not.toBeNull()
|
|
35
|
+
expect(result).toContain('label="Name"')
|
|
36
|
+
expect(result).not.toContain("<Input.InputLabel")
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("Input before InputLabel → label prop added, InputLabel removed", () => {
|
|
40
|
+
const input = `
|
|
41
|
+
import { Input, StackView } from "@planningcenter/tapestry-react"
|
|
42
|
+
|
|
43
|
+
function Test() {
|
|
44
|
+
return (
|
|
45
|
+
<StackView>
|
|
46
|
+
<Input id="f1" />
|
|
47
|
+
<Input.InputLabel htmlFor="f1">Email</Input.InputLabel>
|
|
48
|
+
</StackView>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
`.trim()
|
|
52
|
+
|
|
53
|
+
const result = applyTransform(input)
|
|
54
|
+
expect(result).not.toBeNull()
|
|
55
|
+
expect(result).toContain('label="Email"')
|
|
56
|
+
expect(result).not.toContain("<Input.InputLabel")
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it("no axis prop (default vertical) → transforms", () => {
|
|
60
|
+
const input = `
|
|
61
|
+
import { Input, StackView } from "@planningcenter/tapestry-react"
|
|
62
|
+
|
|
63
|
+
function Test() {
|
|
64
|
+
return (
|
|
65
|
+
<StackView>
|
|
66
|
+
<Input.InputLabel>Address</Input.InputLabel>
|
|
67
|
+
<Input id="addr" />
|
|
68
|
+
</StackView>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
`.trim()
|
|
72
|
+
|
|
73
|
+
const result = applyTransform(input)
|
|
74
|
+
expect(result).not.toBeNull()
|
|
75
|
+
expect(result).toContain('label="Address"')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('axis="vertical" explicitly → transforms', () => {
|
|
79
|
+
const input = `
|
|
80
|
+
import { Input, StackView } from "@planningcenter/tapestry-react"
|
|
81
|
+
|
|
82
|
+
function Test() {
|
|
83
|
+
return (
|
|
84
|
+
<StackView axis="vertical">
|
|
85
|
+
<Input.InputLabel>City</Input.InputLabel>
|
|
86
|
+
<Input id="city" />
|
|
87
|
+
</StackView>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
`.trim()
|
|
91
|
+
|
|
92
|
+
const result = applyTransform(input)
|
|
93
|
+
expect(result).not.toBeNull()
|
|
94
|
+
expect(result).toContain('label="City"')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it("axis={someVar} (dynamic, non-string-literal) → transforms (best-effort)", () => {
|
|
98
|
+
const input = `
|
|
99
|
+
import { Input, StackView } from "@planningcenter/tapestry-react"
|
|
100
|
+
|
|
101
|
+
function Test() {
|
|
102
|
+
return (
|
|
103
|
+
<StackView axis={someVar}>
|
|
104
|
+
<Input.InputLabel>State</Input.InputLabel>
|
|
105
|
+
<Input id="state" />
|
|
106
|
+
</StackView>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
`.trim()
|
|
110
|
+
|
|
111
|
+
const result = applyTransform(input)
|
|
112
|
+
expect(result).not.toBeNull()
|
|
113
|
+
expect(result).toContain('label="State"')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it("multiple label+input pairs in one StackView → both transformed", () => {
|
|
117
|
+
const input = `
|
|
118
|
+
import { Input, StackView } from "@planningcenter/tapestry-react"
|
|
119
|
+
|
|
120
|
+
function Test() {
|
|
121
|
+
return (
|
|
122
|
+
<StackView>
|
|
123
|
+
<Input.InputLabel>First Name</Input.InputLabel>
|
|
124
|
+
<Input id="first" />
|
|
125
|
+
<Input.InputLabel>Last Name</Input.InputLabel>
|
|
126
|
+
<Input id="last" />
|
|
127
|
+
</StackView>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
`.trim()
|
|
131
|
+
|
|
132
|
+
const result = applyTransform(input)
|
|
133
|
+
expect(result).not.toBeNull()
|
|
134
|
+
expect(result).toContain('label="First Name"')
|
|
135
|
+
expect(result).toContain('label="Last Name"')
|
|
136
|
+
expect(result).not.toContain("<Input.InputLabel")
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe("no-op / comment cases", () => {
|
|
141
|
+
it('axis="horizontal" → no transform, returns null', () => {
|
|
142
|
+
const input = `
|
|
143
|
+
import { Input, StackView } from "@planningcenter/tapestry-react"
|
|
144
|
+
|
|
145
|
+
function Test() {
|
|
146
|
+
return (
|
|
147
|
+
<StackView axis="horizontal">
|
|
148
|
+
<Input.InputLabel>Name</Input.InputLabel>
|
|
149
|
+
<Input id="f1" />
|
|
150
|
+
</StackView>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
`.trim()
|
|
154
|
+
|
|
155
|
+
const result = applyTransform(input)
|
|
156
|
+
expect(result).toBeNull()
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it("Input has renderLeft → no transform for that pair, returns null", () => {
|
|
160
|
+
const input = `
|
|
161
|
+
import { Input, StackView } from "@planningcenter/tapestry-react"
|
|
162
|
+
|
|
163
|
+
function Test() {
|
|
164
|
+
return (
|
|
165
|
+
<StackView>
|
|
166
|
+
<Input.InputLabel>Name</Input.InputLabel>
|
|
167
|
+
<Input id="f1" renderLeft={() => <span />} />
|
|
168
|
+
</StackView>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
`.trim()
|
|
172
|
+
|
|
173
|
+
const result = applyTransform(input)
|
|
174
|
+
expect(result).toBeNull()
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it("Input has renderRight → no transform for that pair, returns null", () => {
|
|
178
|
+
const input = `
|
|
179
|
+
import { Input, StackView } from "@planningcenter/tapestry-react"
|
|
180
|
+
|
|
181
|
+
function Test() {
|
|
182
|
+
return (
|
|
183
|
+
<StackView>
|
|
184
|
+
<Input.InputLabel>Name</Input.InputLabel>
|
|
185
|
+
<Input id="f1" renderRight={() => <span />} />
|
|
186
|
+
</StackView>
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
`.trim()
|
|
190
|
+
|
|
191
|
+
const result = applyTransform(input)
|
|
192
|
+
expect(result).toBeNull()
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it("Input already has label prop → TODO comment added, InputLabel not removed", () => {
|
|
196
|
+
const input = `
|
|
197
|
+
import { Input, StackView } from "@planningcenter/tapestry-react"
|
|
198
|
+
|
|
199
|
+
function Test() {
|
|
200
|
+
return (
|
|
201
|
+
<StackView>
|
|
202
|
+
<Input.InputLabel>New Label</Input.InputLabel>
|
|
203
|
+
<Input id="f1" label="Existing" />
|
|
204
|
+
</StackView>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
`.trim()
|
|
208
|
+
|
|
209
|
+
const result = applyTransform(input)
|
|
210
|
+
expect(result).not.toBeNull()
|
|
211
|
+
expect(result).toContain(
|
|
212
|
+
"TODO: tapestry-migration (inputLabelToLabelProp)"
|
|
213
|
+
)
|
|
214
|
+
expect(result).toContain("already has a label prop")
|
|
215
|
+
expect(result).toContain("New Label")
|
|
216
|
+
expect(result).toContain('label="Existing"')
|
|
217
|
+
// Should not add a second label attribute
|
|
218
|
+
expect(result?.match(/label=/g)?.length).toBe(1)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it("InputLabel has complex children (JSX nodes) → TODO comment added, InputLabel not removed", () => {
|
|
222
|
+
const input = `
|
|
223
|
+
import { Input, StackView } from "@planningcenter/tapestry-react"
|
|
224
|
+
|
|
225
|
+
function Test() {
|
|
226
|
+
return (
|
|
227
|
+
<StackView>
|
|
228
|
+
<Input.InputLabel><span>Name <em>*</em></span></Input.InputLabel>
|
|
229
|
+
<Input id="f1" />
|
|
230
|
+
</StackView>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
`.trim()
|
|
234
|
+
|
|
235
|
+
const result = applyTransform(input)
|
|
236
|
+
expect(result).not.toBeNull()
|
|
237
|
+
expect(result).toContain(
|
|
238
|
+
"TODO: tapestry-migration (inputLabelToLabelProp)"
|
|
239
|
+
)
|
|
240
|
+
expect(result).toContain("complex and cannot be auto-converted")
|
|
241
|
+
expect(result).toContain("<Input.InputLabel")
|
|
242
|
+
expect(result).not.toContain('label="')
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it("Input and InputLabel not adjacent (other element between them) → no transform", () => {
|
|
246
|
+
const input = `
|
|
247
|
+
import { Input, StackView } from "@planningcenter/tapestry-react"
|
|
248
|
+
|
|
249
|
+
function Test() {
|
|
250
|
+
return (
|
|
251
|
+
<StackView>
|
|
252
|
+
<Input.InputLabel>Name</Input.InputLabel>
|
|
253
|
+
<span>hint</span>
|
|
254
|
+
<Input id="f1" />
|
|
255
|
+
</StackView>
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
`.trim()
|
|
259
|
+
|
|
260
|
+
const result = applyTransform(input)
|
|
261
|
+
expect(result).toBeNull()
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it("no StackView wrapper → no transform, returns null", () => {
|
|
265
|
+
const input = `
|
|
266
|
+
import { Input, StackView } from "@planningcenter/tapestry-react"
|
|
267
|
+
|
|
268
|
+
function Test() {
|
|
269
|
+
return (
|
|
270
|
+
<div>
|
|
271
|
+
<Input.InputLabel>Name</Input.InputLabel>
|
|
272
|
+
<Input id="f1" />
|
|
273
|
+
</div>
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
`.trim()
|
|
277
|
+
|
|
278
|
+
const result = applyTransform(input)
|
|
279
|
+
expect(result).toBeNull()
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it("StackView from different package → no transform, returns null", () => {
|
|
283
|
+
const input = `
|
|
284
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
285
|
+
import { StackView } from "other-library"
|
|
286
|
+
|
|
287
|
+
function Test() {
|
|
288
|
+
return (
|
|
289
|
+
<StackView>
|
|
290
|
+
<Input.InputLabel>Name</Input.InputLabel>
|
|
291
|
+
<Input id="f1" />
|
|
292
|
+
</StackView>
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
`.trim()
|
|
296
|
+
|
|
297
|
+
const result = applyTransform(input)
|
|
298
|
+
expect(result).toBeNull()
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it("StackView not imported at all → no transform, returns null", () => {
|
|
302
|
+
const input = `
|
|
303
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
304
|
+
|
|
305
|
+
function Test() {
|
|
306
|
+
return (
|
|
307
|
+
<StackView>
|
|
308
|
+
<Input.InputLabel>Name</Input.InputLabel>
|
|
309
|
+
<Input id="f1" />
|
|
310
|
+
</StackView>
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
`.trim()
|
|
314
|
+
|
|
315
|
+
const result = applyTransform(input)
|
|
316
|
+
expect(result).toBeNull()
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
})
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { JSXElement, JSXText, Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
type JSXChild = NonNullable<JSXElement["children"]>[number]
|
|
4
|
+
|
|
5
|
+
import { addAttribute } from "../../shared/actions/addAttribute"
|
|
6
|
+
import { addComment } from "../../shared/actions/addComment"
|
|
7
|
+
import { getAttribute } from "../../shared/actions/getAttribute"
|
|
8
|
+
import { extractTextContent } from "../../shared/helpers/childrenToLabelHelpers"
|
|
9
|
+
import { getImportName } from "../../shared/transformFactories/helpers/manageImports"
|
|
10
|
+
|
|
11
|
+
const SCOPE = "inputLabelToLabelProp"
|
|
12
|
+
|
|
13
|
+
function isInputLabelElement(node: JSXChild, localName: string): boolean {
|
|
14
|
+
return (
|
|
15
|
+
node.type === "JSXElement" &&
|
|
16
|
+
node.openingElement.name.type === "JSXMemberExpression" &&
|
|
17
|
+
node.openingElement.name.object.type === "JSXIdentifier" &&
|
|
18
|
+
node.openingElement.name.object.name === localName &&
|
|
19
|
+
node.openingElement.name.property.name === "InputLabel"
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isInputElement(node: JSXChild, localName: string): boolean {
|
|
24
|
+
return (
|
|
25
|
+
node.type === "JSXElement" &&
|
|
26
|
+
node.openingElement.name.type === "JSXIdentifier" &&
|
|
27
|
+
node.openingElement.name.name === localName
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isHorizontalAxis(stackViewElement: JSXElement): boolean {
|
|
32
|
+
const axisAttr = getAttribute({ element: stackViewElement, name: "axis" })
|
|
33
|
+
if (!axisAttr) return false
|
|
34
|
+
const value = axisAttr.value
|
|
35
|
+
if (!value) return false
|
|
36
|
+
if (value.type === "StringLiteral" && value.value === "horizontal")
|
|
37
|
+
return true
|
|
38
|
+
if (
|
|
39
|
+
value.type === "JSXExpressionContainer" &&
|
|
40
|
+
value.expression.type === "StringLiteral" &&
|
|
41
|
+
value.expression.value === "horizontal"
|
|
42
|
+
)
|
|
43
|
+
return true
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function hasRenderSideProp(inputElement: JSXElement): boolean {
|
|
48
|
+
const attrs = inputElement.openingElement.attributes || []
|
|
49
|
+
return attrs.some(
|
|
50
|
+
(attr) =>
|
|
51
|
+
attr.type === "JSXAttribute" &&
|
|
52
|
+
attr.name.type === "JSXIdentifier" &&
|
|
53
|
+
(attr.name.name === "renderLeft" || attr.name.name === "renderRight")
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const transform: Transform = (fileInfo, api) => {
|
|
58
|
+
const j = api.jscodeshift
|
|
59
|
+
const source = j(fileInfo.source)
|
|
60
|
+
|
|
61
|
+
const inputLocalName = getImportName(
|
|
62
|
+
"Input",
|
|
63
|
+
"@planningcenter/tapestry-react",
|
|
64
|
+
{
|
|
65
|
+
j,
|
|
66
|
+
source,
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
if (!inputLocalName) return null
|
|
70
|
+
|
|
71
|
+
const stackViewLocalName = getImportName(
|
|
72
|
+
"StackView",
|
|
73
|
+
"@planningcenter/tapestry-react",
|
|
74
|
+
{
|
|
75
|
+
j,
|
|
76
|
+
source,
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
if (!stackViewLocalName) return null
|
|
80
|
+
|
|
81
|
+
let hasChanges = false
|
|
82
|
+
|
|
83
|
+
source.find(j.JSXElement).forEach((path) => {
|
|
84
|
+
const el = path.value
|
|
85
|
+
const opening = el.openingElement
|
|
86
|
+
|
|
87
|
+
if (opening.name.type !== "JSXIdentifier") return
|
|
88
|
+
if (opening.name.name !== stackViewLocalName) return
|
|
89
|
+
|
|
90
|
+
if (isHorizontalAxis(el)) return
|
|
91
|
+
|
|
92
|
+
const children = el.children || []
|
|
93
|
+
let i = 0
|
|
94
|
+
|
|
95
|
+
while (i < children.length) {
|
|
96
|
+
const child = children[i]
|
|
97
|
+
|
|
98
|
+
// Skip whitespace JSXText nodes
|
|
99
|
+
if (child.type === "JSXText" && child.value.trim() === "") {
|
|
100
|
+
i++
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Find next non-whitespace sibling
|
|
105
|
+
let nextIdx = i + 1
|
|
106
|
+
while (
|
|
107
|
+
nextIdx < children.length &&
|
|
108
|
+
children[nextIdx].type === "JSXText" &&
|
|
109
|
+
(children[nextIdx] as JSXText).value.trim() === ""
|
|
110
|
+
) {
|
|
111
|
+
nextIdx++
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (nextIdx >= children.length) {
|
|
115
|
+
i++
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const nextChild = children[nextIdx]
|
|
120
|
+
|
|
121
|
+
let inputLabelEl: JSXElement | null = null
|
|
122
|
+
let inputEl: JSXElement | null = null
|
|
123
|
+
let labelFirst = false
|
|
124
|
+
|
|
125
|
+
if (
|
|
126
|
+
isInputLabelElement(child, inputLocalName) &&
|
|
127
|
+
isInputElement(nextChild, inputLocalName)
|
|
128
|
+
) {
|
|
129
|
+
inputLabelEl = child as JSXElement
|
|
130
|
+
inputEl = nextChild as JSXElement
|
|
131
|
+
labelFirst = true
|
|
132
|
+
} else if (
|
|
133
|
+
isInputElement(child, inputLocalName) &&
|
|
134
|
+
isInputLabelElement(nextChild, inputLocalName)
|
|
135
|
+
) {
|
|
136
|
+
inputEl = child as JSXElement
|
|
137
|
+
inputLabelEl = nextChild as JSXElement
|
|
138
|
+
labelFirst = false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!inputLabelEl || !inputEl) {
|
|
142
|
+
i++
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Skip if Input has renderLeft or renderRight
|
|
147
|
+
if (hasRenderSideProp(inputEl)) {
|
|
148
|
+
i++
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const { isSimpleText, textContent } = extractTextContent(
|
|
153
|
+
inputLabelEl.children || []
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if (!isSimpleText) {
|
|
157
|
+
addComment({
|
|
158
|
+
element: inputEl,
|
|
159
|
+
j,
|
|
160
|
+
scope: SCOPE,
|
|
161
|
+
source,
|
|
162
|
+
text: "InputLabel children are complex and cannot be auto-converted to a label prop. Please migrate manually.",
|
|
163
|
+
})
|
|
164
|
+
hasChanges = true
|
|
165
|
+
i = nextIdx + 1
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const existingLabel = getAttribute({ element: inputEl, name: "label" })
|
|
170
|
+
if (existingLabel) {
|
|
171
|
+
addComment({
|
|
172
|
+
element: inputEl,
|
|
173
|
+
j,
|
|
174
|
+
scope: SCOPE,
|
|
175
|
+
source,
|
|
176
|
+
text: `Input already has a label prop. The InputLabel had text: "${textContent}". Please review manually.`,
|
|
177
|
+
})
|
|
178
|
+
hasChanges = true
|
|
179
|
+
i = nextIdx + 1
|
|
180
|
+
continue
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
addAttribute({ element: inputEl, j, name: "label", value: textContent })
|
|
184
|
+
|
|
185
|
+
if (labelFirst) {
|
|
186
|
+
// InputLabel is at i, Input is at nextIdx — remove InputLabel + whitespace between
|
|
187
|
+
children.splice(i, nextIdx - i)
|
|
188
|
+
// Input is now at i; advance past it
|
|
189
|
+
} else {
|
|
190
|
+
// Input is at i, InputLabel is at nextIdx — remove whitespace + InputLabel after Input
|
|
191
|
+
children.splice(i + 1, nextIdx - i)
|
|
192
|
+
// Input is still at i; advance past it
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
hasChanges = true
|
|
196
|
+
i++
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
return hasChanges ? source.toSource() : null
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export default transform
|