@planningcenter/tapestry-migration-cli 3.2.3-rc.4 → 3.2.3-rc.6

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 (46) hide show
  1. package/package.json +3 -3
  2. package/src/components/button/transforms/unsupportedProps.ts +3 -31
  3. package/src/components/checkbox/transforms/unsupportedProps.ts +3 -28
  4. package/src/components/date-picker/index.ts +56 -0
  5. package/src/components/date-picker/transforms/auditSpreadProps.test.ts +97 -0
  6. package/src/components/date-picker/transforms/auditSpreadProps.ts +10 -0
  7. package/src/components/date-picker/transforms/convertStyleProps.test.ts +58 -0
  8. package/src/components/date-picker/transforms/convertStyleProps.ts +10 -0
  9. package/src/components/date-picker/transforms/maxDateToMax.test.ts +87 -0
  10. package/src/components/date-picker/transforms/maxDateToMax.ts +16 -0
  11. package/src/components/date-picker/transforms/mergeFieldIntoDateField.test.ts +240 -0
  12. package/src/components/date-picker/transforms/mergeFieldIntoDateField.ts +5 -0
  13. package/src/components/date-picker/transforms/minDateToMin.test.ts +87 -0
  14. package/src/components/date-picker/transforms/minDateToMin.ts +16 -0
  15. package/src/components/date-picker/transforms/momentToDateString.test.ts +240 -0
  16. package/src/components/date-picker/transforms/momentToDateString.ts +87 -0
  17. package/src/components/date-picker/transforms/moveDatePickerImport.test.ts +157 -0
  18. package/src/components/date-picker/transforms/moveDatePickerImport.ts +14 -0
  19. package/src/components/date-picker/transforms/nativeDateToString.test.ts +220 -0
  20. package/src/components/date-picker/transforms/nativeDateToString.ts +59 -0
  21. package/src/components/date-picker/transforms/removeDuplicateKeys.test.ts +120 -0
  22. package/src/components/date-picker/transforms/removeDuplicateKeys.ts +8 -0
  23. package/src/components/date-picker/transforms/removeFormatValue.test.ts +119 -0
  24. package/src/components/date-picker/transforms/removeFormatValue.ts +22 -0
  25. package/src/components/date-picker/transforms/removePlaceholder.test.ts +117 -0
  26. package/src/components/date-picker/transforms/removePlaceholder.ts +22 -0
  27. package/src/components/date-picker/transforms/sizeMapping.test.ts +173 -0
  28. package/src/components/date-picker/transforms/sizeMapping.ts +15 -0
  29. package/src/components/date-picker/transforms/stateToInvalid.test.ts +109 -0
  30. package/src/components/date-picker/transforms/stateToInvalid.ts +57 -0
  31. package/src/components/date-picker/transforms/stateToInvalidTernary.test.ts +72 -0
  32. package/src/components/date-picker/transforms/stateToInvalidTernary.ts +11 -0
  33. package/src/components/date-picker/transforms/unsupportedProps.test.ts +170 -0
  34. package/src/components/date-picker/transforms/unsupportedProps.ts +10 -0
  35. package/src/components/input/transforms/unsupportedProps.ts +2 -2
  36. package/src/components/link/transforms/unsupportedProps.test.ts +3 -1
  37. package/src/components/link/transforms/unsupportedProps.ts +9 -37
  38. package/src/components/radio/transforms/unsupportedProps.ts +3 -28
  39. package/src/components/select/transforms/unsupportedProps.ts +9 -35
  40. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +22 -0
  41. package/src/components/shared/transformFactories/unsupportedPropsFactory.test.ts +162 -0
  42. package/src/components/shared/transformFactories/unsupportedPropsFactory.ts +60 -0
  43. package/src/components/text-area/transforms/unsupportedProps.ts +3 -28
  44. package/src/components/time-field/transforms/unsupportedProps.ts +3 -30
  45. package/src/components/toggle-switch/transforms/unsupportedProps.ts +3 -28
  46. package/src/index.ts +7 -1
@@ -0,0 +1,240 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./mergeFieldIntoDateField"
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("mergeFieldIntoDateField transform", () => {
18
+ describe("basic prop merging", () => {
19
+ it("merges label prop from Field into DateField", () => {
20
+ const input = `
21
+ import { DateField, Field } from "@planningcenter/tapestry-react"
22
+
23
+ function Test() {
24
+ return (
25
+ <Field label="Start date"><DateField value={date} onChange={setDate} /></Field>
26
+ )
27
+ }
28
+ `.trim()
29
+
30
+ const result = applyTransform(input)
31
+ expect(result).not.toBeNull()
32
+ expect(result).not.toContain("<Field")
33
+ expect(result).toContain('label="Start date"')
34
+ expect(result).toContain("<DateField")
35
+ })
36
+
37
+ it("renames feedbackText to description on DateField", () => {
38
+ const input = `
39
+ import { DateField, Field } from "@planningcenter/tapestry-react"
40
+
41
+ function Test() {
42
+ return (
43
+ <Field feedbackText="Required"><DateField value={date} onChange={setDate} /></Field>
44
+ )
45
+ }
46
+ `.trim()
47
+
48
+ const result = applyTransform(input)
49
+ expect(result).not.toBeNull()
50
+ expect(result).not.toContain("<Field")
51
+ expect(result).not.toContain("feedbackText")
52
+ expect(result).toContain('description="Required"')
53
+ })
54
+
55
+ it("copies state prop as-is to DateField", () => {
56
+ const input = `
57
+ import { DateField, Field } from "@planningcenter/tapestry-react"
58
+
59
+ function Test() {
60
+ return (
61
+ <Field state="error"><DateField value={date} onChange={setDate} /></Field>
62
+ )
63
+ }
64
+ `.trim()
65
+
66
+ const result = applyTransform(input)
67
+ expect(result).not.toBeNull()
68
+ expect(result).not.toContain("<Field")
69
+ expect(result).toContain('state="error"')
70
+ })
71
+ })
72
+
73
+ describe("unsupported props", () => {
74
+ it("adds TODO comment for helpContent and removes Field", () => {
75
+ const input = `
76
+ import { DateField, Field } from "@planningcenter/tapestry-react"
77
+
78
+ function Test() {
79
+ return (
80
+ <Field helpContent="Help"><DateField value={date} onChange={setDate} /></Field>
81
+ )
82
+ }
83
+ `.trim()
84
+
85
+ const result = applyTransform(input)
86
+ expect(result).not.toBeNull()
87
+ expect(result).not.toContain("<Field")
88
+ expect(result).toContain(
89
+ "TODO: tapestry-migration (mergeFieldIntoDateField)"
90
+ )
91
+ expect(result).toContain("helpContent")
92
+ expect(result).toContain("not supported by DateField")
93
+ })
94
+ })
95
+
96
+ describe("conflict detection", () => {
97
+ it("adds warning when DateField already has label", () => {
98
+ const input = `
99
+ import { DateField, Field } from "@planningcenter/tapestry-react"
100
+
101
+ function Test() {
102
+ return (
103
+ <Field label="Name"><DateField label="Other" value={date} onChange={setDate} /></Field>
104
+ )
105
+ }
106
+ `.trim()
107
+
108
+ const result = applyTransform(input)
109
+ expect(result).not.toBeNull()
110
+ expect(result).not.toContain("<Field")
111
+ expect(result).toContain('label="Other"')
112
+ expect(result).toContain(
113
+ "TODO: tapestry-migration (mergeFieldIntoDateField)"
114
+ )
115
+ expect(result).toContain("already has label")
116
+ })
117
+ })
118
+
119
+ describe("multiple children", () => {
120
+ it("adds comment when Field has multiple children", () => {
121
+ const input = `
122
+ import { DateField, Field } from "@planningcenter/tapestry-react"
123
+
124
+ function Test() {
125
+ return (
126
+ <Field><DateField value={date} onChange={setDate} /><button /></Field>
127
+ )
128
+ }
129
+ `.trim()
130
+
131
+ const result = applyTransform(input)
132
+ expect(result).not.toBeNull()
133
+ expect(result).toContain("<Field")
134
+ expect(result).toContain(
135
+ "TODO: tapestry-migration (mergeFieldIntoDateField)"
136
+ )
137
+ expect(result).toContain("multiple children")
138
+ })
139
+ })
140
+
141
+ describe("spread props on Field", () => {
142
+ it("bails out with TODO comment when Field has spread props", () => {
143
+ const input = `
144
+ import { DateField, Field } from "@planningcenter/tapestry-react"
145
+
146
+ function Test() {
147
+ return (
148
+ <Field {...fieldProps}><DateField value={date} onChange={setDate} /></Field>
149
+ )
150
+ }
151
+ `.trim()
152
+
153
+ const result = applyTransform(input)
154
+ expect(result).not.toBeNull()
155
+ expect(result).toContain("<Field")
156
+ expect(result).toContain(
157
+ "TODO: tapestry-migration (mergeFieldIntoDateField)"
158
+ )
159
+ expect(result).toContain("spread props")
160
+ })
161
+ })
162
+
163
+ describe("import cleanup", () => {
164
+ it("removes Field from import when all Fields are converted", () => {
165
+ const input = `
166
+ import { DateField, Field } from "@planningcenter/tapestry-react"
167
+
168
+ function Test() {
169
+ return (
170
+ <Field label="Start date"><DateField value={date} onChange={setDate} /></Field>
171
+ )
172
+ }
173
+ `.trim()
174
+
175
+ const result = applyTransform(input)
176
+ expect(result).not.toBeNull()
177
+ expect(result).not.toContain("{ DateField, Field }")
178
+ expect(result).toContain("DateField")
179
+ })
180
+ })
181
+
182
+ describe("not from tapestry-react", () => {
183
+ it("returns null when Field is not imported from tapestry-react", () => {
184
+ const input = `
185
+ import { Field } from "some-other-library"
186
+ import { DateField } from "@planningcenter/tapestry-react"
187
+
188
+ function Test() {
189
+ return (
190
+ <Field label="Name"><DateField value={date} onChange={setDate} /></Field>
191
+ )
192
+ }
193
+ `.trim()
194
+
195
+ const result = applyTransform(input)
196
+ expect(result).toBeNull()
197
+ })
198
+
199
+ it("returns null when DateField is not imported from tapestry-react", () => {
200
+ const input = `
201
+ import { Field } from "@planningcenter/tapestry-react"
202
+ import { DateField } from "some-other-library"
203
+
204
+ function Test() {
205
+ return (
206
+ <Field label="Name"><DateField value={date} onChange={setDate} /></Field>
207
+ )
208
+ }
209
+ `.trim()
210
+
211
+ const result = applyTransform(input)
212
+ expect(result).toBeNull()
213
+ })
214
+ })
215
+
216
+ describe("full prop set", () => {
217
+ it("merges label, feedbackText, state and flags helpContent as unsupported", () => {
218
+ const input = `
219
+ import { DateField, Field } from "@planningcenter/tapestry-react"
220
+
221
+ function Test() {
222
+ return (
223
+ <Field label="Start date" feedbackText="Required" state="error" helpContent="Help">
224
+ <DateField value={date} onChange={setDate} />
225
+ </Field>
226
+ )
227
+ }
228
+ `.trim()
229
+
230
+ const result = applyTransform(input)
231
+ expect(result).not.toBeNull()
232
+ expect(result).not.toContain("<Field")
233
+ expect(result).toContain('label="Start date"')
234
+ expect(result).toContain('description="Required"')
235
+ expect(result).toContain('state="error"')
236
+ expect(result).toContain("helpContent")
237
+ expect(result).toContain("not supported by DateField")
238
+ })
239
+ })
240
+ })
@@ -0,0 +1,5 @@
1
+ import { mergeFieldFactory } from "../../shared/transformFactories/mergeFieldFactory"
2
+
3
+ export default mergeFieldFactory({
4
+ targetComponent: "DateField",
5
+ })
@@ -0,0 +1,87 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./minDateToMin"
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("minDateToMin transform", () => {
19
+ it("should rename minDate to min", () => {
20
+ const input = `
21
+ import { DateField } from "@planningcenter/tapestry-react"
22
+
23
+ function Test() {
24
+ return <DateField minDate={startDate} value={date} onChange={setDate} />
25
+ }
26
+ `.trim()
27
+
28
+ const result = applyTransform(input)
29
+ expect(result).toContain("min={startDate}")
30
+ expect(result).not.toContain("minDate")
31
+ })
32
+
33
+ it("should rename minDate with inline Date value", () => {
34
+ const input = `
35
+ import { DateField } from "@planningcenter/tapestry-react"
36
+
37
+ function Test() {
38
+ return <DateField minDate={new Date("2024-01-01")} value={date} onChange={setDate} />
39
+ }
40
+ `.trim()
41
+
42
+ const result = applyTransform(input)
43
+ expect(result).toContain("min={")
44
+ expect(result).not.toContain("minDate=")
45
+ })
46
+
47
+ it("should not transform DateField without minDate prop", () => {
48
+ const input = `
49
+ import { DateField } from "@planningcenter/tapestry-react"
50
+
51
+ function Test() {
52
+ return <DateField value={date} onChange={setDate} />
53
+ }
54
+ `.trim()
55
+
56
+ const result = applyTransform(input)
57
+ expect(result).toBe(input)
58
+ })
59
+
60
+ it("should not affect other components", () => {
61
+ const input = `
62
+ import { Input } from "@planningcenter/tapestry-react"
63
+
64
+ function Test() {
65
+ return <Input minDate={startDate} label="Name" />
66
+ }
67
+ `.trim()
68
+
69
+ const result = applyTransform(input)
70
+ expect(result).toBe(input)
71
+ })
72
+
73
+ it("should preserve other props", () => {
74
+ const input = `
75
+ import { DateField } from "@planningcenter/tapestry-react"
76
+
77
+ function Test() {
78
+ return <DateField minDate={startDate} maxDate={endDate} value={date} onChange={setDate} />
79
+ }
80
+ `.trim()
81
+
82
+ const result = applyTransform(input)
83
+ expect(result).toContain("min={startDate}")
84
+ expect(result).toContain("maxDate={endDate}")
85
+ expect(result).toContain("value={date}")
86
+ })
87
+ })
@@ -0,0 +1,16 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { transformAttributeName } from "../../shared/actions/transformAttributeName"
4
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
5
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
6
+
7
+ const transform: Transform = attributeTransformFactory({
8
+ condition: hasAttribute("minDate"),
9
+ targetComponent: "DateField",
10
+ targetPackage: "@planningcenter/tapestry-react",
11
+ transform: (element, { j, options }) => {
12
+ return transformAttributeName("minDate", "min", { element, j, options })
13
+ },
14
+ })
15
+
16
+ export default transform
@@ -0,0 +1,240 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./momentToDateString"
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("momentToDateString transform", () => {
18
+ describe("minDate with moment().toDate()", () => {
19
+ it("converts moment(starts).add(1, 'day').toDate() to .format()", () => {
20
+ const input = `
21
+ import { DateField } from "@planningcenter/tapestry-react"
22
+
23
+ function Test() {
24
+ return <DateField minDate={moment(starts).add(1, 'day').toDate()} value={date} onChange={setDate} />
25
+ }
26
+ `.trim()
27
+
28
+ const result = applyTransform(input)
29
+ expect(result).not.toBeNull()
30
+ expect(result).toContain('.format("YYYY-MM-DD")')
31
+ expect(result).not.toContain(".toDate()}")
32
+ expect(result).toContain("moment(starts).add(1, 'day')")
33
+ })
34
+
35
+ it("converts moment().add(-10, 'years').toDate() to .format()", () => {
36
+ const input = `
37
+ import { DateField } from "@planningcenter/tapestry-react"
38
+
39
+ function Test() {
40
+ return <DateField minDate={moment().add(-10, 'years').toDate()} value={date} onChange={setDate} />
41
+ }
42
+ `.trim()
43
+
44
+ const result = applyTransform(input)
45
+ expect(result).not.toBeNull()
46
+ expect(result).toContain('.format("YYYY-MM-DD")')
47
+ expect(result).not.toContain(".toDate()}")
48
+ })
49
+
50
+ it("converts simple moment().toDate() to .format()", () => {
51
+ const input = `
52
+ import { DateField } from "@planningcenter/tapestry-react"
53
+
54
+ function Test() {
55
+ return <DateField minDate={moment().toDate()} value={date} onChange={setDate} />
56
+ }
57
+ `.trim()
58
+
59
+ const result = applyTransform(input)
60
+ expect(result).not.toBeNull()
61
+ expect(result).toContain('moment().format("YYYY-MM-DD")')
62
+ expect(result).not.toContain(".toDate()}")
63
+ })
64
+
65
+ it("adds a change comment noting the conversion", () => {
66
+ const input = `
67
+ import { DateField } from "@planningcenter/tapestry-react"
68
+
69
+ function Test() {
70
+ return <DateField minDate={moment().toDate()} value={date} onChange={setDate} />
71
+ }
72
+ `.trim()
73
+
74
+ const result = applyTransform(input)
75
+ expect(result).not.toBeNull()
76
+ expect(result).toContain("Converted .toDate()")
77
+ expect(result).toContain("ISO date strings")
78
+ })
79
+ })
80
+
81
+ describe("maxDate with moment().toDate()", () => {
82
+ it("converts maxDate moment chain to .format()", () => {
83
+ const input = `
84
+ import { DateField } from "@planningcenter/tapestry-react"
85
+
86
+ function Test() {
87
+ return <DateField maxDate={moment(ends).subtract(1, 'day').toDate()} value={date} onChange={setDate} />
88
+ }
89
+ `.trim()
90
+
91
+ const result = applyTransform(input)
92
+ expect(result).not.toBeNull()
93
+ expect(result).toContain('.format("YYYY-MM-DD")')
94
+ expect(result).not.toContain(".toDate()}")
95
+ })
96
+ })
97
+
98
+ describe("value with moment().toDate()", () => {
99
+ it("converts value moment chain to .format()", () => {
100
+ const input = `
101
+ import { DateField } from "@planningcenter/tapestry-react"
102
+
103
+ function Test() {
104
+ return <DateField value={moment(selectedDate).toDate()} onChange={setDate} />
105
+ }
106
+ `.trim()
107
+
108
+ const result = applyTransform(input)
109
+ expect(result).not.toBeNull()
110
+ expect(result).toContain('.format("YYYY-MM-DD")')
111
+ expect(result).not.toContain(".toDate()}")
112
+ })
113
+ })
114
+
115
+ describe("multiple props with moment", () => {
116
+ it("converts both minDate and maxDate moment chains", () => {
117
+ const input = `
118
+ import { DateField } from "@planningcenter/tapestry-react"
119
+
120
+ function Test() {
121
+ return (
122
+ <DateField
123
+ minDate={moment().add(-10, 'years').toDate()}
124
+ maxDate={moment().toDate()}
125
+ value={date}
126
+ onChange={setDate}
127
+ />
128
+ )
129
+ }
130
+ `.trim()
131
+
132
+ const result = applyTransform(input)
133
+ expect(result).not.toBeNull()
134
+ expect(result).not.toContain(".toDate()}")
135
+ const formatMatches = result!.match(/\)\.format\("YYYY-MM-DD"\)\}/g)
136
+ expect(formatMatches).toHaveLength(2)
137
+ })
138
+ })
139
+
140
+ describe("moment without .toDate()", () => {
141
+ it("adds TODO comment when moment is used without .toDate()", () => {
142
+ const input = `
143
+ import { DateField } from "@planningcenter/tapestry-react"
144
+
145
+ function Test() {
146
+ return <DateField minDate={moment(starts)} value={date} onChange={setDate} />
147
+ }
148
+ `.trim()
149
+
150
+ const result = applyTransform(input)
151
+ expect(result).not.toBeNull()
152
+ expect(result).toContain("TODO: tapestry-migration")
153
+ expect(result).toContain("manual conversion")
154
+ })
155
+
156
+ it("adds TODO for moment variable reference in value", () => {
157
+ const input = `
158
+ import { DateField } from "@planningcenter/tapestry-react"
159
+
160
+ function Test() {
161
+ const d = moment()
162
+ return <DateField value={moment(d)} onChange={setDate} />
163
+ }
164
+ `.trim()
165
+
166
+ const result = applyTransform(input)
167
+ expect(result).not.toBeNull()
168
+ expect(result).toContain("TODO: tapestry-migration")
169
+ })
170
+ })
171
+
172
+ describe("non-date values", () => {
173
+ it("does not transform string values", () => {
174
+ const input = `
175
+ import { DateField } from "@planningcenter/tapestry-react"
176
+
177
+ function Test() {
178
+ return <DateField value="2024-01-01" onChange={setDate} />
179
+ }
180
+ `.trim()
181
+
182
+ const result = applyTransform(input)
183
+ expect(result).toBeNull()
184
+ })
185
+
186
+ it("does not transform variable references", () => {
187
+ const input = `
188
+ import { DateField } from "@planningcenter/tapestry-react"
189
+
190
+ function Test() {
191
+ return <DateField minDate={startDate} value={date} onChange={setDate} />
192
+ }
193
+ `.trim()
194
+
195
+ const result = applyTransform(input)
196
+ expect(result).toBeNull()
197
+ })
198
+ })
199
+
200
+ describe("edge cases", () => {
201
+ it("does not affect other components", () => {
202
+ const input = `
203
+ import { Input } from "@planningcenter/tapestry-react"
204
+
205
+ function Test() {
206
+ return <Input value={moment().toDate()} label="Name" />
207
+ }
208
+ `.trim()
209
+
210
+ const result = applyTransform(input)
211
+ expect(result).toBeNull()
212
+ })
213
+
214
+ it("does not transform non-date props that use moment", () => {
215
+ const input = `
216
+ import { DateField } from "@planningcenter/tapestry-react"
217
+
218
+ function Test() {
219
+ return <DateField onChange={moment().toDate()} value={date} />
220
+ }
221
+ `.trim()
222
+
223
+ const result = applyTransform(input)
224
+ expect(result).toBeNull()
225
+ })
226
+
227
+ it("does not transform DateField from other packages", () => {
228
+ const input = `
229
+ import { DateField } from "other-library"
230
+
231
+ function Test() {
232
+ return <DateField minDate={moment().toDate()} value={date} onChange={setDate} />
233
+ }
234
+ `.trim()
235
+
236
+ const result = applyTransform(input)
237
+ expect(result).toBeNull()
238
+ })
239
+ })
240
+ })
@@ -0,0 +1,87 @@
1
+ import {
2
+ CallExpression,
3
+ Expression,
4
+ MemberExpression,
5
+ Transform,
6
+ } from "jscodeshift"
7
+
8
+ import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
9
+ import { getAttribute } from "../../shared/actions/getAttribute"
10
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
11
+
12
+ const DATE_PROPS = ["minDate", "maxDate", "value"]
13
+
14
+ /**
15
+ * Checks if an expression is a call to `.toDate()` at the end of a chain
16
+ */
17
+ function isToDateCall(expr: Expression): expr is CallExpression {
18
+ return (
19
+ expr.type === "CallExpression" &&
20
+ expr.callee.type === "MemberExpression" &&
21
+ expr.callee.property.type === "Identifier" &&
22
+ expr.callee.property.name === "toDate" &&
23
+ expr.arguments.length === 0
24
+ )
25
+ }
26
+
27
+ /**
28
+ * Checks whether an expression references `moment` anywhere in its call chain
29
+ */
30
+ function usesMoment(expr: Expression): boolean {
31
+ if (expr.type === "Identifier" && expr.name === "moment") return true
32
+ if (expr.type === "CallExpression") {
33
+ if (usesMoment(expr.callee as Expression)) return true
34
+ return expr.arguments.some((arg) =>
35
+ arg.type !== "SpreadElement" ? usesMoment(arg as Expression) : false
36
+ )
37
+ }
38
+ if (expr.type === "MemberExpression") {
39
+ return usesMoment(expr.object as Expression)
40
+ }
41
+ return false
42
+ }
43
+
44
+ const transform: Transform = attributeTransformFactory({
45
+ targetComponent: "DateField",
46
+ targetPackage: "@planningcenter/tapestry-react",
47
+ transform: (element, { j }) => {
48
+ let hasChanges = false
49
+
50
+ for (const propName of DATE_PROPS) {
51
+ const attr = getAttribute({ element, name: propName })
52
+ if (!attr?.value) continue
53
+ if (attr.value.type !== "JSXExpressionContainer") continue
54
+
55
+ const expr = attr.value.expression
56
+ if (expr.type === "JSXEmptyExpression") continue
57
+
58
+ if (!usesMoment(expr as Expression)) continue
59
+
60
+ if (isToDateCall(expr as Expression)) {
61
+ const callExpr = expr as CallExpression
62
+ const memberExpr = callExpr.callee as MemberExpression
63
+ memberExpr.property = j.identifier("format")
64
+ callExpr.arguments = [j.stringLiteral("YYYY-MM-DD")]
65
+
66
+ addCommentToAttribute({
67
+ attribute: attr,
68
+ commentKind: "change",
69
+ j,
70
+ text: `Converted .toDate() to .format("YYYY-MM-DD") — DatePicker accepts ISO date strings instead of Date objects. Verify the format is correct.`,
71
+ })
72
+ hasChanges = true
73
+ } else {
74
+ addCommentToAttribute({
75
+ attribute: attr,
76
+ j,
77
+ text: `This prop uses moment and may need manual conversion. DatePicker accepts DateValue or ISO date strings (YYYY-MM-DD) instead of Date objects.`,
78
+ })
79
+ hasChanges = true
80
+ }
81
+ }
82
+
83
+ return hasChanges
84
+ },
85
+ })
86
+
87
+ export default transform