@planningcenter/tapestry-migration-cli 3.6.0 → 3.7.0-rc.0
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 +195 -11
- package/package.json +3 -3
- package/src/components/flex/index.test.ts +181 -0
- package/src/components/flex/index.ts +16 -0
- package/src/components/flex/transforms/alignmentToAlign.test.ts +101 -0
- package/src/components/flex/transforms/alignmentToAlign.ts +74 -20
- package/src/components/flex/transforms/auditSpreadProps.test.ts +93 -0
- package/src/components/flex/transforms/auditSpreadProps.ts +6 -0
- package/src/components/flex/transforms/axisToDirection.test.ts +15 -0
- package/src/components/flex/transforms/axisToDirection.ts +26 -0
- package/src/components/flex/transforms/constants.ts +4 -0
- package/src/components/flex/transforms/convertStyleProps.test.ts +147 -0
- package/src/components/flex/transforms/convertStyleProps.ts +23 -0
- package/src/components/flex/transforms/cssFlexAliasesToProps.test.ts +202 -0
- package/src/components/flex/transforms/cssFlexAliasesToProps.ts +106 -0
- package/src/components/flex/transforms/dedupeAttributes.test.ts +120 -0
- package/src/components/flex/transforms/dedupeAttributes.ts +49 -0
- package/src/components/flex/transforms/distributionToJustify.test.ts +59 -0
- package/src/components/flex/transforms/distributionToJustify.ts +69 -20
- package/src/components/flex/transforms/flexPropToExpand.test.ts +172 -0
- package/src/components/flex/transforms/flexPropToExpand.ts +100 -0
- package/src/components/flex/transforms/mediaQueriesToResponsive.test.ts +795 -0
- package/src/components/flex/transforms/mediaQueriesToResponsive.ts +562 -0
- package/src/components/flex/transforms/paddingPropsToFlex.test.ts +252 -0
- package/src/components/flex/transforms/paddingPropsToFlex.ts +150 -0
- package/src/components/flex/transforms/setDefaultAxis.test.ts +12 -0
- package/src/components/flex/transforms/setDefaultAxis.ts +1 -0
- package/src/components/flex/transforms/spacingToGap.test.ts +87 -0
- package/src/components/flex/transforms/spacingToGap.ts +28 -10
- package/src/components/flex/transforms/unsupportedProps.test.ts +141 -0
- package/src/components/flex/transforms/unsupportedProps.ts +17 -0
- package/src/components/shared/helpers/unsupportedPropsHelpers.ts +41 -0
- package/src/components/shared/transformFactories/stylePropTransformFactory.test.ts +47 -0
- package/src/components/shared/transformFactories/stylePropTransformFactory.ts +16 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { JSXAttribute } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
|
|
4
|
+
import { getAttribute } from "../../shared/actions/getAttribute"
|
|
5
|
+
import { transformAttributeName } from "../../shared/actions/transformAttributeName"
|
|
6
|
+
import { hasAttribute } from "../../shared/conditions/hasAttribute"
|
|
7
|
+
import { orConditions } from "../../shared/conditions/orConditions"
|
|
8
|
+
import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
|
|
9
|
+
import { FLEX_START_END_MAP } from "./constants"
|
|
10
|
+
|
|
11
|
+
const VALID_JUSTIFY_VALUES = new Set([
|
|
12
|
+
"start",
|
|
13
|
+
"center",
|
|
14
|
+
"end",
|
|
15
|
+
"space-between",
|
|
16
|
+
"space-evenly",
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
const VALID_ALIGN_VALUES = new Set([
|
|
20
|
+
"start",
|
|
21
|
+
"center",
|
|
22
|
+
"end",
|
|
23
|
+
"stretch",
|
|
24
|
+
"baseline",
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
function getStringValue(attr: JSXAttribute): string | null {
|
|
28
|
+
const { value } = attr
|
|
29
|
+
if (value?.type === "StringLiteral") return value.value
|
|
30
|
+
if (
|
|
31
|
+
value?.type === "JSXExpressionContainer" &&
|
|
32
|
+
value.expression.type === "StringLiteral"
|
|
33
|
+
)
|
|
34
|
+
return value.expression.value
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default attributeTransformFactory({
|
|
39
|
+
condition: orConditions(
|
|
40
|
+
hasAttribute("alignItems"),
|
|
41
|
+
hasAttribute("flexBasis"),
|
|
42
|
+
hasAttribute("flexDirection"),
|
|
43
|
+
hasAttribute("flexGrow"),
|
|
44
|
+
hasAttribute("flexShrink"),
|
|
45
|
+
hasAttribute("flexWrap"),
|
|
46
|
+
hasAttribute("justifyContent")
|
|
47
|
+
),
|
|
48
|
+
targetComponent: "StackView",
|
|
49
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
50
|
+
transform: (element, { j, options }) => {
|
|
51
|
+
let changed = false
|
|
52
|
+
|
|
53
|
+
if (transformAttributeName("flexBasis", "basis", { element, j, options }))
|
|
54
|
+
changed = true
|
|
55
|
+
if (
|
|
56
|
+
transformAttributeName("flexDirection", "direction", {
|
|
57
|
+
element,
|
|
58
|
+
j,
|
|
59
|
+
options,
|
|
60
|
+
})
|
|
61
|
+
)
|
|
62
|
+
changed = true
|
|
63
|
+
if (transformAttributeName("flexGrow", "grow", { element, j, options }))
|
|
64
|
+
changed = true
|
|
65
|
+
if (transformAttributeName("flexShrink", "shrink", { element, j, options }))
|
|
66
|
+
changed = true
|
|
67
|
+
if (transformAttributeName("flexWrap", "wrap", { element, j, options }))
|
|
68
|
+
changed = true
|
|
69
|
+
|
|
70
|
+
const justifyAttr = getAttribute({ element, name: "justifyContent" })
|
|
71
|
+
if (justifyAttr) {
|
|
72
|
+
const raw = getStringValue(justifyAttr)
|
|
73
|
+
const normalized = raw !== null ? (FLEX_START_END_MAP[raw] ?? raw) : null
|
|
74
|
+
if (normalized !== null && VALID_JUSTIFY_VALUES.has(normalized)) {
|
|
75
|
+
justifyAttr.name.name = "justify"
|
|
76
|
+
justifyAttr.value = j.stringLiteral(normalized)
|
|
77
|
+
} else {
|
|
78
|
+
addCommentToAttribute({
|
|
79
|
+
attribute: justifyAttr,
|
|
80
|
+
j,
|
|
81
|
+
text: `could not migrate to justify automatically; valid values are: ${[...VALID_JUSTIFY_VALUES].join(", ")}`,
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
changed = true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const alignAttr = getAttribute({ element, name: "alignItems" })
|
|
88
|
+
if (alignAttr) {
|
|
89
|
+
const raw = getStringValue(alignAttr)
|
|
90
|
+
const normalized = raw !== null ? (FLEX_START_END_MAP[raw] ?? raw) : null
|
|
91
|
+
if (normalized !== null && VALID_ALIGN_VALUES.has(normalized)) {
|
|
92
|
+
alignAttr.name.name = "align"
|
|
93
|
+
alignAttr.value = j.stringLiteral(normalized)
|
|
94
|
+
} else {
|
|
95
|
+
addCommentToAttribute({
|
|
96
|
+
attribute: alignAttr,
|
|
97
|
+
j,
|
|
98
|
+
text: `could not migrate to align automatically; valid values are: ${[...VALID_ALIGN_VALUES].join(", ")}`,
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
changed = true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return changed
|
|
105
|
+
},
|
|
106
|
+
})
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./dedupeAttributes"
|
|
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("dedupeAttributes transform", () => {
|
|
18
|
+
it("keeps the last of two duplicate attributes and drops the earlier one", () => {
|
|
19
|
+
const input = `
|
|
20
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
21
|
+
|
|
22
|
+
export default function Test() {
|
|
23
|
+
return <StackView direction="row" direction="column">Test</StackView>
|
|
24
|
+
}
|
|
25
|
+
`.trim()
|
|
26
|
+
|
|
27
|
+
const result = applyTransform(input)
|
|
28
|
+
expect(result).toContain('direction="column"')
|
|
29
|
+
expect(result?.match(/direction=/g)).toHaveLength(1)
|
|
30
|
+
expect(result).toContain("TODO: tapestry-migration (direction)")
|
|
31
|
+
expect(result).toContain("duplicate direction prop")
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("keeps the last of three duplicate attributes and drops the earlier ones", () => {
|
|
35
|
+
const input = `
|
|
36
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
37
|
+
|
|
38
|
+
export default function Test() {
|
|
39
|
+
return <StackView align="start" align="center" align="end">Test</StackView>
|
|
40
|
+
}
|
|
41
|
+
`.trim()
|
|
42
|
+
|
|
43
|
+
const result = applyTransform(input)
|
|
44
|
+
expect(result).toContain('align="end"')
|
|
45
|
+
expect(result?.match(/align=/g)).toHaveLength(1)
|
|
46
|
+
expect(result).toContain("TODO: tapestry-migration (align)")
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it("dedupes multiple different duplicated attribute names independently", () => {
|
|
50
|
+
const input = `
|
|
51
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
52
|
+
|
|
53
|
+
export default function Test() {
|
|
54
|
+
return <StackView direction="row" direction="column" align="start" align="end">Test</StackView>
|
|
55
|
+
}
|
|
56
|
+
`.trim()
|
|
57
|
+
|
|
58
|
+
const result = applyTransform(input)
|
|
59
|
+
expect(result?.match(/direction=/g)).toHaveLength(1)
|
|
60
|
+
expect(result?.match(/align=/g)).toHaveLength(1)
|
|
61
|
+
expect(result).toContain('direction="column"')
|
|
62
|
+
expect(result).toContain('align="end"')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it("preserves other non-duplicate attributes untouched", () => {
|
|
66
|
+
const input = `
|
|
67
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
68
|
+
|
|
69
|
+
export default function Test() {
|
|
70
|
+
return <StackView direction="row" direction="column" gap={2}>Test</StackView>
|
|
71
|
+
}
|
|
72
|
+
`.trim()
|
|
73
|
+
|
|
74
|
+
const result = applyTransform(input)
|
|
75
|
+
expect(result).toContain("gap={2}")
|
|
76
|
+
expect(result?.match(/direction=/g)).toHaveLength(1)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it("ignores spread attributes", () => {
|
|
80
|
+
const input = `
|
|
81
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
82
|
+
|
|
83
|
+
export default function Test() {
|
|
84
|
+
return <StackView {...props} direction="row" direction="column">Test</StackView>
|
|
85
|
+
}
|
|
86
|
+
`.trim()
|
|
87
|
+
|
|
88
|
+
const result = applyTransform(input)
|
|
89
|
+
expect(result).toContain("{...props}")
|
|
90
|
+
expect(result?.match(/direction=/g)).toHaveLength(1)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it("returns null when there are no duplicate attributes", () => {
|
|
94
|
+
const input = `
|
|
95
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
96
|
+
|
|
97
|
+
export default function Test() {
|
|
98
|
+
return <StackView direction="row" gap={2}>Test</StackView>
|
|
99
|
+
}
|
|
100
|
+
`.trim()
|
|
101
|
+
|
|
102
|
+
expect(applyTransform(input)).toBe(null)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it("returns null when StackView is not imported from tapestry-react", () => {
|
|
106
|
+
const input = `
|
|
107
|
+
import { StackView } from "some-other-package"
|
|
108
|
+
|
|
109
|
+
export default function Test() {
|
|
110
|
+
return <StackView direction="row" direction="column">Test</StackView>
|
|
111
|
+
}
|
|
112
|
+
`.trim()
|
|
113
|
+
|
|
114
|
+
expect(applyTransform(input)).toBe(null)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it("returns null for an empty file", () => {
|
|
118
|
+
expect(applyTransform("")).toBe(null)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { JSXAttribute } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
|
|
4
|
+
import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
|
|
5
|
+
|
|
6
|
+
export default attributeTransformFactory({
|
|
7
|
+
targetComponent: "StackView",
|
|
8
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
9
|
+
transform: (element, { j }) => {
|
|
10
|
+
const attrs = element.openingElement.attributes ?? []
|
|
11
|
+
const byName = new Map<string, JSXAttribute[]>()
|
|
12
|
+
|
|
13
|
+
for (const attr of attrs) {
|
|
14
|
+
if (attr.type !== "JSXAttribute") continue
|
|
15
|
+
if (attr.name.type !== "JSXIdentifier") continue
|
|
16
|
+
const name = attr.name.name
|
|
17
|
+
const group = byName.get(name)
|
|
18
|
+
if (group) {
|
|
19
|
+
group.push(attr)
|
|
20
|
+
} else {
|
|
21
|
+
byName.set(name, [attr])
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let changed = false
|
|
26
|
+
|
|
27
|
+
for (const [name, group] of byName) {
|
|
28
|
+
if (group.length < 2) continue
|
|
29
|
+
|
|
30
|
+
const kept = group[group.length - 1]
|
|
31
|
+
const dropped = group.slice(0, -1)
|
|
32
|
+
|
|
33
|
+
for (const attr of dropped) {
|
|
34
|
+
const idx = attrs.indexOf(attr)
|
|
35
|
+
attrs.splice(idx, 1)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
addCommentToAttribute({
|
|
39
|
+
attribute: kept,
|
|
40
|
+
j,
|
|
41
|
+
text: `duplicate ${name} prop after migration — an earlier ${name} value was dropped in favor of this one; verify this is correct`,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
changed = true
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return changed
|
|
48
|
+
},
|
|
49
|
+
})
|
|
@@ -114,6 +114,65 @@ export default function Test() {
|
|
|
114
114
|
expect(result).not.toContain("StackView.SPACE_EVENLY")
|
|
115
115
|
})
|
|
116
116
|
|
|
117
|
+
it("migrates a ternary with a valid string branch and an undefined pass-through branch", () => {
|
|
118
|
+
const input = `
|
|
119
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
120
|
+
|
|
121
|
+
export default function Test({ compact }) {
|
|
122
|
+
return <StackView distribution={compact ? 'center' : undefined}>Test</StackView>
|
|
123
|
+
}
|
|
124
|
+
`.trim()
|
|
125
|
+
|
|
126
|
+
const result = applyTransform(input)
|
|
127
|
+
expect(result).toContain('justify={compact ? "center" : undefined}')
|
|
128
|
+
expect(result).not.toContain("distribution")
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it("migrates a ternary with two valid string branches", () => {
|
|
132
|
+
const input = `
|
|
133
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
134
|
+
|
|
135
|
+
export default function Test({ compact }) {
|
|
136
|
+
return <StackView distribution={compact ? 'center' : 'start'}>Test</StackView>
|
|
137
|
+
}
|
|
138
|
+
`.trim()
|
|
139
|
+
|
|
140
|
+
const result = applyTransform(input)
|
|
141
|
+
expect(result).toContain('justify={compact ? "center" : "start"}')
|
|
142
|
+
expect(result).not.toContain("distribution")
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it("migrates a ternary with a dynamic branch, trusting it at runtime", () => {
|
|
146
|
+
const input = `
|
|
147
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
148
|
+
|
|
149
|
+
export default function Test({ compact, dynamicValue }) {
|
|
150
|
+
return <StackView distribution={compact ? 'center' : dynamicValue}>Test</StackView>
|
|
151
|
+
}
|
|
152
|
+
`.trim()
|
|
153
|
+
|
|
154
|
+
const result = applyTransform(input)
|
|
155
|
+
expect(result).toContain('justify={compact ? "center" : dynamicValue}')
|
|
156
|
+
expect(result).not.toContain("distribution")
|
|
157
|
+
expect(result).not.toContain("TODO")
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it("flags a ternary with a fill branch and leaves it in place", () => {
|
|
161
|
+
const input = `
|
|
162
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
163
|
+
|
|
164
|
+
export default function Test({ compact }) {
|
|
165
|
+
return <StackView distribution={compact ? 'center' : 'fill'}>Test</StackView>
|
|
166
|
+
}
|
|
167
|
+
`.trim()
|
|
168
|
+
|
|
169
|
+
const result = applyTransform(input)
|
|
170
|
+
expect(result).toContain("distribution={compact ? 'center' : 'fill'}")
|
|
171
|
+
expect(result).toContain("TODO: tapestry-migration (distribution)")
|
|
172
|
+
expect(result).not.toContain('justify="')
|
|
173
|
+
expect(result).not.toContain("justify={")
|
|
174
|
+
})
|
|
175
|
+
|
|
117
176
|
it("flags distribution={StackView.FILL} with a TODO and leaves it in place", () => {
|
|
118
177
|
const input = `
|
|
119
178
|
import { StackView } from "@planningcenter/tapestry-react"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Transform } from "jscodeshift"
|
|
1
|
+
import { Expression, Transform } from "jscodeshift"
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
|
|
4
4
|
import { getAttribute } from "../../shared/actions/getAttribute"
|
|
5
5
|
import { resolveConstantAttributeValue } from "../../shared/actions/resolveConstantAttributeValue"
|
|
6
6
|
import { hasAttribute } from "../../shared/conditions/hasAttribute"
|
|
@@ -23,45 +23,94 @@ const CONSTANT_DISTRIBUTION_MAP: Record<string, string> = {
|
|
|
23
23
|
START: "start",
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
const TODO_TEXT =
|
|
27
|
+
"could not migrate distribution to justify automatically; convert this value to start, center, end, space-between, or space-evenly manually"
|
|
28
|
+
|
|
29
|
+
function resolveJustifyValue(raw: string): string | null {
|
|
30
|
+
return VALID_JUSTIFY_VALUES.has(raw) ? raw : null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type BranchResolution =
|
|
34
|
+
| { kind: "invalid" }
|
|
35
|
+
| { kind: "passthrough" }
|
|
36
|
+
| { kind: "valid"; value: string }
|
|
37
|
+
|
|
38
|
+
function resolveBranch(node: Expression): BranchResolution {
|
|
39
|
+
if (node.type === "StringLiteral") {
|
|
40
|
+
const value = resolveJustifyValue(node.value)
|
|
41
|
+
return value !== null ? { kind: "valid", value } : { kind: "invalid" }
|
|
42
|
+
}
|
|
43
|
+
if (node.type === "MemberExpression" && node.property.type === "Identifier") {
|
|
44
|
+
const mapped = CONSTANT_DISTRIBUTION_MAP[node.property.name]
|
|
45
|
+
if (mapped === undefined) return { kind: "passthrough" }
|
|
46
|
+
const value = resolveJustifyValue(mapped)
|
|
47
|
+
return value !== null ? { kind: "valid", value } : { kind: "invalid" }
|
|
48
|
+
}
|
|
49
|
+
return { kind: "passthrough" }
|
|
50
|
+
}
|
|
51
|
+
|
|
26
52
|
const transform: Transform = attributeTransformFactory({
|
|
27
53
|
condition: hasAttribute("distribution"),
|
|
28
54
|
targetComponent: "StackView",
|
|
29
55
|
targetPackage: "@planningcenter/tapestry-react",
|
|
30
|
-
transform: (element, { j
|
|
56
|
+
transform: (element, { j }) => {
|
|
31
57
|
const distributionAttribute = getAttribute({
|
|
32
58
|
element,
|
|
33
59
|
name: "distribution",
|
|
34
60
|
})
|
|
35
61
|
if (!distributionAttribute) return false
|
|
36
62
|
|
|
63
|
+
const value = distributionAttribute.value
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
value?.type === "JSXExpressionContainer" &&
|
|
67
|
+
value.expression.type === "ConditionalExpression"
|
|
68
|
+
) {
|
|
69
|
+
const conditional = value.expression
|
|
70
|
+
const consequent = resolveBranch(conditional.consequent as Expression)
|
|
71
|
+
const alternate = resolveBranch(conditional.alternate as Expression)
|
|
72
|
+
|
|
73
|
+
if (consequent.kind !== "invalid" && alternate.kind !== "invalid") {
|
|
74
|
+
if (consequent.kind === "valid") {
|
|
75
|
+
conditional.consequent = j.stringLiteral(consequent.value)
|
|
76
|
+
}
|
|
77
|
+
if (alternate.kind === "valid") {
|
|
78
|
+
conditional.alternate = j.stringLiteral(alternate.value)
|
|
79
|
+
}
|
|
80
|
+
distributionAttribute.name.name = "justify"
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
addCommentToAttribute({
|
|
85
|
+
attribute: distributionAttribute,
|
|
86
|
+
j,
|
|
87
|
+
text: TODO_TEXT,
|
|
88
|
+
})
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
|
|
37
92
|
const resolved = resolveConstantAttributeValue({
|
|
38
93
|
attribute: distributionAttribute,
|
|
39
94
|
constantMap: CONSTANT_DISTRIBUTION_MAP,
|
|
40
95
|
})
|
|
41
|
-
const distributionValue =
|
|
42
|
-
resolved !== null &&
|
|
43
|
-
(VALID_JUSTIFY_VALUES.has(resolved) || resolved === "fill")
|
|
44
|
-
? resolved
|
|
45
|
-
: null
|
|
46
96
|
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
97
|
+
if (resolved === "fill") {
|
|
98
|
+
addCommentToAttribute({
|
|
99
|
+
attribute: distributionAttribute,
|
|
50
100
|
j,
|
|
51
|
-
|
|
52
|
-
source,
|
|
53
|
-
text: "could not migrate distribution to justify automatically; convert this value to start, center, end, space-between, or space-evenly manually",
|
|
101
|
+
text: 'distribution="fill" has no Flex equivalent; remove this prop and use expand on the child elements instead',
|
|
54
102
|
})
|
|
55
103
|
return true
|
|
56
104
|
}
|
|
57
105
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
106
|
+
const distributionValue =
|
|
107
|
+
resolved !== null ? resolveJustifyValue(resolved) : null
|
|
108
|
+
|
|
109
|
+
if (distributionValue === null) {
|
|
110
|
+
addCommentToAttribute({
|
|
111
|
+
attribute: distributionAttribute,
|
|
61
112
|
j,
|
|
62
|
-
|
|
63
|
-
source,
|
|
64
|
-
text: 'distribution="fill" has no Flex equivalent; remove this prop and use expand on the child elements instead',
|
|
113
|
+
text: TODO_TEXT,
|
|
65
114
|
})
|
|
66
115
|
return true
|
|
67
116
|
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./flexPropToExpand"
|
|
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("flexPropToExpand transform", () => {
|
|
18
|
+
it("converts flex={1} to expand", () => {
|
|
19
|
+
const input = `
|
|
20
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
21
|
+
export default function Test() {
|
|
22
|
+
return <StackView flex={1}>Test</StackView>
|
|
23
|
+
}
|
|
24
|
+
`.trim()
|
|
25
|
+
|
|
26
|
+
const result = applyTransform(input)
|
|
27
|
+
expect(result).toContain("expand")
|
|
28
|
+
expect(result).not.toContain("flex")
|
|
29
|
+
expect(result).not.toContain("TODO")
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('converts flex="1" to expand', () => {
|
|
33
|
+
const input = `
|
|
34
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
35
|
+
export default function Test() {
|
|
36
|
+
return <StackView flex="1">Test</StackView>
|
|
37
|
+
}
|
|
38
|
+
`.trim()
|
|
39
|
+
|
|
40
|
+
const result = applyTransform(input)
|
|
41
|
+
expect(result).toContain("expand")
|
|
42
|
+
expect(result).not.toContain("flex")
|
|
43
|
+
expect(result).not.toContain("TODO")
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('expands flex="1 1 0" into grow, shrink, and basis', () => {
|
|
47
|
+
const input = `
|
|
48
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
49
|
+
export default function Test() {
|
|
50
|
+
return <StackView flex="1 1 0">Test</StackView>
|
|
51
|
+
}
|
|
52
|
+
`.trim()
|
|
53
|
+
|
|
54
|
+
const result = applyTransform(input)
|
|
55
|
+
expect(result).toContain("grow={1}")
|
|
56
|
+
expect(result).toContain("shrink={1}")
|
|
57
|
+
expect(result).toContain('basis="0"')
|
|
58
|
+
expect(result).not.toContain("flex")
|
|
59
|
+
expect(result).not.toContain("TODO")
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('expands flex="1 0 0%" into grow, shrink, and basis', () => {
|
|
63
|
+
const input = `
|
|
64
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
65
|
+
export default function Test() {
|
|
66
|
+
return <StackView flex="1 0 0%">Test</StackView>
|
|
67
|
+
}
|
|
68
|
+
`.trim()
|
|
69
|
+
|
|
70
|
+
const result = applyTransform(input)
|
|
71
|
+
expect(result).toContain("grow={1}")
|
|
72
|
+
expect(result).toContain("shrink={0}")
|
|
73
|
+
expect(result).toContain('basis="0%"')
|
|
74
|
+
expect(result).not.toContain("flex")
|
|
75
|
+
expect(result).not.toContain("TODO")
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('expands flex="0 0 auto" into grow, shrink, and basis', () => {
|
|
79
|
+
const input = `
|
|
80
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
81
|
+
export default function Test() {
|
|
82
|
+
return <StackView flex="0 0 auto">Test</StackView>
|
|
83
|
+
}
|
|
84
|
+
`.trim()
|
|
85
|
+
|
|
86
|
+
const result = applyTransform(input)
|
|
87
|
+
expect(result).toContain("grow={0}")
|
|
88
|
+
expect(result).toContain("shrink={0}")
|
|
89
|
+
expect(result).toContain('basis="auto"')
|
|
90
|
+
expect(result).not.toContain("flex")
|
|
91
|
+
expect(result).not.toContain("TODO")
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it("expands flex={0} into grow={0} shrink={1} basis='0'", () => {
|
|
95
|
+
const input = `
|
|
96
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
97
|
+
export default function Test() {
|
|
98
|
+
return <StackView flex={0}>Test</StackView>
|
|
99
|
+
}
|
|
100
|
+
`.trim()
|
|
101
|
+
|
|
102
|
+
const result = applyTransform(input)
|
|
103
|
+
expect(result).toContain("grow={0}")
|
|
104
|
+
expect(result).toContain("shrink={1}")
|
|
105
|
+
expect(result).toContain('basis="0"')
|
|
106
|
+
expect(result).not.toContain("flex=")
|
|
107
|
+
expect(result).not.toContain("TODO")
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it("expands flex={2} into grow={2} shrink={1} basis='0'", () => {
|
|
111
|
+
const input = `
|
|
112
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
113
|
+
export default function Test() {
|
|
114
|
+
return <StackView flex={2}>Test</StackView>
|
|
115
|
+
}
|
|
116
|
+
`.trim()
|
|
117
|
+
|
|
118
|
+
const result = applyTransform(input)
|
|
119
|
+
expect(result).toContain("grow={2}")
|
|
120
|
+
expect(result).toContain("shrink={1}")
|
|
121
|
+
expect(result).toContain('basis="0"')
|
|
122
|
+
expect(result).not.toContain("flex=")
|
|
123
|
+
expect(result).not.toContain("TODO")
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it("adds a TODO for a dynamic flex value", () => {
|
|
127
|
+
const input = `
|
|
128
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
129
|
+
export default function Test() {
|
|
130
|
+
return <StackView flex={flexValue}>Test</StackView>
|
|
131
|
+
}
|
|
132
|
+
`.trim()
|
|
133
|
+
|
|
134
|
+
const result = applyTransform(input)
|
|
135
|
+
expect(result).toContain("TODO")
|
|
136
|
+
expect(result).toContain("flex={flexValue}")
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('adds a TODO for flex="auto"', () => {
|
|
140
|
+
const input = `
|
|
141
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
142
|
+
export default function Test() {
|
|
143
|
+
return <StackView flex="auto">Test</StackView>
|
|
144
|
+
}
|
|
145
|
+
`.trim()
|
|
146
|
+
|
|
147
|
+
const result = applyTransform(input)
|
|
148
|
+
expect(result).toContain("TODO")
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it("returns null when StackView has no flex prop", () => {
|
|
152
|
+
const input = `
|
|
153
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
154
|
+
export default function Test() {
|
|
155
|
+
return <StackView gap={2}>Test</StackView>
|
|
156
|
+
}
|
|
157
|
+
`.trim()
|
|
158
|
+
|
|
159
|
+
expect(applyTransform(input)).toBe(null)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it("returns null when StackView is not imported from tapestry-react", () => {
|
|
163
|
+
const input = `
|
|
164
|
+
import { StackView } from "some-other-package"
|
|
165
|
+
export default function Test() {
|
|
166
|
+
return <StackView flex={1}>Test</StackView>
|
|
167
|
+
}
|
|
168
|
+
`.trim()
|
|
169
|
+
|
|
170
|
+
expect(applyTransform(input)).toBe(null)
|
|
171
|
+
})
|
|
172
|
+
})
|