@planningcenter/tapestry-migration-cli 2.1.1-rc.2 → 2.1.1-rc.3
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 +2 -2
- package/src/components/button/index.ts +2 -1
- package/src/components/button/transforms/linkToButton.ts +4 -9
- package/src/components/button/transforms/titleToLabel.test.ts +418 -0
- package/src/components/button/transforms/titleToLabel.ts +19 -0
- package/src/components/shared/actions/transformAttributeName.ts +20 -0
- package/src/components/shared/actions/transformElementName.test.ts +59 -0
- package/src/components/shared/actions/transformElementName.ts +27 -0
- package/src/components/shared/conditions/andConditions.test.ts +65 -0
- package/src/components/shared/conditions/andConditions.ts +13 -0
- package/src/components/shared/conditions/hasAttribute.test.ts +43 -0
- package/src/components/shared/conditions/hasAttribute.ts +18 -0
- package/src/components/shared/conditions/hasAttributeValue.test.ts +48 -0
- package/src/components/shared/conditions/hasAttributeValue.ts +23 -0
- package/src/components/shared/conditions/helpers/createJSXElement.ts +9 -0
- package/src/components/shared/conditions/index.test.ts +63 -0
- package/src/components/shared/conditions/orConditions.test.ts +76 -0
- package/src/components/shared/conditions/orConditions.ts +13 -0
- package/src/components/shared/findAttribute.ts +15 -0
- package/src/components/shared/transformFactories/attributeTransformFactory.test.ts +88 -0
- package/src/components/shared/transformFactories/attributeTransformFactory.ts +51 -0
- package/src/components/shared/transformFactories/componentTransformFactory.test.ts +7 -0
- package/src/components/shared/transformFactories/componentTransformFactory.ts +77 -0
- package/src/components/shared/{componentTransformUtilities.test.ts → transformFactories/helpers/manageImports.test.ts} +1 -55
- package/src/components/shared/{componentTransformUtilities.ts → transformFactories/helpers/manageImports.ts} +17 -93
- package/src/components/shared/types.ts +6 -0
- package/src/components/shared/getTapestryReactImportName.ts +0 -35
- package/src/components/shared/transformConfig.test.ts +0 -288
- package/src/components/shared/transformConfig.ts +0 -79
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { hasAttribute } from "./hasAttribute"
|
|
4
|
+
import { createJSXElement } from "./helpers/createJSXElement"
|
|
5
|
+
|
|
6
|
+
describe("hasAttribute", () => {
|
|
7
|
+
it("should return true when attribute exists", () => {
|
|
8
|
+
const condition = hasAttribute("href")
|
|
9
|
+
const element = createJSXElement(' href="/test"')
|
|
10
|
+
|
|
11
|
+
expect(condition(element)).toBe(true)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it("should return false when attribute does not exist", () => {
|
|
15
|
+
const condition = hasAttribute("href")
|
|
16
|
+
const element = createJSXElement(' className="test"')
|
|
17
|
+
|
|
18
|
+
expect(condition(element)).toBe(false)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it("should return true for attribute without value", () => {
|
|
22
|
+
const condition = hasAttribute("disabled")
|
|
23
|
+
const element = createJSXElement(" disabled")
|
|
24
|
+
|
|
25
|
+
expect(condition(element)).toBe(true)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("should return false for empty element", () => {
|
|
29
|
+
const condition = hasAttribute("href")
|
|
30
|
+
const element = createJSXElement("")
|
|
31
|
+
|
|
32
|
+
expect(condition(element)).toBe(false)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it("should handle multiple attributes", () => {
|
|
36
|
+
const condition = hasAttribute("onClick")
|
|
37
|
+
const element = createJSXElement(
|
|
38
|
+
' className="test" onClick={handleClick} disabled'
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
expect(condition(element)).toBe(true)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { JSXElement } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { TransformCondition } from "../types"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Helper function to create a condition that checks for the presence of an attribute
|
|
7
|
+
*/
|
|
8
|
+
export function hasAttribute(attributeName: string): TransformCondition {
|
|
9
|
+
return (element: JSXElement) => {
|
|
10
|
+
const attributes = element.openingElement.attributes || []
|
|
11
|
+
return attributes.some(
|
|
12
|
+
(attr) =>
|
|
13
|
+
attr.type === "JSXAttribute" &&
|
|
14
|
+
attr.name?.type === "JSXIdentifier" &&
|
|
15
|
+
attr.name.name === attributeName
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { hasAttributeValue } from "./hasAttributeValue"
|
|
4
|
+
import { createJSXElement } from "./helpers/createJSXElement"
|
|
5
|
+
|
|
6
|
+
describe("hasAttributeValue", () => {
|
|
7
|
+
it("should return true when attribute has exact string value", () => {
|
|
8
|
+
const condition = hasAttributeValue("as", "a")
|
|
9
|
+
const element = createJSXElement(' as="a"')
|
|
10
|
+
|
|
11
|
+
expect(condition(element)).toBe(true)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it("should return false when attribute has different value", () => {
|
|
15
|
+
const condition = hasAttributeValue("as", "a")
|
|
16
|
+
const element = createJSXElement(' as="button"')
|
|
17
|
+
|
|
18
|
+
expect(condition(element)).toBe(false)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it("should return false when attribute does not exist", () => {
|
|
22
|
+
const condition = hasAttributeValue("as", "a")
|
|
23
|
+
const element = createJSXElement(' className="test"')
|
|
24
|
+
|
|
25
|
+
expect(condition(element)).toBe(false)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("should return false when attribute exists but has no value", () => {
|
|
29
|
+
const condition = hasAttributeValue("disabled", "true")
|
|
30
|
+
const element = createJSXElement(" disabled")
|
|
31
|
+
|
|
32
|
+
expect(condition(element)).toBe(false)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it("should return false for expression values", () => {
|
|
36
|
+
const condition = hasAttributeValue("onClick", "handleClick")
|
|
37
|
+
const element = createJSXElement(" onClick={handleClick}")
|
|
38
|
+
|
|
39
|
+
expect(condition(element)).toBe(false)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it("should handle multiple attributes with different values", () => {
|
|
43
|
+
const condition = hasAttributeValue("type", "submit")
|
|
44
|
+
const element = createJSXElement(' className="test" type="submit" disabled')
|
|
45
|
+
|
|
46
|
+
expect(condition(element)).toBe(true)
|
|
47
|
+
})
|
|
48
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { JSXElement } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { TransformCondition } from "../types"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Helper function to create a condition that checks for an attribute with a specific value
|
|
7
|
+
*/
|
|
8
|
+
export function hasAttributeValue(
|
|
9
|
+
attributeName: string,
|
|
10
|
+
value: string
|
|
11
|
+
): TransformCondition {
|
|
12
|
+
return (element: JSXElement) => {
|
|
13
|
+
const attributes = element.openingElement.attributes || []
|
|
14
|
+
return attributes.some(
|
|
15
|
+
(attr) =>
|
|
16
|
+
attr.type === "JSXAttribute" &&
|
|
17
|
+
attr.name?.type === "JSXIdentifier" &&
|
|
18
|
+
attr.name.name === attributeName &&
|
|
19
|
+
attr.value?.type === "StringLiteral" &&
|
|
20
|
+
attr.value.value === value
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import jscodeshift, { JSXElement } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
const j = jscodeshift.withParser("tsx")
|
|
4
|
+
|
|
5
|
+
export function createJSXElement(attributes: string): JSXElement {
|
|
6
|
+
const code = `<Button${attributes}>Content</Button>`
|
|
7
|
+
const ast = j(code)
|
|
8
|
+
return ast.find(j.JSXElement).get().value as JSXElement
|
|
9
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { andConditions } from "./andConditions"
|
|
4
|
+
import { hasAttribute } from "./hasAttribute"
|
|
5
|
+
import { hasAttributeValue } from "./hasAttributeValue"
|
|
6
|
+
import { createJSXElement } from "./helpers/createJSXElement"
|
|
7
|
+
import { orConditions } from "./orConditions"
|
|
8
|
+
|
|
9
|
+
describe("transformConfig", () => {
|
|
10
|
+
describe("complex condition combinations", () => {
|
|
11
|
+
it("should handle nested and/or conditions", () => {
|
|
12
|
+
const condition = andConditions(
|
|
13
|
+
hasAttribute("className"),
|
|
14
|
+
orConditions(hasAttribute("href"), hasAttribute("onClick"))
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// Should match className + href
|
|
18
|
+
const element1 = createJSXElement(' className="btn" href="/test"')
|
|
19
|
+
expect(condition(element1)).toBe(true)
|
|
20
|
+
|
|
21
|
+
// Should match className + onClick
|
|
22
|
+
const element2 = createJSXElement(' className="btn" onClick={handle}')
|
|
23
|
+
expect(condition(element2)).toBe(true)
|
|
24
|
+
|
|
25
|
+
// Should not match (missing className)
|
|
26
|
+
const element3 = createJSXElement(' href="/test"')
|
|
27
|
+
expect(condition(element3)).toBe(false)
|
|
28
|
+
|
|
29
|
+
// Should not match (missing href/onClick)
|
|
30
|
+
const element4 = createJSXElement(' className="btn"')
|
|
31
|
+
expect(condition(element4)).toBe(false)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("should handle or of and conditions", () => {
|
|
35
|
+
const condition = orConditions(
|
|
36
|
+
andConditions(
|
|
37
|
+
hasAttribute("href"),
|
|
38
|
+
hasAttributeValue("target", "_blank")
|
|
39
|
+
),
|
|
40
|
+
andConditions(
|
|
41
|
+
hasAttribute("onClick"),
|
|
42
|
+
hasAttributeValue("type", "button")
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
// Should match first AND condition
|
|
47
|
+
const element1 = createJSXElement(' href="/test" target="_blank"')
|
|
48
|
+
expect(condition(element1)).toBe(true)
|
|
49
|
+
|
|
50
|
+
// Should match second AND condition
|
|
51
|
+
const element2 = createJSXElement(' onClick={handle} type="button"')
|
|
52
|
+
expect(condition(element2)).toBe(true)
|
|
53
|
+
|
|
54
|
+
// Should not match (partial first condition)
|
|
55
|
+
const element3 = createJSXElement(' href="/test" target="_self"')
|
|
56
|
+
expect(condition(element3)).toBe(false)
|
|
57
|
+
|
|
58
|
+
// Should not match (partial second condition)
|
|
59
|
+
const element4 = createJSXElement(' onClick={handle} type="submit"')
|
|
60
|
+
expect(condition(element4)).toBe(false)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { hasAttribute } from "./hasAttribute"
|
|
4
|
+
import { hasAttributeValue } from "./hasAttributeValue"
|
|
5
|
+
import { createJSXElement } from "./helpers/createJSXElement"
|
|
6
|
+
import { orConditions } from "./orConditions"
|
|
7
|
+
|
|
8
|
+
describe("orConditions", () => {
|
|
9
|
+
it("should return true when at least one condition is met", () => {
|
|
10
|
+
const condition = orConditions(
|
|
11
|
+
hasAttribute("href"),
|
|
12
|
+
hasAttribute("onClick")
|
|
13
|
+
)
|
|
14
|
+
const element = createJSXElement(' href="/test"')
|
|
15
|
+
|
|
16
|
+
expect(condition(element)).toBe(true)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it("should return true when all conditions are met", () => {
|
|
20
|
+
const condition = orConditions(
|
|
21
|
+
hasAttribute("href"),
|
|
22
|
+
hasAttribute("onClick")
|
|
23
|
+
)
|
|
24
|
+
const element = createJSXElement(' href="/test" onClick={handleClick}')
|
|
25
|
+
|
|
26
|
+
expect(condition(element)).toBe(true)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("should return false when no conditions are met", () => {
|
|
30
|
+
const condition = orConditions(
|
|
31
|
+
hasAttribute("href"),
|
|
32
|
+
hasAttribute("onClick")
|
|
33
|
+
)
|
|
34
|
+
const element = createJSXElement(' className="test"')
|
|
35
|
+
|
|
36
|
+
expect(condition(element)).toBe(false)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("should handle single condition", () => {
|
|
40
|
+
const condition = orConditions(hasAttribute("disabled"))
|
|
41
|
+
const element = createJSXElement(" disabled")
|
|
42
|
+
|
|
43
|
+
expect(condition(element)).toBe(true)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it("should handle empty conditions (return false)", () => {
|
|
47
|
+
const condition = orConditions()
|
|
48
|
+
const element = createJSXElement("")
|
|
49
|
+
|
|
50
|
+
expect(condition(element)).toBe(false)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it("should handle mixed attribute and value conditions", () => {
|
|
54
|
+
const condition = orConditions(
|
|
55
|
+
hasAttributeValue("type", "submit"),
|
|
56
|
+
hasAttribute("href"),
|
|
57
|
+
hasAttributeValue("role", "button")
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
// Should match type="submit"
|
|
61
|
+
const element1 = createJSXElement(' type="submit"')
|
|
62
|
+
expect(condition(element1)).toBe(true)
|
|
63
|
+
|
|
64
|
+
// Should match href
|
|
65
|
+
const element2 = createJSXElement(' href="/test"')
|
|
66
|
+
expect(condition(element2)).toBe(true)
|
|
67
|
+
|
|
68
|
+
// Should match role="button"
|
|
69
|
+
const element3 = createJSXElement(' role="button"')
|
|
70
|
+
expect(condition(element3)).toBe(true)
|
|
71
|
+
|
|
72
|
+
// Should not match
|
|
73
|
+
const element4 = createJSXElement(' className="test" type="button"')
|
|
74
|
+
expect(condition(element4)).toBe(false)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { JSXElement } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { TransformCondition } from "../types"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Helper function to combine multiple conditions with OR logic
|
|
7
|
+
*/
|
|
8
|
+
export function orConditions(
|
|
9
|
+
...conditions: TransformCondition[]
|
|
10
|
+
): TransformCondition {
|
|
11
|
+
return (element: JSXElement) =>
|
|
12
|
+
conditions.some((condition) => condition(element))
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { JSXAttribute, JSXOpeningElement } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Finds an attribute by name in a JSX element's attributes array
|
|
5
|
+
*/
|
|
6
|
+
export function findAttribute(
|
|
7
|
+
attributes: JSXOpeningElement["attributes"],
|
|
8
|
+
attributeName: string
|
|
9
|
+
): JSXAttribute | undefined {
|
|
10
|
+
if (!attributes) return
|
|
11
|
+
|
|
12
|
+
return attributes.find(
|
|
13
|
+
(attr) => attr.type === "JSXAttribute" && attr.name?.name === attributeName
|
|
14
|
+
) as JSXAttribute | undefined
|
|
15
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import { hasAttribute } from "../conditions/hasAttribute"
|
|
5
|
+
import { attributeTransformFactory } from "./attributeTransformFactory"
|
|
6
|
+
|
|
7
|
+
const j = jscodeshift.withParser("tsx")
|
|
8
|
+
|
|
9
|
+
describe("attributeTransformFactory", () => {
|
|
10
|
+
it("should create a working attribute transform", () => {
|
|
11
|
+
const transform = attributeTransformFactory({
|
|
12
|
+
condition: hasAttribute("title"),
|
|
13
|
+
targetComponent: "Button",
|
|
14
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
15
|
+
transform: (element) => {
|
|
16
|
+
const attributes = element.openingElement.attributes || []
|
|
17
|
+
const titleAttr = attributes.find(
|
|
18
|
+
(attr) => attr.type === "JSXAttribute" && attr.name?.name === "title"
|
|
19
|
+
)
|
|
20
|
+
if (titleAttr?.type === "JSXAttribute") {
|
|
21
|
+
titleAttr.name.name = "label"
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
return false
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
const fileInfo = {
|
|
28
|
+
path: "test.tsx",
|
|
29
|
+
source: `import { Button } from "@planningcenter/tapestry-react";<Button title="Save">Test</Button>`,
|
|
30
|
+
}
|
|
31
|
+
const api = { j, jscodeshift: j, report: () => {}, stats: () => {} }
|
|
32
|
+
|
|
33
|
+
const result = transform(fileInfo, api, {})
|
|
34
|
+
expect(result).toContain('label="Save"')
|
|
35
|
+
expect(result).not.toContain("title")
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it("should return null when target component not imported", () => {
|
|
39
|
+
const transform = attributeTransformFactory({
|
|
40
|
+
condition: hasAttribute("title"),
|
|
41
|
+
targetComponent: "Button",
|
|
42
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
43
|
+
transform: () => true,
|
|
44
|
+
})
|
|
45
|
+
const fileInfo = {
|
|
46
|
+
path: "test.tsx",
|
|
47
|
+
source: `import { Link } from "@planningcenter/tapestry-react";<Link title="Test">Test</Link>`,
|
|
48
|
+
}
|
|
49
|
+
const api = { j, jscodeshift: j, report: () => {}, stats: () => {} }
|
|
50
|
+
|
|
51
|
+
const result = transform(fileInfo, api, {})
|
|
52
|
+
expect(result).toBe(null)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it("should return null when condition not met", () => {
|
|
56
|
+
const transform = attributeTransformFactory({
|
|
57
|
+
condition: hasAttribute("nonexistent"),
|
|
58
|
+
targetComponent: "Button",
|
|
59
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
60
|
+
transform: () => true,
|
|
61
|
+
})
|
|
62
|
+
const fileInfo = {
|
|
63
|
+
path: "test.tsx",
|
|
64
|
+
source: `import { Button } from "@planningcenter/tapestry-react";<Button>Test</Button>`,
|
|
65
|
+
}
|
|
66
|
+
const api = { j, jscodeshift: j, report: () => {}, stats: () => {} }
|
|
67
|
+
|
|
68
|
+
const result = transform(fileInfo, api, {})
|
|
69
|
+
expect(result).toBe(null)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it("should return null when transform returns false", () => {
|
|
73
|
+
const transform = attributeTransformFactory({
|
|
74
|
+
condition: hasAttribute("title"),
|
|
75
|
+
targetComponent: "Button",
|
|
76
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
77
|
+
transform: () => false,
|
|
78
|
+
})
|
|
79
|
+
const fileInfo = {
|
|
80
|
+
path: "test.tsx",
|
|
81
|
+
source: `import { Button } from "@planningcenter/tapestry-react";<Button title="Test">Test</Button>`,
|
|
82
|
+
}
|
|
83
|
+
const api = { j, jscodeshift: j, report: () => {}, stats: () => {} }
|
|
84
|
+
|
|
85
|
+
const result = transform(fileInfo, api, {})
|
|
86
|
+
expect(result).toBe(null)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { JSCodeshift, JSXElement, Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { TransformCondition } from "../types"
|
|
4
|
+
import { getImportName } from "./helpers/manageImports"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Main function to create an attribute transform based on configuration
|
|
8
|
+
*/
|
|
9
|
+
export function attributeTransformFactory(config: {
|
|
10
|
+
/** Condition that must be met for the transform to occur */
|
|
11
|
+
condition: TransformCondition
|
|
12
|
+
/** Component to target for attribute transformation */
|
|
13
|
+
targetComponent: string
|
|
14
|
+
/** Package the target component is imported from */
|
|
15
|
+
targetPackage: string
|
|
16
|
+
/** Function that performs the actual attribute transformation */
|
|
17
|
+
transform: (element: JSXElement, j: JSCodeshift) => boolean
|
|
18
|
+
}): Transform {
|
|
19
|
+
return (fileInfo, api) => {
|
|
20
|
+
const j = api.jscodeshift
|
|
21
|
+
const source = j(fileInfo.source)
|
|
22
|
+
let hasChanges = false
|
|
23
|
+
|
|
24
|
+
// Get the local name of the target component
|
|
25
|
+
const targetComponentName = getImportName(
|
|
26
|
+
config.targetComponent,
|
|
27
|
+
config.targetPackage,
|
|
28
|
+
{ j, source }
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// Only proceed if target component is imported
|
|
32
|
+
if (!targetComponentName) {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Find and transform matching JSX elements
|
|
37
|
+
source
|
|
38
|
+
.find(j.JSXOpeningElement, { name: { name: targetComponentName } })
|
|
39
|
+
.forEach((path) => {
|
|
40
|
+
const element = path.parent.value
|
|
41
|
+
|
|
42
|
+
if (config.condition(element)) {
|
|
43
|
+
if (config.transform(element, j)) {
|
|
44
|
+
hasChanges = true
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
return hasChanges ? source.toSource() : null
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { transformElementName } from "../actions/transformElementName"
|
|
4
|
+
import { TransformCondition } from "../types"
|
|
5
|
+
import {
|
|
6
|
+
getImportName,
|
|
7
|
+
hasConflictingImport,
|
|
8
|
+
manageImports,
|
|
9
|
+
} from "./helpers/manageImports"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Main function to create a component transform based on configuration
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export function componentTransformFactory(config: {
|
|
16
|
+
/** Condition that must be met for the transform to occur */
|
|
17
|
+
condition: TransformCondition
|
|
18
|
+
/** Optional alias to use if target component conflicts with existing imports */
|
|
19
|
+
conflictAlias?: string
|
|
20
|
+
/** The source component name to transform from */
|
|
21
|
+
fromComponent: string
|
|
22
|
+
/** The package to import the source component from */
|
|
23
|
+
fromPackage: string
|
|
24
|
+
/** The target component name to transform to */
|
|
25
|
+
toComponent: string
|
|
26
|
+
/** The package to import the target component from */
|
|
27
|
+
toPackage: string
|
|
28
|
+
}): Transform {
|
|
29
|
+
return (fileInfo, api) => {
|
|
30
|
+
const j = api.jscodeshift
|
|
31
|
+
const source = j(fileInfo.source)
|
|
32
|
+
let hasChanges = false
|
|
33
|
+
|
|
34
|
+
// Get the local name of the source component
|
|
35
|
+
const sourceComponentName = getImportName(
|
|
36
|
+
config.fromComponent,
|
|
37
|
+
config.fromPackage,
|
|
38
|
+
{ j, source }
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
// Only proceed if source component is imported
|
|
42
|
+
if (!sourceComponentName) {
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check for conflicting imports
|
|
47
|
+
const hasConflict = hasConflictingImport(
|
|
48
|
+
config.toComponent,
|
|
49
|
+
config.toPackage,
|
|
50
|
+
{ j, source }
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const targetComponentName = hasConflict
|
|
54
|
+
? config.conflictAlias || `T${config.toComponent}`
|
|
55
|
+
: config.toComponent
|
|
56
|
+
|
|
57
|
+
// Transform matching JSX elements
|
|
58
|
+
source
|
|
59
|
+
.find(j.JSXOpeningElement, { name: { name: sourceComponentName } })
|
|
60
|
+
.forEach((path) => {
|
|
61
|
+
const element = path.parent.value
|
|
62
|
+
|
|
63
|
+
if (config.condition(element)) {
|
|
64
|
+
if (transformElementName(path, targetComponentName)) {
|
|
65
|
+
hasChanges = true
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
if (hasChanges) {
|
|
71
|
+
// Handle import management
|
|
72
|
+
manageImports(source, config, sourceComponentName, targetComponentName, j)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return hasChanges ? source.toSource() : null
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -7,8 +7,7 @@ import {
|
|
|
7
7
|
getImportName,
|
|
8
8
|
hasConflictingImport,
|
|
9
9
|
removeImportFromDeclaration,
|
|
10
|
-
|
|
11
|
-
} from "./componentTransformUtilities"
|
|
10
|
+
} from "./manageImports"
|
|
12
11
|
|
|
13
12
|
const j = jscodeshift.withParser("tsx")
|
|
14
13
|
|
|
@@ -176,59 +175,6 @@ describe("componentTransformUtilities", () => {
|
|
|
176
175
|
})
|
|
177
176
|
})
|
|
178
177
|
|
|
179
|
-
describe("transformElementName", () => {
|
|
180
|
-
it("should transform JSX element name", () => {
|
|
181
|
-
const code = `<Button>Click me</Button>`
|
|
182
|
-
const source = j(code)
|
|
183
|
-
const elementPath = source.find(j.JSXOpeningElement).at(0)
|
|
184
|
-
|
|
185
|
-
const result = transformElementName(elementPath.get(), "Link")
|
|
186
|
-
|
|
187
|
-
expect(result).toBe(true)
|
|
188
|
-
expect(source.toSource()).toContain("<Link>Click me</Link>")
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it("should transform both opening and closing tags", () => {
|
|
192
|
-
const code = `<Button className="test">Content</Button>`
|
|
193
|
-
const source = j(code)
|
|
194
|
-
const elementPath = source.find(j.JSXOpeningElement).at(0)
|
|
195
|
-
|
|
196
|
-
const result = transformElementName(elementPath.get(), "Link")
|
|
197
|
-
|
|
198
|
-
expect(result).toBe(true)
|
|
199
|
-
const output = source.toSource()
|
|
200
|
-
expect(output).toContain('<Link className="test">')
|
|
201
|
-
expect(output).toContain("</Link>")
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
it("should handle self-closing tags", () => {
|
|
205
|
-
const code = `<Button />`
|
|
206
|
-
const source = j(code)
|
|
207
|
-
const elementPath = source.find(j.JSXOpeningElement).at(0)
|
|
208
|
-
|
|
209
|
-
const result = transformElementName(elementPath.get(), "Link")
|
|
210
|
-
|
|
211
|
-
expect(result).toBe(true)
|
|
212
|
-
expect(source.toSource()).toContain("<Link />")
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it("should preserve all attributes", () => {
|
|
216
|
-
const code = `<Button className="test" onClick={handler} disabled>Content</Button>`
|
|
217
|
-
const source = j(code)
|
|
218
|
-
const elementPath = source.find(j.JSXOpeningElement).at(0)
|
|
219
|
-
|
|
220
|
-
const result = transformElementName(elementPath.get(), "Link")
|
|
221
|
-
|
|
222
|
-
expect(result).toBe(true)
|
|
223
|
-
const output = source.toSource()
|
|
224
|
-
expect(output).toContain('className="test"')
|
|
225
|
-
expect(output).toContain("onClick={handler}")
|
|
226
|
-
expect(output).toContain("disabled")
|
|
227
|
-
expect(output).toContain("<Link")
|
|
228
|
-
expect(output).toContain("</Link>")
|
|
229
|
-
})
|
|
230
|
-
})
|
|
231
|
-
|
|
232
178
|
describe("addImportToExisting", () => {
|
|
233
179
|
it("should add new import to existing import declaration", () => {
|
|
234
180
|
const code = `import { Button } from "@planningcenter/tapestry-react"`
|