@planningcenter/tapestry-migration-cli 3.2.2-rc.8 → 3.2.2
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 +3 -3
- package/src/components/checkbox/transforms/moveCheckboxImport.test.ts +26 -0
- package/src/components/checkbox/transforms/moveCheckboxImport.ts +1 -0
- package/src/components/input/transforms/moveInputImport.test.ts +26 -0
- package/src/components/input/transforms/moveInputImport.ts +1 -0
- package/src/components/input/transforms/numberFieldRenameToInput.test.ts +51 -0
- package/src/components/input/transforms/numberFieldRenameToInput.ts +1 -0
- package/src/components/radio/transforms/moveRadioImport.test.ts +26 -0
- package/src/components/radio/transforms/moveRadioImport.ts +1 -0
- package/src/components/select/transforms/moveSelectImport.test.ts +24 -0
- package/src/components/select/transforms/moveSelectImport.ts +1 -0
- package/src/components/shared/helpers/unsupportedPropsHelpers.ts +24 -0
- package/src/components/shared/transformFactories/helpers/manageImports.ts +25 -1
- package/src/components/shared/transformFactories/stylePropTransformFactory.test.ts +330 -0
- package/src/components/shared/transformFactories/stylePropTransformFactory.ts +156 -15
- package/src/components/shared/transformFactories/ternaryConditionalToPropFactory.ts +17 -12
- package/src/components/text-area/transforms/moveTextAreaImport.test.ts +26 -0
- package/src/components/text-area/transforms/moveTextAreaImport.ts +1 -0
- package/src/components/time-field/index.ts +48 -0
- package/src/components/time-field/transforms/auditSpreadProps.test.ts +76 -0
- package/src/components/time-field/transforms/auditSpreadProps.ts +10 -0
- package/src/components/time-field/transforms/convertStyleProps.test.ts +43 -0
- package/src/components/time-field/transforms/convertStyleProps.ts +10 -0
- package/src/components/time-field/transforms/flagMinMax.test.ts +103 -0
- package/src/components/time-field/transforms/flagMinMax.ts +31 -0
- package/src/components/time-field/transforms/mergeFieldIntoTimeField.test.ts +106 -0
- package/src/components/time-field/transforms/mergeFieldIntoTimeField.ts +5 -0
- package/src/components/time-field/transforms/moveTimeFieldImport.test.ts +153 -0
- package/src/components/time-field/transforms/moveTimeFieldImport.ts +14 -0
- package/src/components/time-field/transforms/sizeMapping.test.ts +173 -0
- package/src/components/time-field/transforms/sizeMapping.ts +15 -0
- package/src/components/time-field/transforms/stateToInvalid.test.ts +87 -0
- package/src/components/time-field/transforms/stateToInvalid.ts +56 -0
- package/src/components/time-field/transforms/stateToInvalidTernary.test.ts +100 -0
- package/src/components/time-field/transforms/stateToInvalidTernary.ts +11 -0
- package/src/components/time-field/transforms/tupleToTime.test.ts +182 -0
- package/src/components/time-field/transforms/tupleToTime.ts +107 -0
- package/src/components/time-field/transforms/twelveHourClockToHourCycle.test.ts +117 -0
- package/src/components/time-field/transforms/twelveHourClockToHourCycle.ts +65 -0
- package/src/components/time-field/transforms/unsupportedProps.test.ts +160 -0
- package/src/components/time-field/transforms/unsupportedProps.ts +37 -0
- package/src/components/toggle-switch/transforms/moveToggleSwitchImport.test.ts +28 -0
- package/src/components/toggle-switch/transforms/moveToggleSwitchImport.ts +1 -0
- package/src/index.ts +2 -1
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
splitStyles,
|
|
13
13
|
stylePropNames,
|
|
14
14
|
} from "../../../../dist/tapestry-react-shim.cjs"
|
|
15
|
-
import { addComment } from "../../shared/actions/addComment"
|
|
15
|
+
import { addComment, formatComment } from "../../shared/actions/addComment"
|
|
16
16
|
import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
|
|
17
17
|
import { getAttribute } from "../../shared/actions/getAttribute"
|
|
18
18
|
import { removeAttribute } from "../../shared/actions/removeAttribute"
|
|
@@ -27,6 +27,49 @@ type StylePropMapping = Record<
|
|
|
27
27
|
}
|
|
28
28
|
>
|
|
29
29
|
|
|
30
|
+
// JSX expression values bypass splitStyles, so we mirror its plugin behavior
|
|
31
|
+
// for props that bypass it. Pure key renames pass through live; everything
|
|
32
|
+
// else gets commented out under the css target(s) so the developer can verify
|
|
33
|
+
// the value before enabling.
|
|
34
|
+
const liveAliases: Record<string, string> = {
|
|
35
|
+
column: "gridColumn",
|
|
36
|
+
columnEnd: "gridColumnEnd",
|
|
37
|
+
columnStart: "gridColumnStart",
|
|
38
|
+
direction: "flexDirection",
|
|
39
|
+
row: "gridRow",
|
|
40
|
+
rowEnd: "gridRowEnd",
|
|
41
|
+
rowStart: "gridRowStart",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const commentedAliases: Record<string, string[]> = {
|
|
45
|
+
basis: ["flexBasis"],
|
|
46
|
+
elevation: ["boxShadow"],
|
|
47
|
+
grow: ["flexGrow"],
|
|
48
|
+
marginHorizontal: ["marginLeft", "marginRight"],
|
|
49
|
+
marginVertical: ["marginTop", "marginBottom"],
|
|
50
|
+
paddingHorizontal: ["paddingLeft", "paddingRight"],
|
|
51
|
+
paddingVertical: ["paddingTop", "paddingBottom"],
|
|
52
|
+
radius: [
|
|
53
|
+
"borderTopLeftRadius",
|
|
54
|
+
"borderTopRightRadius",
|
|
55
|
+
"borderBottomRightRadius",
|
|
56
|
+
"borderBottomLeftRadius",
|
|
57
|
+
],
|
|
58
|
+
radiusBottom: ["borderBottomRightRadius", "borderBottomLeftRadius"],
|
|
59
|
+
radiusLeft: ["borderTopLeftRadius", "borderBottomLeftRadius"],
|
|
60
|
+
radiusRight: ["borderTopRightRadius", "borderBottomRightRadius"],
|
|
61
|
+
radiusTop: ["borderTopLeftRadius", "borderTopRightRadius"],
|
|
62
|
+
rotate: ["transform"],
|
|
63
|
+
scale: ["transform"],
|
|
64
|
+
shrink: ["flexShrink"],
|
|
65
|
+
strokeAlign: ["strokeAlign"],
|
|
66
|
+
strokeWeight: ["strokeWeight"],
|
|
67
|
+
uppercase: ["textTransform"],
|
|
68
|
+
wrap: ["flexWrap"],
|
|
69
|
+
x: ["transform"],
|
|
70
|
+
y: ["transform"],
|
|
71
|
+
}
|
|
72
|
+
|
|
30
73
|
function extractPropValue(attr: JSXAttribute, j: JSCodeshift) {
|
|
31
74
|
if (!attr.value) {
|
|
32
75
|
return true
|
|
@@ -50,22 +93,36 @@ function extractPropValue(attr: JSXAttribute, j: JSCodeshift) {
|
|
|
50
93
|
}
|
|
51
94
|
}
|
|
52
95
|
|
|
96
|
+
type ManualReviewEntry = {
|
|
97
|
+
cssTargets: string[]
|
|
98
|
+
originalProp: string
|
|
99
|
+
originalValue: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function reviewHeading(entry: ManualReviewEntry) {
|
|
103
|
+
return `migrated from \`${entry.originalProp}\`; value may need manual transformation`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function reviewBody(entry: ManualReviewEntry) {
|
|
107
|
+
return entry.cssTargets.map((key) => `${key}: ${entry.originalValue}`)
|
|
108
|
+
}
|
|
109
|
+
|
|
53
110
|
function processKeepStyleProps({
|
|
54
111
|
attributes,
|
|
55
112
|
j,
|
|
56
|
-
element,
|
|
57
|
-
source,
|
|
58
113
|
}: {
|
|
59
114
|
attributes: JSXAttribute[]
|
|
60
|
-
element: JSXElement
|
|
61
115
|
j: JSCodeshift
|
|
62
|
-
source: Collection
|
|
63
116
|
}): {
|
|
64
117
|
directProps: Record<string, unknown>
|
|
118
|
+
manualReviewEntries: ManualReviewEntry[]
|
|
119
|
+
processedNames: string[]
|
|
65
120
|
themeProps: Record<string, unknown>
|
|
66
121
|
} {
|
|
67
122
|
const themeProps: Record<string, unknown> = {}
|
|
68
123
|
const directProps: Record<string, unknown> = {}
|
|
124
|
+
const manualReviewEntries: ManualReviewEntry[] = []
|
|
125
|
+
const processedNames: string[] = []
|
|
69
126
|
|
|
70
127
|
attributes.forEach((attr) => {
|
|
71
128
|
const propName = attr.name.name as string
|
|
@@ -76,15 +133,32 @@ function processKeepStyleProps({
|
|
|
76
133
|
propValue.startsWith("{") &&
|
|
77
134
|
propValue.endsWith("}")
|
|
78
135
|
) {
|
|
79
|
-
|
|
136
|
+
if (propName in commentedAliases) {
|
|
137
|
+
manualReviewEntries.push({
|
|
138
|
+
cssTargets: commentedAliases[propName],
|
|
139
|
+
originalProp: propName,
|
|
140
|
+
originalValue: propValue.slice(1, -1),
|
|
141
|
+
})
|
|
142
|
+
} else {
|
|
143
|
+
directProps[liveAliases[propName] ?? propName] = propValue
|
|
144
|
+
}
|
|
80
145
|
} else {
|
|
81
146
|
themeProps[propName] = propValue
|
|
82
147
|
}
|
|
83
148
|
|
|
84
|
-
|
|
149
|
+
processedNames.push(propName)
|
|
85
150
|
})
|
|
86
151
|
|
|
87
|
-
return { directProps, themeProps }
|
|
152
|
+
return { directProps, manualReviewEntries, processedNames, themeProps }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isInlineableStyleValue(value: unknown): boolean {
|
|
156
|
+
return (
|
|
157
|
+
value === null ||
|
|
158
|
+
typeof value === "string" ||
|
|
159
|
+
typeof value === "number" ||
|
|
160
|
+
typeof value === "boolean"
|
|
161
|
+
)
|
|
88
162
|
}
|
|
89
163
|
|
|
90
164
|
function processRemoveStyleProps({
|
|
@@ -160,20 +234,36 @@ function processStylePropMappings({
|
|
|
160
234
|
return styleProps
|
|
161
235
|
}
|
|
162
236
|
|
|
237
|
+
function buildManualReviewComments(
|
|
238
|
+
entries: ManualReviewEntry[],
|
|
239
|
+
j: JSCodeshift
|
|
240
|
+
) {
|
|
241
|
+
return entries.flatMap((entry) => [
|
|
242
|
+
j.commentBlock(
|
|
243
|
+
formatComment(reviewHeading(entry), "styleProp"),
|
|
244
|
+
true,
|
|
245
|
+
false
|
|
246
|
+
),
|
|
247
|
+
...reviewBody(entry).map((line) => j.commentLine(` ${line},`)),
|
|
248
|
+
])
|
|
249
|
+
}
|
|
250
|
+
|
|
163
251
|
function applyStylesToComponent({
|
|
164
252
|
j,
|
|
165
253
|
element,
|
|
166
254
|
styles,
|
|
255
|
+
manualReviewEntries = [],
|
|
167
256
|
}: {
|
|
168
257
|
element: JSXElement
|
|
169
258
|
j: JSCodeshift
|
|
259
|
+
manualReviewEntries?: ManualReviewEntry[]
|
|
170
260
|
source: Collection
|
|
171
261
|
styles: Record<string, unknown>
|
|
172
262
|
}) {
|
|
173
263
|
const styleAttr = getAttribute({ element, name: "style" })
|
|
174
264
|
const styleValue = j.jsxExpressionContainer(
|
|
175
265
|
j.objectExpression(
|
|
176
|
-
Object.entries(styles).map(([key, value]) => {
|
|
266
|
+
Object.entries(styles).map(([key, value], index) => {
|
|
177
267
|
let valueNode
|
|
178
268
|
if (
|
|
179
269
|
typeof value === "string" &&
|
|
@@ -198,7 +288,11 @@ function applyStylesToComponent({
|
|
|
198
288
|
valueNode = j.literal(value as string | number | boolean | null)
|
|
199
289
|
}
|
|
200
290
|
|
|
201
|
-
|
|
291
|
+
const property = j.objectProperty(j.identifier(key), valueNode)
|
|
292
|
+
if (index === 0 && manualReviewEntries.length > 0) {
|
|
293
|
+
property.comments = buildManualReviewComments(manualReviewEntries, j)
|
|
294
|
+
}
|
|
295
|
+
return property
|
|
202
296
|
})
|
|
203
297
|
)
|
|
204
298
|
)
|
|
@@ -286,6 +380,7 @@ export function stylePropTransformFactory(config: {
|
|
|
286
380
|
const allAttributes = element.openingElement.attributes || []
|
|
287
381
|
let allStyleProps: Record<string, unknown> = {}
|
|
288
382
|
let directStyleProps: Record<string, unknown> = {}
|
|
383
|
+
const manualReviewEntries: ManualReviewEntry[] = []
|
|
289
384
|
const attributes = allAttributes.filter((attr) => {
|
|
290
385
|
if (attr.type !== "JSXAttribute") return false
|
|
291
386
|
const name = attr.name?.name as string
|
|
@@ -307,9 +402,7 @@ export function stylePropTransformFactory(config: {
|
|
|
307
402
|
)
|
|
308
403
|
const keepResult = processKeepStyleProps({
|
|
309
404
|
attributes: keepAttributes,
|
|
310
|
-
element,
|
|
311
405
|
j,
|
|
312
|
-
source,
|
|
313
406
|
})
|
|
314
407
|
allStyleProps = {
|
|
315
408
|
...allStyleProps,
|
|
@@ -319,6 +412,7 @@ export function stylePropTransformFactory(config: {
|
|
|
319
412
|
...directStyleProps,
|
|
320
413
|
...keepResult.directProps,
|
|
321
414
|
}
|
|
415
|
+
manualReviewEntries.push(...keepResult.manualReviewEntries)
|
|
322
416
|
|
|
323
417
|
allStyleProps = {
|
|
324
418
|
...allStyleProps,
|
|
@@ -343,7 +437,8 @@ export function stylePropTransformFactory(config: {
|
|
|
343
437
|
|
|
344
438
|
if (
|
|
345
439
|
Object.keys(allStyleProps).length > 0 ||
|
|
346
|
-
Object.keys(directStyleProps).length > 0
|
|
440
|
+
Object.keys(directStyleProps).length > 0 ||
|
|
441
|
+
manualReviewEntries.length > 0
|
|
347
442
|
) {
|
|
348
443
|
try {
|
|
349
444
|
let styles: Record<string, unknown> = {}
|
|
@@ -360,8 +455,39 @@ export function stylePropTransformFactory(config: {
|
|
|
360
455
|
styles = { ...styles, ...directStyleProps }
|
|
361
456
|
if (options.verbose) console.log("Final generated styles:", styles)
|
|
362
457
|
|
|
363
|
-
|
|
364
|
-
|
|
458
|
+
// Plugins like stackViewPlugin can emit nested CSS selectors (e.g.
|
|
459
|
+
// `{ "& > *": { flex: "1 0 0px" } }` from `distribution: "fill"`)
|
|
460
|
+
// that can't be represented in React's inline `style={}`. Preserve
|
|
461
|
+
// the source props so nothing is silently dropped and flag for review.
|
|
462
|
+
const nonInlineableKeys = Object.entries(styles)
|
|
463
|
+
.filter(([, v]) => !isInlineableStyleValue(v))
|
|
464
|
+
.map(([k]) => k)
|
|
465
|
+
if (nonInlineableKeys.length > 0) {
|
|
466
|
+
addComment({
|
|
467
|
+
element,
|
|
468
|
+
j,
|
|
469
|
+
scope: "styleProp",
|
|
470
|
+
source,
|
|
471
|
+
text: `Could not migrate style props (${keepResult.processedNames.join(
|
|
472
|
+
", "
|
|
473
|
+
)}) — produced non-inlineable style values (${nonInlineableKeys.join(
|
|
474
|
+
", "
|
|
475
|
+
)}) that don't fit in inline style (for example, nested selectors). Manual review required.`,
|
|
476
|
+
})
|
|
477
|
+
} else {
|
|
478
|
+
for (const propName of keepResult.processedNames) {
|
|
479
|
+
removeAttribute(propName, { element, j, source })
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (nonInlineableKeys.length === 0 && Object.keys(styles).length > 0) {
|
|
484
|
+
applyStylesToComponent({
|
|
485
|
+
element,
|
|
486
|
+
j,
|
|
487
|
+
manualReviewEntries,
|
|
488
|
+
source,
|
|
489
|
+
styles,
|
|
490
|
+
})
|
|
365
491
|
|
|
366
492
|
if (options.verbose) {
|
|
367
493
|
const styleAttr = getAttribute({ element, name: "style" })
|
|
@@ -381,6 +507,21 @@ export function stylePropTransformFactory(config: {
|
|
|
381
507
|
}
|
|
382
508
|
}
|
|
383
509
|
}
|
|
510
|
+
} else if (
|
|
511
|
+
nonInlineableKeys.length === 0 &&
|
|
512
|
+
manualReviewEntries.length > 0
|
|
513
|
+
) {
|
|
514
|
+
// No anchoring property in the style block — emit each manual-review
|
|
515
|
+
// entry as a JSX-level TODO comment instead.
|
|
516
|
+
for (const entry of manualReviewEntries) {
|
|
517
|
+
addComment({
|
|
518
|
+
element,
|
|
519
|
+
j,
|
|
520
|
+
scope: "styleProp",
|
|
521
|
+
source,
|
|
522
|
+
text: `${reviewHeading(entry)}: ${reviewBody(entry).join("; ")}`,
|
|
523
|
+
})
|
|
524
|
+
}
|
|
384
525
|
}
|
|
385
526
|
} catch (error) {
|
|
386
527
|
console.log("Error processing style props:", error)
|
|
@@ -35,27 +35,32 @@ export function ternaryConditionalToPropFactory({
|
|
|
35
35
|
const expr = attr.value.expression
|
|
36
36
|
if (expr.type !== "ConditionalExpression") return false
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
) {
|
|
42
|
-
return false
|
|
43
|
-
}
|
|
38
|
+
const isNullish = (node: Expression) =>
|
|
39
|
+
node.type === "NullLiteral" ||
|
|
40
|
+
(node.type === "Identifier" && node.name === "undefined")
|
|
44
41
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
expr.alternate.type === "Identifier" &&
|
|
48
|
-
expr.alternate.name === "undefined"
|
|
42
|
+
const isMatchString = (node: Expression) =>
|
|
43
|
+
node.type === "StringLiteral" && node.value === matchValue
|
|
49
44
|
|
|
50
|
-
|
|
45
|
+
const matchInConsequent =
|
|
46
|
+
isMatchString(expr.consequent as Expression) &&
|
|
47
|
+
isNullish(expr.alternate as Expression)
|
|
48
|
+
const matchInAlternate =
|
|
49
|
+
isMatchString(expr.alternate as Expression) &&
|
|
50
|
+
isNullish(expr.consequent as Expression)
|
|
51
|
+
|
|
52
|
+
if (!matchInConsequent && !matchInAlternate) return false
|
|
51
53
|
|
|
52
54
|
const testExpr = expr.test as Expression
|
|
55
|
+
const propValue = matchInConsequent
|
|
56
|
+
? testExpr
|
|
57
|
+
: j.unaryExpression("!", testExpr)
|
|
53
58
|
|
|
54
59
|
removeAttribute(fromProp, { element, j, source })
|
|
55
60
|
element.openingElement.attributes.push(
|
|
56
61
|
j.jsxAttribute(
|
|
57
62
|
j.jsxIdentifier(toProp),
|
|
58
|
-
j.jsxExpressionContainer(
|
|
63
|
+
j.jsxExpressionContainer(propValue)
|
|
59
64
|
)
|
|
60
65
|
)
|
|
61
66
|
|
|
@@ -96,6 +96,32 @@ function Test() {
|
|
|
96
96
|
})
|
|
97
97
|
})
|
|
98
98
|
|
|
99
|
+
describe("import conflict handling", () => {
|
|
100
|
+
it("should use TTextArea alias when TextArea is already imported from another package", () => {
|
|
101
|
+
const input = `
|
|
102
|
+
import { TextArea } from "some-other-library"
|
|
103
|
+
import { TextArea as ReactTextArea } from "@planningcenter/tapestry-react"
|
|
104
|
+
|
|
105
|
+
function Test() {
|
|
106
|
+
return (
|
|
107
|
+
<div>
|
|
108
|
+
<TextArea />
|
|
109
|
+
<ReactTextArea label="Notes" />
|
|
110
|
+
</div>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
`.trim()
|
|
114
|
+
|
|
115
|
+
const result = applyTransform(input)
|
|
116
|
+
expect(result).toContain('import { TextArea } from "some-other-library"')
|
|
117
|
+
expect(result).toContain(
|
|
118
|
+
'import { TextArea as TTextArea } from "@planningcenter/tapestry"'
|
|
119
|
+
)
|
|
120
|
+
expect(result).toContain("<TTextArea")
|
|
121
|
+
expect(result).not.toContain("ReactTextArea")
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
99
125
|
describe("edge cases", () => {
|
|
100
126
|
it("should handle already migrated imports", () => {
|
|
101
127
|
const input = `
|
|
@@ -4,6 +4,7 @@ import { componentTransformFactory } from "../../shared/transformFactories/compo
|
|
|
4
4
|
|
|
5
5
|
const transform: Transform = componentTransformFactory({
|
|
6
6
|
condition: () => true,
|
|
7
|
+
conflictAlias: "TTextArea",
|
|
7
8
|
fromComponent: "TextArea",
|
|
8
9
|
fromPackage: "@planningcenter/tapestry-react",
|
|
9
10
|
toComponent: "TextArea",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import auditSpreadProps from "./transforms/auditSpreadProps"
|
|
4
|
+
import convertStyleProps from "./transforms/convertStyleProps"
|
|
5
|
+
import flagMinMax from "./transforms/flagMinMax"
|
|
6
|
+
import mergeFieldIntoTimeField from "./transforms/mergeFieldIntoTimeField"
|
|
7
|
+
import moveTimeFieldImport from "./transforms/moveTimeFieldImport"
|
|
8
|
+
import sizeMapping from "./transforms/sizeMapping"
|
|
9
|
+
import stateToInvalid from "./transforms/stateToInvalid"
|
|
10
|
+
import stateToInvalidTernary from "./transforms/stateToInvalidTernary"
|
|
11
|
+
import tupleToTime from "./transforms/tupleToTime"
|
|
12
|
+
import twelveHourClockToHourCycle from "./transforms/twelveHourClockToHourCycle"
|
|
13
|
+
import unsupportedProps from "./transforms/unsupportedProps"
|
|
14
|
+
|
|
15
|
+
const transform: Transform = (fileInfo, api, options) => {
|
|
16
|
+
let currentSource = fileInfo.source
|
|
17
|
+
let hasAnyChanges = false
|
|
18
|
+
|
|
19
|
+
const transforms = [
|
|
20
|
+
mergeFieldIntoTimeField,
|
|
21
|
+
auditSpreadProps,
|
|
22
|
+
sizeMapping,
|
|
23
|
+
stateToInvalidTernary,
|
|
24
|
+
stateToInvalid,
|
|
25
|
+
twelveHourClockToHourCycle,
|
|
26
|
+
tupleToTime,
|
|
27
|
+
flagMinMax,
|
|
28
|
+
convertStyleProps,
|
|
29
|
+
unsupportedProps,
|
|
30
|
+
moveTimeFieldImport,
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
for (const individualTransform of transforms) {
|
|
34
|
+
const result = individualTransform(
|
|
35
|
+
{ ...fileInfo, source: currentSource },
|
|
36
|
+
api,
|
|
37
|
+
options
|
|
38
|
+
)
|
|
39
|
+
if (result && result !== currentSource) {
|
|
40
|
+
currentSource = result as string
|
|
41
|
+
hasAnyChanges = true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return hasAnyChanges ? currentSource : null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default transform
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./auditSpreadProps"
|
|
5
|
+
|
|
6
|
+
const j = jscodeshift.withParser("tsx")
|
|
7
|
+
|
|
8
|
+
const AUDIT_COMMENT =
|
|
9
|
+
"TODO: tapestry-migration (spreadAttribute): Spread props can contain unsupported props, please explore usages and migrate as needed."
|
|
10
|
+
|
|
11
|
+
function applyTransform(source: string) {
|
|
12
|
+
const fileInfo = { path: "test.tsx", source }
|
|
13
|
+
return transform(
|
|
14
|
+
fileInfo,
|
|
15
|
+
{ j, jscodeshift: j, report: () => {}, stats: () => {} },
|
|
16
|
+
{}
|
|
17
|
+
) as string | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("auditSpreadProps transform", () => {
|
|
21
|
+
it("adds comment to TimeField with spread props", () => {
|
|
22
|
+
const input = `
|
|
23
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
24
|
+
|
|
25
|
+
export default function Test() {
|
|
26
|
+
const props = { onChange: handleChange }
|
|
27
|
+
return <TimeField {...props} value={time} onChange={setTime} />
|
|
28
|
+
}
|
|
29
|
+
`.trim()
|
|
30
|
+
|
|
31
|
+
const result = applyTransform(input)
|
|
32
|
+
expect(result).toContain(AUDIT_COMMENT)
|
|
33
|
+
expect(result).toContain("{...props}")
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it("returns null when TimeField has no spread props", () => {
|
|
37
|
+
const input = `
|
|
38
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
39
|
+
|
|
40
|
+
export default function Test() {
|
|
41
|
+
return <TimeField value={time} onChange={setTime} />
|
|
42
|
+
}
|
|
43
|
+
`.trim()
|
|
44
|
+
|
|
45
|
+
const result = applyTransform(input)
|
|
46
|
+
expect(result).toBe(null)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it("returns null when TimeField is from another package", () => {
|
|
50
|
+
const input = `
|
|
51
|
+
import { TimeField } from "other-library"
|
|
52
|
+
|
|
53
|
+
export default function Test() {
|
|
54
|
+
const props = { onChange: handleChange }
|
|
55
|
+
return <TimeField {...props} value={time} />
|
|
56
|
+
}
|
|
57
|
+
`.trim()
|
|
58
|
+
|
|
59
|
+
const result = applyTransform(input)
|
|
60
|
+
expect(result).toBe(null)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("returns null when TimeField is not imported at all", () => {
|
|
64
|
+
const input = `
|
|
65
|
+
import { Button } from "@planningcenter/tapestry-react"
|
|
66
|
+
|
|
67
|
+
export default function Test() {
|
|
68
|
+
const props = { onClick: handleClick }
|
|
69
|
+
return <Button {...props}>Save</Button>
|
|
70
|
+
}
|
|
71
|
+
`.trim()
|
|
72
|
+
|
|
73
|
+
const result = applyTransform(input)
|
|
74
|
+
expect(result).toBe(null)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { commentOnSpreadPropsFactory } from "../../shared/transformFactories/commentOnSpreadPropsFactory"
|
|
4
|
+
|
|
5
|
+
const transform: Transform = commentOnSpreadPropsFactory({
|
|
6
|
+
targetComponent: "TimeField",
|
|
7
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export default transform
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./convertStyleProps"
|
|
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("convertStyleProps transform", () => {
|
|
18
|
+
it("returns null when TimeField has no style props", () => {
|
|
19
|
+
const input = `
|
|
20
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
21
|
+
|
|
22
|
+
function Test() {
|
|
23
|
+
return <TimeField value={time} onChange={setTime} />
|
|
24
|
+
}
|
|
25
|
+
`.trim()
|
|
26
|
+
|
|
27
|
+
const result = applyTransform(input)
|
|
28
|
+
expect(result).toBeNull()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("does not affect TimeField from other packages", () => {
|
|
32
|
+
const input = `
|
|
33
|
+
import { TimeField } from "some-other-lib"
|
|
34
|
+
|
|
35
|
+
function Test() {
|
|
36
|
+
return <TimeField p={2} value={time} onChange={setTime} />
|
|
37
|
+
}
|
|
38
|
+
`.trim()
|
|
39
|
+
|
|
40
|
+
const result = applyTransform(input)
|
|
41
|
+
expect(result).toBeNull()
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { stackViewPlugin } from "../../../stubs/stackViewPlugin"
|
|
2
|
+
import { stylePropTransformFactory } from "../../shared/transformFactories/stylePropTransformFactory"
|
|
3
|
+
|
|
4
|
+
export default stylePropTransformFactory({
|
|
5
|
+
plugin: stackViewPlugin,
|
|
6
|
+
stylesToKeep: [],
|
|
7
|
+
stylesToRemove: [],
|
|
8
|
+
targetComponent: "TimeField",
|
|
9
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
10
|
+
})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./flagMinMax"
|
|
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("flagMinMax transform", () => {
|
|
18
|
+
it("adds TODO comment to min prop", () => {
|
|
19
|
+
const input = `
|
|
20
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
21
|
+
|
|
22
|
+
function Test() {
|
|
23
|
+
return <TimeField min={9} value={time} onChange={setTime} />
|
|
24
|
+
}
|
|
25
|
+
`.trim()
|
|
26
|
+
|
|
27
|
+
const result = applyTransform(input)
|
|
28
|
+
expect(result).not.toBeNull()
|
|
29
|
+
expect(result).toContain("TODO: tapestry-migration (min)")
|
|
30
|
+
expect(result).toContain("TimeValue")
|
|
31
|
+
expect(result).toContain("min={9}")
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("adds TODO comment to max prop", () => {
|
|
35
|
+
const input = `
|
|
36
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
37
|
+
|
|
38
|
+
function Test() {
|
|
39
|
+
return <TimeField max={17} value={time} onChange={setTime} />
|
|
40
|
+
}
|
|
41
|
+
`.trim()
|
|
42
|
+
|
|
43
|
+
const result = applyTransform(input)
|
|
44
|
+
expect(result).not.toBeNull()
|
|
45
|
+
expect(result).toContain("TODO: tapestry-migration (max)")
|
|
46
|
+
expect(result).toContain("TimeValue")
|
|
47
|
+
expect(result).toContain("max={17}")
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it("flags both min and max when present", () => {
|
|
51
|
+
const input = `
|
|
52
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
53
|
+
|
|
54
|
+
function Test() {
|
|
55
|
+
return <TimeField min={9} max={17} value={time} onChange={setTime} />
|
|
56
|
+
}
|
|
57
|
+
`.trim()
|
|
58
|
+
|
|
59
|
+
const result = applyTransform(input)
|
|
60
|
+
expect(result).not.toBeNull()
|
|
61
|
+
expect(result).toContain("TODO: tapestry-migration (min)")
|
|
62
|
+
expect(result).toContain("TODO: tapestry-migration (max)")
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it("returns null when neither min nor max is present", () => {
|
|
66
|
+
const input = `
|
|
67
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
68
|
+
|
|
69
|
+
function Test() {
|
|
70
|
+
return <TimeField value={time} onChange={setTime} />
|
|
71
|
+
}
|
|
72
|
+
`.trim()
|
|
73
|
+
|
|
74
|
+
const result = applyTransform(input)
|
|
75
|
+
expect(result).toBeNull()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it("does not affect other components", () => {
|
|
79
|
+
const input = `
|
|
80
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
81
|
+
|
|
82
|
+
function Test() {
|
|
83
|
+
return <Input min={1} max={10} label="Count" />
|
|
84
|
+
}
|
|
85
|
+
`.trim()
|
|
86
|
+
|
|
87
|
+
const result = applyTransform(input)
|
|
88
|
+
expect(result).toBeNull()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("does not affect TimeField from other packages", () => {
|
|
92
|
+
const input = `
|
|
93
|
+
import { TimeField } from "some-other-lib"
|
|
94
|
+
|
|
95
|
+
function Test() {
|
|
96
|
+
return <TimeField min={9} max={17} value={time} onChange={setTime} />
|
|
97
|
+
}
|
|
98
|
+
`.trim()
|
|
99
|
+
|
|
100
|
+
const result = applyTransform(input)
|
|
101
|
+
expect(result).toBeNull()
|
|
102
|
+
})
|
|
103
|
+
})
|