@planningcenter/tapestry-migration-cli 2.4.0-rc.1 → 2.4.0-rc.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/package.json +2 -2
  2. package/src/components/button/transforms/childrenToLabel.test.ts +5 -4
  3. package/src/components/button/transforms/childrenToLabel.ts +9 -39
  4. package/src/components/button/transforms/unsupportedProps.ts +7 -31
  5. package/src/components/link/index.ts +16 -2
  6. package/src/components/link/transforms/childrenToLabel.test.ts +331 -0
  7. package/src/components/link/transforms/childrenToLabel.ts +54 -0
  8. package/src/components/link/transforms/convertStyleProps.test.ts +391 -0
  9. package/src/components/link/transforms/convertStyleProps.ts +10 -0
  10. package/src/components/link/transforms/{inlineToKind.test.ts → inlineMemberToKind.test.ts} +2 -2
  11. package/src/components/link/transforms/{inlineToKind.ts → inlineMemberToKind.ts} +0 -2
  12. package/src/components/link/transforms/inlinePropToKind.test.ts +312 -0
  13. package/src/components/link/transforms/inlinePropToKind.ts +24 -0
  14. package/src/components/link/transforms/moveLinkImport.test.ts +295 -0
  15. package/src/components/link/transforms/moveLinkImport.ts +14 -0
  16. package/src/components/link/transforms/removeAs.test.ts +192 -0
  17. package/src/components/link/transforms/removeAs.ts +17 -0
  18. package/src/components/link/transforms/reviewStyles.test.ts +172 -0
  19. package/src/components/link/transforms/reviewStyles.ts +17 -0
  20. package/src/components/link/transforms/targetBlankToExternal.test.ts +14 -0
  21. package/src/components/link/transforms/unsupportedProps.test.ts +265 -0
  22. package/src/components/link/transforms/unsupportedProps.ts +58 -0
  23. package/src/components/shared/conditions/hasAttributeValue.test.ts +22 -1
  24. package/src/components/shared/conditions/hasAttributeValue.ts +24 -6
  25. package/src/components/shared/helpers/childrenToLabelHelpers.ts +43 -0
  26. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +35 -0
  27. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +5 -1
  28. package/src/stubs/textPlugin.ts +45 -0
@@ -0,0 +1,58 @@
1
+ import { JSXAttribute, Transform } from "jscodeshift"
2
+
3
+ import { addCommentToUnsupportedProps } from "../../shared/actions/addCommentToUnsupportedProps"
4
+ import { SUPPORTED_PROPS_BASE } from "../../shared/helpers/unsupportedPropsHelpers"
5
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
6
+
7
+ // Note: 'target' and 'rel' are NOT included because they are handled by targetBlankToExternal transform
8
+ const LINK_SPECIFIC_PROPS = [
9
+ "download",
10
+ "external",
11
+ "href",
12
+ "hrefLang",
13
+ "media",
14
+ "ping",
15
+ "referrerPolicy",
16
+ "type",
17
+ ]
18
+
19
+ const SUPPORTED_PROPS = [...SUPPORTED_PROPS_BASE, ...LINK_SPECIFIC_PROPS]
20
+
21
+ const transform: Transform = attributeTransformFactory({
22
+ targetComponent: "Link",
23
+ targetPackage: "@planningcenter/tapestry-react",
24
+ transform: (element, { j }) => {
25
+ const UNSUPPORTED_PROPS = (element.openingElement.attributes || [])
26
+ .filter(
27
+ (attr) =>
28
+ attr.type === "JSXAttribute" &&
29
+ !SUPPORTED_PROPS.includes(attr.name.name as string) &&
30
+ !(attr.name.name as string).startsWith("aria-") &&
31
+ !(attr.name.name as string).startsWith("data-")
32
+ )
33
+ .map((attr) => (attr as JSXAttribute).name.name as string)
34
+
35
+ return addCommentToUnsupportedProps({
36
+ element,
37
+ j,
38
+ messageSuffix: (prop) => {
39
+ if (prop === "css") {
40
+ return "\n * Use 'className' prop with CSS classes instead of the css prop.\n"
41
+ }
42
+ if (prop === "disabled") {
43
+ return "\n * Links do not support the disabled prop. Consider using a button, hiding the link, or using CSS to style it as disabled.\n"
44
+ }
45
+ if (prop === "mediaQueries") {
46
+ return "\n * It is recommended to use CSS media queries in a class that you apply to the component.\n"
47
+ }
48
+ if (prop === "hover" || prop === "focus" || prop === "active") {
49
+ return "\n * State-based styles (hover, focus, active) should be handled with CSS class selectors.\n"
50
+ }
51
+ return ""
52
+ },
53
+ props: UNSUPPORTED_PROPS,
54
+ })
55
+ },
56
+ })
57
+
58
+ export default transform
@@ -32,7 +32,28 @@ describe("hasAttributeValue", () => {
32
32
  expect(condition(element)).toBe(false)
33
33
  })
34
34
 
35
- it("should return false for expression values", () => {
35
+ it("should return true for expression with string literal", () => {
36
+ const condition = hasAttributeValue("target", "_blank")
37
+ const element = createJSXElement(" target={'_blank'}")
38
+
39
+ expect(condition(element)).toBe(true)
40
+ })
41
+
42
+ it("should return true for expression with double quotes", () => {
43
+ const condition = hasAttributeValue("target", "_blank")
44
+ const element = createJSXElement(' target={"_blank"}')
45
+
46
+ expect(condition(element)).toBe(true)
47
+ })
48
+
49
+ it("should return false for expression with different value", () => {
50
+ const condition = hasAttributeValue("target", "_blank")
51
+ const element = createJSXElement(" target={'_self'}")
52
+
53
+ expect(condition(element)).toBe(false)
54
+ })
55
+
56
+ it("should return false for complex expression values", () => {
36
57
  const condition = hasAttributeValue("onClick", "handleClick")
37
58
  const element = createJSXElement(" onClick={handleClick}")
38
59
 
@@ -4,6 +4,7 @@ import { TransformCondition } from "../types"
4
4
 
5
5
  /**
6
6
  * Helper function to create a condition that checks for an attribute with a specific value
7
+ * Handles both string literals and expressions with string literals
7
8
  */
8
9
  export function hasAttributeValue(
9
10
  attributeName: string,
@@ -11,13 +12,30 @@ export function hasAttributeValue(
11
12
  ): TransformCondition {
12
13
  return (element: JSXElement) => {
13
14
  const attributes = element.openingElement.attributes || []
14
- return attributes.some(
15
- (attr) =>
15
+ return attributes.some((attr) => {
16
+ const hasAttribute =
16
17
  attr.type === "JSXAttribute" &&
17
18
  attr.name?.type === "JSXIdentifier" &&
18
- attr.name.name === attributeName &&
19
- attr.value?.type === "StringLiteral" &&
20
- attr.value.value === value
21
- )
19
+ attr.name.name === attributeName
20
+
21
+ if (!hasAttribute) {
22
+ return false
23
+ }
24
+
25
+ // Handle string literal: attribute="value"
26
+ if (attr.value?.type === "StringLiteral") {
27
+ return attr.value.value === value
28
+ }
29
+
30
+ // Handle expression: attribute={"value"} or attribute={'value'}
31
+ if (attr.value?.type === "JSXExpressionContainer") {
32
+ const { expression } = attr.value
33
+ if (expression.type === "StringLiteral") {
34
+ return expression.value === value
35
+ }
36
+ }
37
+
38
+ return false
39
+ })
22
40
  }
23
41
  }
@@ -0,0 +1,43 @@
1
+ import { JSXElement } from "jscodeshift"
2
+
3
+ export function extractTextContent(
4
+ children: NonNullable<JSXElement["children"]>
5
+ ): {
6
+ isSimpleText: boolean
7
+ textContent: string
8
+ } {
9
+ let textContent = ""
10
+
11
+ for (const child of children) {
12
+ if (child.type === "JSXText") {
13
+ const text = child.value.trim()
14
+ if (text) textContent += text
15
+ } else if (
16
+ child.type === "JSXExpressionContainer" &&
17
+ child.expression.type === "StringLiteral"
18
+ ) {
19
+ textContent += child.expression.value
20
+ } else if (
21
+ child.type === "JSXExpressionContainer" &&
22
+ child.expression.type === "TemplateLiteral" &&
23
+ child.expression.expressions.length === 0
24
+ ) {
25
+ textContent += child.expression.quasis[0].value.raw
26
+ } else {
27
+ return { isSimpleText: false, textContent: "" }
28
+ }
29
+ }
30
+
31
+ return { isSimpleText: true, textContent }
32
+ }
33
+
34
+ export function buildComment(
35
+ message: string,
36
+ includeIconGuidance: boolean
37
+ ): string {
38
+ const baseMessage = `${message} - take time to find the right text for the component.`
39
+ if (includeIconGuidance) {
40
+ return `${baseMessage} If icons are used in the component, you can use prefix and suffix to correctly display those icons.`
41
+ }
42
+ return baseMessage
43
+ }
@@ -0,0 +1,35 @@
1
+ import { stylePropNames } from "../../../../dist/tapestry-react-shim.cjs"
2
+
3
+ export const STYLE_PROP_NAMES_WITHOUT_CSS = stylePropNames.filter(
4
+ (prop: string) => prop !== "css"
5
+ )
6
+
7
+ export const COMMON_PROPS = [
8
+ "className",
9
+ "id",
10
+ "key",
11
+ "kind",
12
+ "label",
13
+ "onBlur",
14
+ "onFocus",
15
+ "onClick",
16
+ "onKeyDown",
17
+ "onKeyUp",
18
+ "onMouseDown",
19
+ "onMouseOut",
20
+ "onMouseOver",
21
+ "onMouseUp",
22
+ "prefix",
23
+ "ref",
24
+ "role",
25
+ "size",
26
+ "style",
27
+ "suffix",
28
+ "tabIndex",
29
+ "title",
30
+ ]
31
+
32
+ export const SUPPORTED_PROPS_BASE = [
33
+ ...STYLE_PROP_NAMES_WITHOUT_CSS,
34
+ ...COMMON_PROPS,
35
+ ]
@@ -251,6 +251,7 @@ export function stylePropTransformFactory(config: {
251
251
  const name = attr.name?.name as string
252
252
  return (
253
253
  name &&
254
+ name !== "css" &&
254
255
  (stylePropNames.includes(name) ||
255
256
  name in stylePropMapping ||
256
257
  stylesToKeep.includes(name) ||
@@ -319,7 +320,10 @@ export function stylePropTransformFactory(config: {
319
320
  styles = { ...styles, ...directStyleProps }
320
321
  if (options.verbose) console.log("Final generated styles:", styles)
321
322
 
322
- applyStylesToComponent({ element, j, styles })
323
+ // Only apply styles if there are actual CSS properties to add
324
+ if (Object.keys(styles).length > 0) {
325
+ applyStylesToComponent({ element, j, styles })
326
+ }
323
327
  } catch (error) {
324
328
  console.log("Error processing style props:", error)
325
329
  console.log("Style props that caused error:", allStyleProps)
@@ -0,0 +1,45 @@
1
+ // copied from packages/tapestry-react/src/Text/Text.tsx in tapestry-react
2
+ // size prop is not included since it is a supported prop in tapestry link
3
+
4
+ export const textPlugin = {
5
+ getStyles({
6
+ align,
7
+ italic,
8
+ truncate,
9
+ underline,
10
+ weight,
11
+ wrap = true,
12
+ ...styles
13
+ }: Record<string, unknown>) {
14
+ if (align) {
15
+ styles.textAlign = align
16
+ }
17
+
18
+ if (italic) {
19
+ styles.fontStyle = "italic"
20
+ }
21
+
22
+ if (truncate) {
23
+ if (styles.display !== "block" && styles.display !== "flex") {
24
+ styles.display = "block"
25
+ }
26
+ styles.overflow = "hidden"
27
+ styles.textOverflow = "ellipsis"
28
+ }
29
+
30
+ if (truncate || wrap === false) {
31
+ styles.whiteSpace = "nowrap"
32
+ }
33
+
34
+ if (underline) {
35
+ styles.textDecoration = underline
36
+ }
37
+
38
+ if (weight) {
39
+ styles.fontWeight = weight
40
+ }
41
+
42
+ return styles
43
+ },
44
+ styleProps: ["align", "italic", "truncate", "underline", "weight", "wrap"],
45
+ }