@planningcenter/tapestry-migration-cli 3.1.0-rc.6 → 3.1.0-rc.8
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/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 +8 -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 +391 -0
- package/src/components/input/transforms/mergeFieldIntoInput.ts +213 -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/removeTypeText.test.ts +160 -0
- package/src/components/input/transforms/removeTypeText.ts +18 -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/shared/helpers/unsupportedPropsHelpers.ts +52 -0
- package/src/components/shared/transformFactories/helpers/manageImports.ts +14 -12
- package/src/components/shared/transformFactories/sizeMappingFactory.ts +9 -2
- package/src/components/shared/transformFactories/stylePropTransformFactory.ts +54 -16
- package/src/components/shared/transformFactories/ternaryConditionalToPropFactory.ts +65 -0
|
@@ -136,7 +136,6 @@ export function removeImportFromDeclaration(
|
|
|
136
136
|
if (importIndex >= 0) {
|
|
137
137
|
specifiers.splice(importIndex, 1)
|
|
138
138
|
|
|
139
|
-
// Remove entire import if no specifiers left
|
|
140
139
|
if (specifiers.length === 0) {
|
|
141
140
|
importPath.get().prune()
|
|
142
141
|
}
|
|
@@ -162,19 +161,23 @@ export function addImport({
|
|
|
162
161
|
pkg: string
|
|
163
162
|
source: Collection
|
|
164
163
|
}): string {
|
|
165
|
-
// Check if component is already imported from the target package
|
|
166
164
|
const existingImportName = getImportName(component, pkg, { j, source })
|
|
167
165
|
if (existingImportName) {
|
|
168
166
|
return existingImportName
|
|
169
167
|
}
|
|
170
168
|
|
|
171
|
-
// Check for conflicts with imports from other packages
|
|
172
169
|
const hasConflict = hasConflictingImport(component, pkg, { j, source })
|
|
173
170
|
const finalComponentName = hasConflict ? conflictAlias : component
|
|
174
171
|
const alias =
|
|
175
172
|
finalComponentName !== component ? finalComponentName : undefined
|
|
176
173
|
|
|
177
|
-
//
|
|
174
|
+
// If there's a conflict but no meaningful alias (conflictAlias === component), skip
|
|
175
|
+
// adding the import to avoid duplicate identifiers. The conflicting import from another
|
|
176
|
+
// package will be migrated to the target package by another transform in the pipeline.
|
|
177
|
+
if (hasConflict && !alias) {
|
|
178
|
+
return finalComponentName
|
|
179
|
+
}
|
|
180
|
+
|
|
178
181
|
const targetImport = source
|
|
179
182
|
.find(j.ImportDeclaration, {
|
|
180
183
|
source: { value: pkg },
|
|
@@ -223,18 +226,17 @@ export function manageImports(
|
|
|
223
226
|
name: { name: sourceComponentName },
|
|
224
227
|
}).length > 0
|
|
225
228
|
|
|
226
|
-
// Handle source package import cleanup
|
|
227
|
-
|
|
228
|
-
|
|
229
|
+
// Handle source package import cleanup — search all declarations, not just the first,
|
|
230
|
+
// since a component may be in a separate import declaration from the same package.
|
|
231
|
+
if (!stillUsesSource) {
|
|
232
|
+
const sourceImports = source.find(j.ImportDeclaration, {
|
|
229
233
|
source: { value: config.fromPackage },
|
|
230
234
|
})
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
removeImportFromDeclaration(sourceImport, config.fromComponent)
|
|
235
|
+
for (let i = 0; i < sourceImports.length; i++) {
|
|
236
|
+
removeImportFromDeclaration(sourceImports.at(i), config.fromComponent)
|
|
237
|
+
}
|
|
235
238
|
}
|
|
236
239
|
|
|
237
|
-
// Handle target package import addition using addImport
|
|
238
240
|
addImport({
|
|
239
241
|
component: config.toComponent,
|
|
240
242
|
conflictAlias: config.conflictAlias || targetComponentName,
|
|
@@ -2,7 +2,9 @@ import { Transform } from "jscodeshift"
|
|
|
2
2
|
|
|
3
3
|
import { addCommentToAttribute } from "../actions/addCommentToAttribute"
|
|
4
4
|
import { getAttributeValue } from "../actions/getAttributeValue"
|
|
5
|
+
import { andConditions } from "../conditions/andConditions"
|
|
5
6
|
import { hasAttribute } from "../conditions/hasAttribute"
|
|
7
|
+
import { TransformCondition } from "../types"
|
|
6
8
|
import { attributeTransformFactory } from "./attributeTransformFactory"
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -13,16 +15,21 @@ import { attributeTransformFactory } from "./attributeTransformFactory"
|
|
|
13
15
|
* @param options.targetComponent - The component name to target (e.g., "ToggleSwitch", "Radio")
|
|
14
16
|
* @param options.targetPackage - The package the component is imported from
|
|
15
17
|
* @param options.sizeMapping - Object mapping old size values to new size values (e.g., { xs: "sm", lg: "md" })
|
|
18
|
+
* @param options.condition - Optional additional condition beyond hasAttribute("size")
|
|
16
19
|
* @returns A Transform function that maps size attributes
|
|
17
20
|
*/
|
|
18
21
|
export function sizeMappingFactory(options: {
|
|
22
|
+
condition?: TransformCondition
|
|
19
23
|
sizeMapping: Record<string, string>
|
|
20
24
|
targetComponent: string
|
|
21
25
|
targetPackage: string
|
|
22
26
|
}): Transform {
|
|
23
|
-
const { sizeMapping, ...restOptions } = options
|
|
27
|
+
const { condition, sizeMapping, ...restOptions } = options
|
|
28
|
+
const baseCondition = hasAttribute("size")
|
|
24
29
|
return attributeTransformFactory({
|
|
25
|
-
condition:
|
|
30
|
+
condition: condition
|
|
31
|
+
? andConditions(baseCondition, condition)
|
|
32
|
+
: baseCondition,
|
|
26
33
|
...restOptions,
|
|
27
34
|
transform: (element, { j }) => {
|
|
28
35
|
let hasChanges = false
|
|
@@ -16,6 +16,7 @@ import { addComment } 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"
|
|
19
|
+
import { TransformCondition } from "../types"
|
|
19
20
|
import { attributeTransformFactory } from "./attributeTransformFactory"
|
|
20
21
|
|
|
21
22
|
type StylePropMapping = Record<
|
|
@@ -26,16 +27,12 @@ type StylePropMapping = Record<
|
|
|
26
27
|
}
|
|
27
28
|
>
|
|
28
29
|
|
|
29
|
-
// Helper function to extract prop value from JSX attribute
|
|
30
30
|
function extractPropValue(attr: JSXAttribute, j: JSCodeshift) {
|
|
31
31
|
if (!attr.value) {
|
|
32
|
-
// Boolean prop like <Button disabled />
|
|
33
32
|
return true
|
|
34
33
|
} else if (attr.value.type === "StringLiteral") {
|
|
35
|
-
// String literal like color="blue"
|
|
36
34
|
return attr.value.value
|
|
37
35
|
} else if (attr.value.type === "JSXExpressionContainer") {
|
|
38
|
-
// Expression like color={someVar} or color={5}
|
|
39
36
|
const expression = attr.value.expression
|
|
40
37
|
if (expression.type === "StringLiteral") {
|
|
41
38
|
return expression.value
|
|
@@ -44,14 +41,11 @@ function extractPropValue(attr: JSXAttribute, j: JSCodeshift) {
|
|
|
44
41
|
} else if (expression.type === "BooleanLiteral") {
|
|
45
42
|
return expression.value
|
|
46
43
|
} else if (expression.type === "Identifier") {
|
|
47
|
-
// Variable reference - we'll use the variable name as a placeholder
|
|
48
44
|
return `{${expression.name}}`
|
|
49
45
|
} else {
|
|
50
|
-
// Complex expression - convert back to string representation
|
|
51
46
|
return `{${j(expression).toSource()}}`
|
|
52
47
|
}
|
|
53
48
|
} else {
|
|
54
|
-
// Fallback for other types
|
|
55
49
|
return j(attr.value).toSource()
|
|
56
50
|
}
|
|
57
51
|
}
|
|
@@ -77,7 +71,6 @@ function processKeepStyleProps({
|
|
|
77
71
|
const propName = attr.name.name as string
|
|
78
72
|
const propValue = extractPropValue(attr, j)
|
|
79
73
|
|
|
80
|
-
// If it's a complex expression (starts and ends with braces), handle it directly
|
|
81
74
|
if (
|
|
82
75
|
typeof propValue === "string" &&
|
|
83
76
|
propValue.startsWith("{") &&
|
|
@@ -118,7 +111,6 @@ function processRemoveStyleProps({
|
|
|
118
111
|
})
|
|
119
112
|
}
|
|
120
113
|
|
|
121
|
-
// Process props that need style property mappings
|
|
122
114
|
function processStylePropMappings({
|
|
123
115
|
attributes,
|
|
124
116
|
j,
|
|
@@ -187,11 +179,9 @@ function applyStylesToComponent({
|
|
|
187
179
|
value.startsWith("{") &&
|
|
188
180
|
value.endsWith("}")
|
|
189
181
|
) {
|
|
190
|
-
|
|
191
|
-
const expressionCode = value.slice(1, -1) // Remove surrounding braces
|
|
182
|
+
const expressionCode = value.slice(1, -1)
|
|
192
183
|
try {
|
|
193
184
|
const parsed = j(expressionCode)
|
|
194
|
-
// Get the first expression from the program body
|
|
195
185
|
const firstStatement = parsed.find(j.Program).get("body", 0).value
|
|
196
186
|
if (firstStatement?.type === "ExpressionStatement") {
|
|
197
187
|
valueNode = firstStatement.expression
|
|
@@ -199,7 +189,6 @@ function applyStylesToComponent({
|
|
|
199
189
|
valueNode = j.stringLiteral(value)
|
|
200
190
|
}
|
|
201
191
|
} catch {
|
|
202
|
-
// If parsing fails, fall back to string literal
|
|
203
192
|
valueNode = j.stringLiteral(value)
|
|
204
193
|
}
|
|
205
194
|
} else if (typeof value === "string") {
|
|
@@ -214,8 +203,56 @@ function applyStylesToComponent({
|
|
|
214
203
|
)
|
|
215
204
|
|
|
216
205
|
if (styleAttr && styleAttr.type === "JSXAttribute") {
|
|
217
|
-
|
|
218
|
-
|
|
206
|
+
const existingValue = styleAttr.value
|
|
207
|
+
if (
|
|
208
|
+
existingValue?.type === "JSXExpressionContainer" &&
|
|
209
|
+
existingValue.expression.type === "ObjectExpression"
|
|
210
|
+
) {
|
|
211
|
+
// Case 1: Existing style is a static object literal — merge, new props win
|
|
212
|
+
const newProps = (
|
|
213
|
+
styleValue.expression as ReturnType<typeof j.objectExpression>
|
|
214
|
+
).properties
|
|
215
|
+
const newKeys = new Set(
|
|
216
|
+
newProps
|
|
217
|
+
.filter((p) => p.type === "ObjectProperty")
|
|
218
|
+
.map((p) => {
|
|
219
|
+
const prop = p as ReturnType<typeof j.objectProperty>
|
|
220
|
+
if (prop.key.type === "Identifier") return prop.key.name
|
|
221
|
+
if (prop.key.type === "StringLiteral") return prop.key.value
|
|
222
|
+
return null
|
|
223
|
+
})
|
|
224
|
+
.filter(Boolean)
|
|
225
|
+
)
|
|
226
|
+
const survivingExisting = existingValue.expression.properties.filter(
|
|
227
|
+
(p) => {
|
|
228
|
+
if (p.type === "SpreadElement" || p.type === "RestElement")
|
|
229
|
+
return true
|
|
230
|
+
const prop = p as ReturnType<typeof j.objectProperty>
|
|
231
|
+
if (prop.key.type === "Identifier") return !newKeys.has(prop.key.name)
|
|
232
|
+
if (prop.key.type === "StringLiteral")
|
|
233
|
+
return !newKeys.has(prop.key.value)
|
|
234
|
+
return true
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
const mergedObject = j.objectExpression([
|
|
238
|
+
...survivingExisting,
|
|
239
|
+
...newProps,
|
|
240
|
+
])
|
|
241
|
+
styleAttr.value = j.jsxExpressionContainer(mergedObject)
|
|
242
|
+
} else if (
|
|
243
|
+
existingValue?.type === "JSXExpressionContainer" &&
|
|
244
|
+
existingValue.expression.type !== "JSXEmptyExpression"
|
|
245
|
+
) {
|
|
246
|
+
// Case 2: Dynamic expression — merge via spread: { ...existing, ...computed }
|
|
247
|
+
const newProps = (
|
|
248
|
+
styleValue.expression as ReturnType<typeof j.objectExpression>
|
|
249
|
+
).properties
|
|
250
|
+
const mergedObject = j.objectExpression([
|
|
251
|
+
j.spreadElement(existingValue.expression),
|
|
252
|
+
...newProps,
|
|
253
|
+
])
|
|
254
|
+
styleAttr.value = j.jsxExpressionContainer(mergedObject)
|
|
255
|
+
}
|
|
219
256
|
} else {
|
|
220
257
|
const styleAttr = j.jsxAttribute(j.jsxIdentifier("style"), styleValue)
|
|
221
258
|
|
|
@@ -225,6 +262,7 @@ function applyStylesToComponent({
|
|
|
225
262
|
}
|
|
226
263
|
|
|
227
264
|
export function stylePropTransformFactory(config: {
|
|
265
|
+
condition?: TransformCondition
|
|
228
266
|
plugin?: {
|
|
229
267
|
getStyles: (props: Record<string, unknown>) => Record<string, unknown>
|
|
230
268
|
styleProps: string[]
|
|
@@ -321,7 +359,6 @@ export function stylePropTransformFactory(config: {
|
|
|
321
359
|
styles = { ...styles, ...directStyleProps }
|
|
322
360
|
if (options.verbose) console.log("Final generated styles:", styles)
|
|
323
361
|
|
|
324
|
-
// Only apply styles if there are actual CSS properties to add
|
|
325
362
|
if (Object.keys(styles).length > 0) {
|
|
326
363
|
applyStylesToComponent({ element, j, styles })
|
|
327
364
|
|
|
@@ -354,6 +391,7 @@ export function stylePropTransformFactory(config: {
|
|
|
354
391
|
}
|
|
355
392
|
|
|
356
393
|
return attributeTransformFactory({
|
|
394
|
+
condition: config.condition,
|
|
357
395
|
targetComponent: config.targetComponent,
|
|
358
396
|
targetPackage: config.targetPackage,
|
|
359
397
|
transform,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Expression, Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { getAttribute } from "../actions/getAttribute"
|
|
4
|
+
import { removeAttribute } from "../actions/removeAttribute"
|
|
5
|
+
import { TransformCondition } from "../types"
|
|
6
|
+
import { attributeTransformFactory } from "./attributeTransformFactory"
|
|
7
|
+
|
|
8
|
+
interface TernaryConditionalToPropConfig {
|
|
9
|
+
condition?: TransformCondition
|
|
10
|
+
fromProp: string
|
|
11
|
+
matchValue: string
|
|
12
|
+
targetComponent: string
|
|
13
|
+
targetPackage: string
|
|
14
|
+
toProp: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ternaryConditionalToPropFactory({
|
|
18
|
+
condition,
|
|
19
|
+
fromProp,
|
|
20
|
+
matchValue,
|
|
21
|
+
targetComponent,
|
|
22
|
+
targetPackage,
|
|
23
|
+
toProp,
|
|
24
|
+
}: TernaryConditionalToPropConfig): Transform {
|
|
25
|
+
return attributeTransformFactory({
|
|
26
|
+
condition,
|
|
27
|
+
targetComponent,
|
|
28
|
+
targetPackage,
|
|
29
|
+
transform: (element, { j, source }) => {
|
|
30
|
+
const attr = getAttribute({ element, name: fromProp })
|
|
31
|
+
if (!attr) return false
|
|
32
|
+
|
|
33
|
+
if (attr.value?.type !== "JSXExpressionContainer") return false
|
|
34
|
+
|
|
35
|
+
const expr = attr.value.expression
|
|
36
|
+
if (expr.type !== "ConditionalExpression") return false
|
|
37
|
+
|
|
38
|
+
if (
|
|
39
|
+
expr.consequent.type !== "StringLiteral" ||
|
|
40
|
+
expr.consequent.value !== matchValue
|
|
41
|
+
) {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const isNullAlternate = expr.alternate.type === "NullLiteral"
|
|
46
|
+
const isUndefinedAlternate =
|
|
47
|
+
expr.alternate.type === "Identifier" &&
|
|
48
|
+
expr.alternate.name === "undefined"
|
|
49
|
+
|
|
50
|
+
if (!isNullAlternate && !isUndefinedAlternate) return false
|
|
51
|
+
|
|
52
|
+
const testExpr = expr.test as Expression
|
|
53
|
+
|
|
54
|
+
removeAttribute(fromProp, { element, j, source })
|
|
55
|
+
element.openingElement.attributes.push(
|
|
56
|
+
j.jsxAttribute(
|
|
57
|
+
j.jsxIdentifier(toProp),
|
|
58
|
+
j.jsxExpressionContainer(testExpr)
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return true
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
}
|