@sanity/ui-codemod 1.0.0-alpha.1

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 (157) hide show
  1. package/bin/run.js +5 -0
  2. package/dist/_chunks-es/addAttribute.js +11 -0
  3. package/dist/_chunks-es/addAttribute.js.map +1 -0
  4. package/dist/_chunks-es/box.mods.js +186 -0
  5. package/dist/_chunks-es/box.mods.js.map +1 -0
  6. package/dist/_chunks-es/flex.mods.js +74 -0
  7. package/dist/_chunks-es/flex.mods.js.map +1 -0
  8. package/dist/_chunks-es/getTransformPath.js +75 -0
  9. package/dist/_chunks-es/getTransformPath.js.map +1 -0
  10. package/dist/_chunks-es/grid.mods.js +110 -0
  11. package/dist/_chunks-es/grid.mods.js.map +1 -0
  12. package/dist/_chunks-es/layout-mods.js +424 -0
  13. package/dist/_chunks-es/layout-mods.js.map +1 -0
  14. package/dist/_chunks-es/transformComponent.js +359 -0
  15. package/dist/_chunks-es/transformComponent.js.map +1 -0
  16. package/dist/commands/latest/box.d.ts +47 -0
  17. package/dist/commands/latest/box.js +17 -0
  18. package/dist/commands/latest/box.js.map +1 -0
  19. package/dist/commands/latest/card.d.ts +47 -0
  20. package/dist/commands/latest/card.js +17 -0
  21. package/dist/commands/latest/card.js.map +1 -0
  22. package/dist/commands/latest/code.d.ts +47 -0
  23. package/dist/commands/latest/code.js +17 -0
  24. package/dist/commands/latest/code.js.map +1 -0
  25. package/dist/commands/latest/container.d.ts +47 -0
  26. package/dist/commands/latest/container.js +17 -0
  27. package/dist/commands/latest/container.js.map +1 -0
  28. package/dist/commands/latest/flex.d.ts +47 -0
  29. package/dist/commands/latest/flex.js +17 -0
  30. package/dist/commands/latest/flex.js.map +1 -0
  31. package/dist/commands/latest/grid.d.ts +47 -0
  32. package/dist/commands/latest/grid.js +17 -0
  33. package/dist/commands/latest/grid.js.map +1 -0
  34. package/dist/commands/latest/heading.d.ts +47 -0
  35. package/dist/commands/latest/heading.js +17 -0
  36. package/dist/commands/latest/heading.js.map +1 -0
  37. package/dist/commands/latest/text.d.ts +47 -0
  38. package/dist/commands/latest/text.js +17 -0
  39. package/dist/commands/latest/text.js.map +1 -0
  40. package/dist/transforms/latest/box/box.d.ts +18 -0
  41. package/dist/transforms/latest/box/box.js +101 -0
  42. package/dist/transforms/latest/box/box.js.map +1 -0
  43. package/dist/transforms/latest/card/card.d.ts +18 -0
  44. package/dist/transforms/latest/card/card.js +618 -0
  45. package/dist/transforms/latest/card/card.js.map +1 -0
  46. package/dist/transforms/latest/code/code.d.ts +18 -0
  47. package/dist/transforms/latest/code/code.js +73 -0
  48. package/dist/transforms/latest/code/code.js.map +1 -0
  49. package/dist/transforms/latest/container/container.d.ts +18 -0
  50. package/dist/transforms/latest/container/container.js +91 -0
  51. package/dist/transforms/latest/container/container.js.map +1 -0
  52. package/dist/transforms/latest/flex/flex.d.ts +18 -0
  53. package/dist/transforms/latest/flex/flex.js +19 -0
  54. package/dist/transforms/latest/flex/flex.js.map +1 -0
  55. package/dist/transforms/latest/grid/grid.d.ts +18 -0
  56. package/dist/transforms/latest/grid/grid.js +19 -0
  57. package/dist/transforms/latest/grid/grid.js.map +1 -0
  58. package/dist/transforms/latest/heading/heading.d.ts +18 -0
  59. package/dist/transforms/latest/heading/heading.js +85 -0
  60. package/dist/transforms/latest/heading/heading.js.map +1 -0
  61. package/dist/transforms/latest/text/text.d.ts +18 -0
  62. package/dist/transforms/latest/text/text.js +88 -0
  63. package/dist/transforms/latest/text/text.js.map +1 -0
  64. package/package.json +68 -0
  65. package/src/baseCommand.ts +74 -0
  66. package/src/commands/latest/box.ts +21 -0
  67. package/src/commands/latest/card.ts +21 -0
  68. package/src/commands/latest/code.ts +21 -0
  69. package/src/commands/latest/container.ts +21 -0
  70. package/src/commands/latest/flex.ts +21 -0
  71. package/src/commands/latest/grid.ts +21 -0
  72. package/src/commands/latest/heading.ts +21 -0
  73. package/src/commands/latest/inline.ts +21 -0
  74. package/src/commands/latest/label.ts +21 -0
  75. package/src/commands/latest/stack.ts +21 -0
  76. package/src/commands/latest/text.ts +21 -0
  77. package/src/constants/latest/layout-mods.ts +422 -0
  78. package/src/transforms/latest/box/box.mods.ts +88 -0
  79. package/src/transforms/latest/box/box.test.ts +204 -0
  80. package/src/transforms/latest/box/box.ts +104 -0
  81. package/src/transforms/latest/card/card.mods.ts +592 -0
  82. package/src/transforms/latest/card/card.test.ts +207 -0
  83. package/src/transforms/latest/card/card.ts +71 -0
  84. package/src/transforms/latest/code/code.mods.ts +57 -0
  85. package/src/transforms/latest/code/code.test.ts +61 -0
  86. package/src/transforms/latest/code/code.ts +53 -0
  87. package/src/transforms/latest/container/container.mods.ts +77 -0
  88. package/src/transforms/latest/container/container.test.ts +105 -0
  89. package/src/transforms/latest/container/container.ts +42 -0
  90. package/src/transforms/latest/flex/flex.mods.ts +73 -0
  91. package/src/transforms/latest/flex/flex.test.ts +80 -0
  92. package/src/transforms/latest/flex/flex.ts +42 -0
  93. package/src/transforms/latest/grid/grid.mods.ts +109 -0
  94. package/src/transforms/latest/grid/grid.test.ts +97 -0
  95. package/src/transforms/latest/grid/grid.ts +42 -0
  96. package/src/transforms/latest/heading/heading.mods.ts +70 -0
  97. package/src/transforms/latest/heading/heading.test.ts +87 -0
  98. package/src/transforms/latest/heading/heading.ts +53 -0
  99. package/src/transforms/latest/inline/inline.mods.ts +65 -0
  100. package/src/transforms/latest/inline/inline.test.ts +57 -0
  101. package/src/transforms/latest/inline/inline.ts +42 -0
  102. package/src/transforms/latest/label/label.mods.ts +72 -0
  103. package/src/transforms/latest/label/label.test.ts +113 -0
  104. package/src/transforms/latest/label/label.ts +67 -0
  105. package/src/transforms/latest/stack/stack.mods.ts +12 -0
  106. package/src/transforms/latest/stack/stack.test.ts +160 -0
  107. package/src/transforms/latest/stack/stack.ts +102 -0
  108. package/src/transforms/latest/text/text.mods.ts +72 -0
  109. package/src/transforms/latest/text/text.test.ts +113 -0
  110. package/src/transforms/latest/text/text.ts +57 -0
  111. package/src/types/AnyExpression.ts +9 -0
  112. package/src/types/AttributeMods.ts +16 -0
  113. package/src/types/BaseOptions.ts +6 -0
  114. package/src/utils/addAttribute.test.ts +43 -0
  115. package/src/utils/addAttribute.ts +26 -0
  116. package/src/utils/addImportSpecifier.test.ts +36 -0
  117. package/src/utils/addImportSpecifier.ts +42 -0
  118. package/src/utils/getAttribute.ts +22 -0
  119. package/src/utils/getAttributeExpression.ts +22 -0
  120. package/src/utils/getAttributeValue.ts +20 -0
  121. package/src/utils/getClonedImportSpecifiers.ts +29 -0
  122. package/src/utils/getComponentJsxNames.ts +17 -0
  123. package/src/utils/getComponentLocalNames.ts +69 -0
  124. package/src/utils/getCompositeAttrValue.ts +39 -0
  125. package/src/utils/getMappingArray.ts +36 -0
  126. package/src/utils/getMappingExpression.ts +22 -0
  127. package/src/utils/getMappingValue.ts +52 -0
  128. package/src/utils/getShorthandAttributes.ts +54 -0
  129. package/src/utils/getSplitImportSpecifiers.ts +40 -0
  130. package/src/utils/getStaticAttributeExpression.ts +35 -0
  131. package/src/utils/getStyleExpression.ts +37 -0
  132. package/src/utils/getStyledComponentAliases.ts +28 -0
  133. package/src/utils/getStyledComponentName.ts +52 -0
  134. package/src/utils/getTransformPath.ts +17 -0
  135. package/src/utils/insertTodoWarning.ts +48 -0
  136. package/src/utils/isImportBindingUsed.ts +53 -0
  137. package/src/utils/isValidStyleType.ts +21 -0
  138. package/src/utils/mergeStyle.ts +64 -0
  139. package/src/utils/removeUnusedImport.test.ts +77 -0
  140. package/src/utils/removeUnusedImport.ts +57 -0
  141. package/src/utils/replaceElement.test.ts +181 -0
  142. package/src/utils/replaceElement.ts +106 -0
  143. package/src/utils/replaceStyledComponent.test.ts +57 -0
  144. package/src/utils/replaceStyledComponent.ts +60 -0
  145. package/src/utils/shouldTransformComponent.test.ts +61 -0
  146. package/src/utils/shouldTransformComponent.ts +40 -0
  147. package/src/utils/testUtils.ts +54 -0
  148. package/src/utils/transformAttributes.test.ts +544 -0
  149. package/src/utils/transformAttributes.ts +248 -0
  150. package/src/utils/transformComponent.test.ts +45 -0
  151. package/src/utils/transformComponent.ts +31 -0
  152. package/src/utils/transformImport.test.ts +175 -0
  153. package/src/utils/transformImport.ts +55 -0
  154. package/src/utils/transformImportAlias.test.ts +40 -0
  155. package/src/utils/transformImportAlias.ts +49 -0
  156. package/src/utils/transformStyledComponents.test.ts +172 -0
  157. package/src/utils/transformStyledComponents.ts +58 -0
@@ -0,0 +1,72 @@
1
+ import type {AttributeMods} from '../../../types/AttributeMods'
2
+
3
+ /** @internal */
4
+ export const TEXT_MODS: AttributeMods = {
5
+ accent: {
6
+ type: 'rename-mapped',
7
+ name: 'tone',
8
+ mapping: {
9
+ true: 'suggest',
10
+ },
11
+ },
12
+ flex: {
13
+ type: 'style-mapped',
14
+ style: 'flex',
15
+ mapping: {
16
+ none: '0 0 auto',
17
+ auto: '1 1 auto',
18
+ initial: '0 1 auto',
19
+ 1: '1',
20
+ 2: '2',
21
+ 3: '3',
22
+ 4: '4',
23
+ 5: '5',
24
+ 6: '6',
25
+ 7: '7',
26
+ 8: '8',
27
+ 9: '9',
28
+ 10: '10',
29
+ 11: '11',
30
+ 12: '12',
31
+ },
32
+ },
33
+ maxWidth: {
34
+ type: 'style-mapped',
35
+ style: 'maxWidth',
36
+ mapping: {
37
+ auto: 'none',
38
+ fill: '100%',
39
+ 0: '20rem',
40
+ 1: '40rem',
41
+ 2: '60rem',
42
+ 3: '80rem',
43
+ 4: '100rem',
44
+ 5: '120rem',
45
+ },
46
+ },
47
+ textOverflow: {
48
+ type: 'rename-mapped',
49
+ name: 'truncate',
50
+ mapping: {
51
+ ellipsis: 1,
52
+ clip: 1,
53
+ },
54
+ },
55
+ width: {
56
+ type: 'style-mapped',
57
+ style: 'width',
58
+ mapping: {
59
+ auto: 'auto',
60
+ fill: '100%',
61
+ stretch: 'stretch',
62
+ min: 'min-content',
63
+ max: 'max-content',
64
+ 0: '20rem',
65
+ 1: '40rem',
66
+ 2: '60rem',
67
+ 3: '80rem',
68
+ 4: '100rem',
69
+ 5: '120rem',
70
+ },
71
+ },
72
+ }
@@ -0,0 +1,113 @@
1
+ import {defineInlineTest} from '../../../utils/testUtils'
2
+ import transform from './text'
3
+
4
+ defineInlineTest(
5
+ transform,
6
+ {fromPackage: '@legacy/ui', toPackage: '@sanity/ui'},
7
+ `
8
+ import {Text} from '@legacy/ui'
9
+
10
+ <Text />
11
+ `,
12
+ `
13
+ import {Text} from "@sanity/ui"
14
+
15
+ <Text as="div" trim={true} />
16
+ `,
17
+ 'updates Text import path based on fromPackage and toPackage',
18
+ )
19
+
20
+ defineInlineTest(
21
+ transform,
22
+ {},
23
+ `
24
+ <Text />
25
+ `,
26
+ `
27
+ <Text as="div" trim={true} />
28
+ `,
29
+ 'adds trim prop',
30
+ )
31
+
32
+ defineInlineTest(
33
+ transform,
34
+ {},
35
+ `
36
+ <Text />
37
+ `,
38
+ `
39
+ <Text as="div" trim={true} />
40
+ `,
41
+ 'adds as prop',
42
+ )
43
+
44
+ defineInlineTest(
45
+ transform,
46
+ {},
47
+ `
48
+ <Text as="span" />
49
+ `,
50
+ `
51
+ <Text as="span" trim={true} />
52
+ `,
53
+ 'does not add as prop if already set',
54
+ )
55
+
56
+ defineInlineTest(
57
+ transform,
58
+ {},
59
+ `
60
+ <Text flex="auto" />
61
+ `,
62
+ `
63
+ <Text
64
+ style={{
65
+ flex: "1 1 auto"
66
+ }}
67
+ as="div"
68
+ trim={true} />
69
+ `,
70
+ 'moves flex prop to style',
71
+ )
72
+
73
+ defineInlineTest(
74
+ transform,
75
+ {},
76
+ `
77
+ <Text accent />
78
+ `,
79
+ `
80
+ <Text tone="suggest" as="div" trim={true} />
81
+ `,
82
+ 'renames accent prop and updates mapped values',
83
+ )
84
+
85
+ defineInlineTest(
86
+ transform,
87
+ {},
88
+ `
89
+ <Text textOverflow="ellipsis" />
90
+ `,
91
+ `
92
+ <Text truncate={1} as="div" trim={true} />
93
+ `,
94
+ 'renames textOverflow prop and updates mapped values',
95
+ )
96
+
97
+ defineInlineTest(
98
+ transform,
99
+ {},
100
+ `
101
+ <Text width={1} maxWidth="fill" />
102
+ `,
103
+ `
104
+ <Text
105
+ style={{
106
+ width: "40rem",
107
+ maxWidth: "100%"
108
+ }}
109
+ as="div"
110
+ trim={true} />
111
+ `,
112
+ 'moves width props to style and updates mapped value',
113
+ )
@@ -0,0 +1,57 @@
1
+ import {type API, type FileInfo} from 'jscodeshift'
2
+
3
+ import type {BaseOptions} from '../../../types/BaseOptions'
4
+ import {addAttribute} from '../../../utils/addAttribute'
5
+ import {getComponentLocalNames} from '../../../utils/getComponentLocalNames'
6
+ import {shouldTransformComponent} from '../../../utils/shouldTransformComponent'
7
+ import {transformAttributes} from '../../../utils/transformAttributes'
8
+ import {transformComponent} from '../../../utils/transformComponent'
9
+ import {transformImport} from '../../../utils/transformImport'
10
+ import {TEXT_MODS} from './text.mods'
11
+
12
+ const TODO_WARNING = 'Please double check the Text migration below'
13
+
14
+ /** @internal */
15
+ export default function transform(
16
+ fileInfo: FileInfo,
17
+ api: API,
18
+ options: BaseOptions,
19
+ ): string | undefined {
20
+ const {fromPackage, toPackage} = options || {}
21
+
22
+ return transformComponent(fileInfo, api, ({j, root, markChanged}) => {
23
+ const localNames = getComponentLocalNames(j, root, 'Text', options)
24
+
25
+ if (!shouldTransformComponent(j, root, 'Text', localNames, options)) {
26
+ return
27
+ }
28
+
29
+ if (transformImport(j, root, 'Text', fromPackage, toPackage)) {
30
+ markChanged()
31
+ }
32
+
33
+ root
34
+ .find(j.JSXOpeningElement, {
35
+ name: {type: 'JSXIdentifier', name: 'Text'},
36
+ })
37
+ .forEach((path) => {
38
+ let changed = false
39
+
40
+ if (transformAttributes(j, path, TEXT_MODS, TODO_WARNING)) {
41
+ changed = true
42
+ }
43
+
44
+ if (addAttribute(j, path.node, 'as', 'div')) {
45
+ changed = true
46
+ }
47
+
48
+ if (addAttribute(j, path.node, 'trim', true)) {
49
+ changed = true
50
+ }
51
+
52
+ if (changed) {
53
+ markChanged()
54
+ }
55
+ })
56
+ })
57
+ }
@@ -0,0 +1,9 @@
1
+ import type {Expression} from 'jscodeshift'
2
+
3
+ export interface AnyExpression extends Expression {
4
+ type: string
5
+ value?: unknown
6
+ elements?: unknown
7
+ quasis?: unknown
8
+ expressions?: unknown
9
+ }
@@ -0,0 +1,16 @@
1
+ export type AttributeMapping = Record<string | number, string | boolean | number>
2
+ export type CompositeAttributeMapping = Record<string, Record<string, string | number | boolean>>
3
+
4
+ export type AttributeMod =
5
+ | {type: 'remove'}
6
+ | {type: 'warn-only'; warning?: string}
7
+ | {type: 'warn-missing'; warning?: string}
8
+ | {type: 'rename-only'; name: string}
9
+ | {type: 'rename-mapped'; name: string; mapping: AttributeMapping}
10
+ | {type: 'style-only'; style: string}
11
+ | {type: 'style-mapped'; style: string; mapping: AttributeMapping}
12
+ | {type: 'mapped-only'; mapping: AttributeMapping}
13
+ | {type: 'shorthand-mapped'; props: {name: string; mapping: AttributeMapping}[]}
14
+ | {type: 'composite'; name: string; mapping: CompositeAttributeMapping}
15
+
16
+ export type AttributeMods = Record<string, AttributeMod>
@@ -0,0 +1,6 @@
1
+ import type {Options} from 'jscodeshift'
2
+
3
+ export interface BaseOptions extends Options {
4
+ fromPackage?: string
5
+ toPackage?: string
6
+ }
@@ -0,0 +1,43 @@
1
+ import {type API, type FileInfo} from 'jscodeshift'
2
+
3
+ import {defineInlineTest} from '../utils/testUtils'
4
+ import {addAttribute} from './addAttribute'
5
+
6
+ function transform(fileInfo: FileInfo, api: API): string {
7
+ const j = api.jscodeshift
8
+ const root = j(fileInfo.source)
9
+
10
+ root
11
+ .find(j.JSXOpeningElement, {
12
+ name: {type: 'JSXIdentifier', name: 'div'},
13
+ })
14
+ .forEach((path) => {
15
+ addAttribute(j, path.node, 'trim', true)
16
+ })
17
+
18
+ return root.toSource()
19
+ }
20
+
21
+ defineInlineTest(
22
+ transform,
23
+ {},
24
+ `
25
+ <div />
26
+ `,
27
+ `
28
+ <div trim={true} />
29
+ `,
30
+ 'adds prop',
31
+ )
32
+
33
+ defineInlineTest(
34
+ transform,
35
+ {},
36
+ `
37
+ <div width="100%" />
38
+ `,
39
+ `
40
+ <div width="100%" trim={true} />
41
+ `,
42
+ 'adds prop and preserves existing props',
43
+ )
@@ -0,0 +1,26 @@
1
+ import type {API, JSXOpeningElement} from 'jscodeshift'
2
+
3
+ import {getAttribute} from './getAttribute'
4
+ import {getAttributeValue} from './getAttributeValue'
5
+
6
+ /**
7
+ * Adds a JSX attribute to the opening element if it doesn't already exist.
8
+ * Returns whether the AST was updated.
9
+ */
10
+ export function addAttribute(
11
+ j: API['jscodeshift'],
12
+ node: JSXOpeningElement,
13
+ name: string,
14
+ value: unknown,
15
+ ): boolean {
16
+ if (getAttribute(node.attributes, name)) {
17
+ return false
18
+ }
19
+
20
+ if (!node.attributes) {
21
+ node.attributes = []
22
+ }
23
+
24
+ node.attributes.push(j.jsxAttribute(j.jsxIdentifier(name), getAttributeValue(j, value)))
25
+ return true
26
+ }
@@ -0,0 +1,36 @@
1
+ import {type API, type FileInfo} from 'jscodeshift'
2
+
3
+ import {addImportSpecifier} from './addImportSpecifier'
4
+ import {defineInlineTest} from './testUtils'
5
+
6
+ function transform(fileInfo: FileInfo, api: API): string {
7
+ const j = api.jscodeshift
8
+ const root = j(fileInfo.source)
9
+
10
+ addImportSpecifier(j, root, 'Card', 'Box')
11
+ return root.toSource()
12
+ }
13
+
14
+ defineInlineTest(
15
+ transform,
16
+ {},
17
+ `
18
+ import {Card} from '@sanity/ui'
19
+ `,
20
+ `
21
+ import { Card, Box } from '@sanity/ui';
22
+ `,
23
+ 'adds import specifier',
24
+ )
25
+
26
+ defineInlineTest(
27
+ transform,
28
+ {},
29
+ `
30
+ import {Text, Card} from '@sanity/ui'
31
+ `,
32
+ `
33
+ import { Text, Card, Box } from '@sanity/ui';
34
+ `,
35
+ 'adds import specifier to import with multiple',
36
+ )
@@ -0,0 +1,42 @@
1
+ import type {API} from 'jscodeshift'
2
+
3
+ /**
4
+ * Adds an import specifier to an existing import declaration.
5
+ * Returns whether the AST was updated.
6
+ */
7
+ export function addImportSpecifier(
8
+ j: API['jscodeshift'],
9
+ root: ReturnType<API['jscodeshift']>,
10
+ addTo: string,
11
+ name: string,
12
+ ): boolean {
13
+ let hasChanges = false
14
+
15
+ root.find(j.ImportDeclaration).forEach((path) => {
16
+ const specs = path.node.specifiers
17
+
18
+ if (!specs?.length) {
19
+ return
20
+ }
21
+
22
+ const hasFromSpecifier = specs.some(
23
+ (s) =>
24
+ s.type === 'ImportSpecifier' &&
25
+ s.imported.type === 'Identifier' &&
26
+ s.imported.name === addTo,
27
+ )
28
+ const hasToSpecifier = specs.some(
29
+ (s) =>
30
+ s.type === 'ImportSpecifier' &&
31
+ s.imported.type === 'Identifier' &&
32
+ s.imported.name === name,
33
+ )
34
+
35
+ if (hasFromSpecifier && !hasToSpecifier) {
36
+ specs.push(j.importSpecifier(j.identifier(name), j.identifier(name)))
37
+ hasChanges = true
38
+ }
39
+ })
40
+
41
+ return hasChanges
42
+ }
@@ -0,0 +1,22 @@
1
+ import type {JSXAttribute, JSXElement} from 'jscodeshift'
2
+
3
+ export function getAttribute(
4
+ attrs: JSXElement['openingElement']['attributes'],
5
+ name: string,
6
+ ): JSXAttribute | undefined {
7
+ if (!attrs) {
8
+ return undefined
9
+ }
10
+
11
+ for (const attr of attrs) {
12
+ if (
13
+ attr.type === 'JSXAttribute' &&
14
+ attr.name.type === 'JSXIdentifier' &&
15
+ attr.name.name === name
16
+ ) {
17
+ return attr
18
+ }
19
+ }
20
+
21
+ return undefined
22
+ }
@@ -0,0 +1,22 @@
1
+ import {type API, type JSXAttribute} from 'jscodeshift'
2
+
3
+ import {type AnyExpression} from '../types/AnyExpression'
4
+
5
+ export function getAttributeExpression(
6
+ attr: JSXAttribute,
7
+ j: API['jscodeshift'],
8
+ ): AnyExpression | null {
9
+ if (!attr.value) {
10
+ return j.booleanLiteral(true)
11
+ }
12
+
13
+ if (attr.value.type === 'StringLiteral' || attr.value.type === 'Literal') {
14
+ return attr.value
15
+ }
16
+
17
+ if (attr.value.type === 'JSXExpressionContainer') {
18
+ return attr.value.expression
19
+ }
20
+
21
+ return null
22
+ }
@@ -0,0 +1,20 @@
1
+ import type {API, JSXAttribute} from 'jscodeshift'
2
+
3
+ export function getAttributeValue(
4
+ j: API['jscodeshift'],
5
+ value: unknown,
6
+ ): NonNullable<JSXAttribute['value']> {
7
+ if (value === true || value === false) {
8
+ return j.jsxExpressionContainer(j.booleanLiteral(value))
9
+ }
10
+
11
+ if (typeof value === 'string') {
12
+ return j.stringLiteral(value)
13
+ }
14
+
15
+ if (typeof value === 'number') {
16
+ return j.jsxExpressionContainer(j.numericLiteral(value))
17
+ }
18
+
19
+ return j.jsxExpressionContainer(j.nullLiteral())
20
+ }
@@ -0,0 +1,29 @@
1
+ import type {API, ImportDefaultSpecifier, ImportSpecifier} from 'jscodeshift'
2
+
3
+ export function getClonedImportSpecifiers(
4
+ j: API['jscodeshift'],
5
+ specifiers: (ImportSpecifier | ImportDefaultSpecifier)[] = [],
6
+ ) {
7
+ return specifiers.map((spec) => {
8
+ if (spec.type === 'ImportSpecifier') {
9
+ if (spec.imported.type !== 'Identifier' || spec.local?.type !== 'Identifier') {
10
+ return spec
11
+ }
12
+
13
+ const clone = j.importSpecifier(
14
+ j.identifier(spec.imported.name),
15
+ j.identifier(spec.local.name),
16
+ )
17
+
18
+ if ('importKind' in spec && spec.importKind === 'type') {
19
+ Object.assign(clone, {importKind: 'type'})
20
+ }
21
+
22
+ return clone
23
+ } else {
24
+ return j.importDefaultSpecifier(
25
+ j.identifier((spec as ImportDefaultSpecifier).local?.name as string),
26
+ )
27
+ }
28
+ })
29
+ }
@@ -0,0 +1,17 @@
1
+ import type {API, Collection} from 'jscodeshift'
2
+
3
+ import type {BaseOptions} from '../types/BaseOptions'
4
+ import {getComponentLocalNames} from './getComponentLocalNames'
5
+ import {getStyledComponentAliases} from './getStyledComponentAliases'
6
+
7
+ export function getComponentJsxNames(
8
+ j: API['jscodeshift'],
9
+ root: Collection,
10
+ componentName: string,
11
+ options?: BaseOptions,
12
+ ): Set<string> {
13
+ const localNames = getComponentLocalNames(j, root, componentName, options)
14
+ const styledAliases = getStyledComponentAliases(j, root, localNames)
15
+
16
+ return new Set([...localNames, ...styledAliases])
17
+ }
@@ -0,0 +1,69 @@
1
+ import type {API, Collection} from 'jscodeshift'
2
+
3
+ import type {BaseOptions} from '../types/BaseOptions'
4
+ import {DEFAULT_UI_PACKAGE} from './transformImport'
5
+
6
+ export function getComponentLocalNames(
7
+ j: API['jscodeshift'],
8
+ root: Collection,
9
+ componentName: string,
10
+ options?: BaseOptions,
11
+ ): Set<string> {
12
+ const importedNames = new Set<string>()
13
+ const packages = new Set([
14
+ options?.fromPackage ?? DEFAULT_UI_PACKAGE,
15
+ options?.toPackage ?? DEFAULT_UI_PACKAGE,
16
+ ])
17
+ let hasOtherPackageImport = false
18
+
19
+ root.find(j.ImportDeclaration).forEach((path) => {
20
+ const node = path.node
21
+
22
+ if ('importKind' in node && node.importKind === 'type') {
23
+ return
24
+ }
25
+
26
+ const matchesPackage = packages.has(node.source.value as string)
27
+
28
+ for (const spec of node.specifiers ?? []) {
29
+ if (spec.type !== 'ImportSpecifier') {
30
+ continue
31
+ }
32
+
33
+ if ('importKind' in spec && spec.importKind === 'type') {
34
+ continue
35
+ }
36
+
37
+ if (spec.imported.type !== 'Identifier' || spec.imported.name !== componentName) {
38
+ continue
39
+ }
40
+
41
+ const local = spec.local?.type === 'Identifier' ? spec.local.name : spec.imported.name
42
+
43
+ if (!matchesPackage) {
44
+ hasOtherPackageImport = true
45
+ continue
46
+ }
47
+
48
+ importedNames.add(local)
49
+ }
50
+ })
51
+
52
+ if (importedNames.size > 0) {
53
+ return importedNames
54
+ }
55
+
56
+ if (hasOtherPackageImport) {
57
+ return new Set()
58
+ }
59
+
60
+ const usesComponentInJsx = root.find(j.JSXOpeningElement).some(({node}) => {
61
+ return node.name.type === 'JSXIdentifier' && node.name.name === componentName
62
+ })
63
+
64
+ if (usesComponentInJsx) {
65
+ return new Set([componentName])
66
+ }
67
+
68
+ return new Set()
69
+ }
@@ -0,0 +1,39 @@
1
+ import type {API, JSXAttribute, JSXSpreadAttribute} from 'jscodeshift'
2
+
3
+ import type {CompositeAttributeMapping} from '../types/AttributeMods'
4
+ import {getStaticAttributeExpression} from './getStaticAttributeExpression'
5
+
6
+ export function getCompositeAttrValue(
7
+ j: API['jscodeshift'],
8
+ attrs: (JSXAttribute | JSXSpreadAttribute)[],
9
+ mapping: CompositeAttributeMapping,
10
+ ) {
11
+ let compositeAttrValue: string | null = null
12
+
13
+ for (const compositeKey of Object.keys(mapping)) {
14
+ const compositeMapping = mapping[compositeKey]
15
+
16
+ if (!compositeMapping) {
17
+ continue
18
+ }
19
+
20
+ let hasMatchingValues = true
21
+
22
+ for (const propName of Object.keys(compositeMapping)) {
23
+ const mappingValue = compositeMapping[propName]
24
+ const attrValue = getStaticAttributeExpression(j, attrs, propName)
25
+
26
+ if (mappingValue === undefined || attrValue !== mappingValue) {
27
+ hasMatchingValues = false
28
+ break
29
+ }
30
+ }
31
+
32
+ if (hasMatchingValues) {
33
+ compositeAttrValue = compositeKey
34
+ break
35
+ }
36
+ }
37
+
38
+ return compositeAttrValue
39
+ }
@@ -0,0 +1,36 @@
1
+ import {type API, type ArrayExpression} from 'jscodeshift'
2
+
3
+ import type {AnyExpression} from '../types/AnyExpression'
4
+ import type {AttributeMapping} from '../types/AttributeMods'
5
+ import {getMappingExpression} from './getMappingExpression'
6
+ import {getMappingValue} from './getMappingValue'
7
+
8
+ export function getMappingArray(
9
+ j: API['jscodeshift'],
10
+ arr: AnyExpression,
11
+ mapping: AttributeMapping,
12
+ allowUndefined?: boolean,
13
+ ): ArrayExpression | null {
14
+ if (arr.type !== 'ArrayExpression') {
15
+ return null
16
+ }
17
+
18
+ const elements = arr.elements as (AnyExpression | null)[]
19
+ const output: AnyExpression[] = []
20
+
21
+ for (const el of elements) {
22
+ if (el == null || el.type === 'SpreadElement') {
23
+ return null
24
+ }
25
+
26
+ const mappingValue = getMappingValue(mapping, el)
27
+
28
+ if (!mappingValue && !allowUndefined) {
29
+ return null
30
+ }
31
+
32
+ output.push(getMappingExpression(j, mappingValue))
33
+ }
34
+
35
+ return j.arrayExpression(output as never[])
36
+ }