@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,248 @@
1
+ import {type API, type ASTPath, type JSXOpeningElement} from 'jscodeshift'
2
+
3
+ import {type AttributeMods} from '../types/AttributeMods'
4
+ import {getAttribute} from './getAttribute'
5
+ import {getAttributeExpression} from './getAttributeExpression'
6
+ import {getCompositeAttrValue} from './getCompositeAttrValue'
7
+ import {getMappingArray} from './getMappingArray'
8
+ import {getMappingExpression} from './getMappingExpression'
9
+ import {getMappingValue} from './getMappingValue'
10
+ import {getShorthandAttributes} from './getShorthandAttributes'
11
+ import {getStyleExpression} from './getStyleExpression'
12
+ import {insertTodoWarning} from './insertTodoWarning'
13
+ import {isValidStyleType} from './isValidStyleType'
14
+ import {mergeStyle} from './mergeStyle'
15
+
16
+ /**
17
+ * Applies attribute migration rules to a JSX opening element.
18
+ * Returns whether the AST was updated.
19
+ */
20
+ export function transformAttributes(
21
+ j: API['jscodeshift'],
22
+ path: ASTPath<JSXOpeningElement>,
23
+ mods: AttributeMods,
24
+ todoWarning: string,
25
+ ): boolean {
26
+ if (!path.node.attributes) {
27
+ return false
28
+ }
29
+
30
+ let hasChanges = false
31
+ const attrs = path.node.attributes
32
+ const removeIdxs: number[] = []
33
+
34
+ for (const [attrName, mod] of Object.entries(mods)) {
35
+ if (mod?.type !== 'warn-missing') {
36
+ continue
37
+ }
38
+
39
+ if (getAttribute(attrs, attrName)) {
40
+ continue
41
+ }
42
+
43
+ if (insertTodoWarning(j, path, mod.warning || todoWarning)) {
44
+ hasChanges = true
45
+ }
46
+ }
47
+
48
+ for (let i = 0; i < attrs.length; i++) {
49
+ const attr = attrs[i]
50
+
51
+ if (!attr || attr.type !== 'JSXAttribute' || attr.name.type !== 'JSXIdentifier') {
52
+ continue
53
+ }
54
+
55
+ const mod = mods[attr.name.name]
56
+ const expr = getAttributeExpression(attr, j)
57
+
58
+ if (!mod || !expr) {
59
+ continue
60
+ }
61
+
62
+ if (mod.type === 'remove') {
63
+ removeIdxs.push(i)
64
+ }
65
+
66
+ if (mod.type === 'warn-only') {
67
+ if (insertTodoWarning(j, path, mod.warning || todoWarning)) {
68
+ hasChanges = true
69
+ }
70
+ }
71
+
72
+ if (mod.type === 'rename-only') {
73
+ if (attr.name.type === 'JSXIdentifier') {
74
+ attr.name.name = mod.name
75
+ hasChanges = true
76
+ }
77
+ }
78
+
79
+ if (mod.type === 'style-only') {
80
+ if (!expr || !isValidStyleType(expr)) {
81
+ if (insertTodoWarning(j, path, todoWarning)) {
82
+ hasChanges = true
83
+ }
84
+
85
+ continue
86
+ }
87
+
88
+ const merged = mergeStyle(j, attrs, mod.style, getStyleExpression(j, expr))
89
+
90
+ if (merged) {
91
+ removeIdxs.push(i)
92
+ } else if (insertTodoWarning(j, path, todoWarning)) {
93
+ hasChanges = true
94
+ }
95
+ }
96
+
97
+ if (mod.type === 'style-mapped') {
98
+ if (!expr || !isValidStyleType(expr)) {
99
+ if (insertTodoWarning(j, path, todoWarning)) {
100
+ hasChanges = true
101
+ }
102
+
103
+ continue
104
+ }
105
+
106
+ const styleValue = getMappingValue(mod.mapping, expr)
107
+
108
+ if (styleValue === undefined) {
109
+ if (insertTodoWarning(j, path, todoWarning)) {
110
+ hasChanges = true
111
+ }
112
+
113
+ continue
114
+ }
115
+
116
+ const merged = mergeStyle(j, attrs, mod.style, getMappingExpression(j, styleValue))
117
+
118
+ if (merged) {
119
+ removeIdxs.push(i)
120
+ } else if (insertTodoWarning(j, path, todoWarning)) {
121
+ hasChanges = true
122
+ }
123
+ }
124
+
125
+ if (mod.type === 'shorthand-mapped') {
126
+ if (expr.type === 'ConditionalExpression' || expr.type === 'Identifier') {
127
+ if (insertTodoWarning(j, path, todoWarning)) {
128
+ hasChanges = true
129
+ }
130
+
131
+ continue
132
+ }
133
+
134
+ const shorthandAttrs = getShorthandAttributes(j, expr, mod)
135
+
136
+ if (shorthandAttrs.length) {
137
+ attrs.splice(i + 1, 0, ...shorthandAttrs)
138
+ removeIdxs.push(i)
139
+ } else if (insertTodoWarning(j, path, todoWarning)) {
140
+ hasChanges = true
141
+ }
142
+ }
143
+
144
+ if (mod.type === 'composite') {
145
+ const triggerName = attr.name.name
146
+ const compositeValue = getCompositeAttrValue(j, attrs, mod.mapping)
147
+
148
+ if (!compositeValue) {
149
+ if (insertTodoWarning(j, path, todoWarning)) {
150
+ hasChanges = true
151
+ }
152
+
153
+ continue
154
+ }
155
+
156
+ if (attr.name.type === 'JSXIdentifier') {
157
+ attr.name.name = mod.name
158
+ }
159
+
160
+ attr.value = j.stringLiteral(compositeValue)
161
+
162
+ for (const attrName of Object.keys(mod.mapping[compositeValue] || {})) {
163
+ if (attrName === triggerName) {
164
+ continue
165
+ }
166
+
167
+ const otherAttr = getAttribute(attrs, attrName)
168
+
169
+ if (otherAttr) {
170
+ const k = attrs.indexOf(otherAttr)
171
+
172
+ if (k !== -1) {
173
+ removeIdxs.push(k)
174
+ }
175
+ }
176
+ }
177
+
178
+ hasChanges = true
179
+ }
180
+
181
+ if (mod.type === 'mapped-only' || mod.type === 'rename-mapped') {
182
+ if (mod.type === 'rename-mapped') {
183
+ if (attr.name.type === 'JSXIdentifier') {
184
+ attr.name.name = mod.name
185
+ }
186
+ }
187
+
188
+ if (expr.type === 'ConditionalExpression' || expr.type === 'Identifier') {
189
+ if (insertTodoWarning(j, path, todoWarning)) {
190
+ hasChanges = true
191
+ }
192
+
193
+ continue
194
+ }
195
+
196
+ if (expr.type === 'ArrayExpression') {
197
+ const styleArray = getMappingArray(j, expr, mod.mapping)
198
+
199
+ if (!styleArray) {
200
+ if (insertTodoWarning(j, path, todoWarning)) {
201
+ hasChanges = true
202
+ }
203
+
204
+ continue
205
+ }
206
+
207
+ if (attr.value?.type === 'JSXExpressionContainer') {
208
+ attr.value.expression = styleArray
209
+ } else {
210
+ attr.value = j.jsxExpressionContainer(styleArray)
211
+ }
212
+
213
+ hasChanges = true
214
+ continue
215
+ }
216
+
217
+ const styleValue = getMappingValue(mod.mapping, expr)
218
+
219
+ if (styleValue === undefined) {
220
+ if (insertTodoWarning(j, path, todoWarning)) {
221
+ hasChanges = true
222
+ }
223
+
224
+ continue
225
+ }
226
+
227
+ if (typeof styleValue === 'string') {
228
+ attr.value = j.stringLiteral(styleValue)
229
+ } else {
230
+ attr.value = j.jsxExpressionContainer(getMappingExpression(j, styleValue) as never)
231
+ }
232
+
233
+ hasChanges = true
234
+ }
235
+ }
236
+
237
+ const idxsToRemove = [...new Set(removeIdxs)].sort((a, b) => b - a)
238
+
239
+ if (idxsToRemove.length > 0) {
240
+ hasChanges = true
241
+
242
+ for (const i of idxsToRemove) {
243
+ attrs.splice(i, 1)
244
+ }
245
+ }
246
+
247
+ return hasChanges
248
+ }
@@ -0,0 +1,45 @@
1
+ import jscodeshift from 'jscodeshift'
2
+ import {describe, expect, it} from 'vitest'
3
+
4
+ import {replaceElement} from './replaceElement'
5
+ import {transformComponent} from './transformComponent'
6
+
7
+ describe('transformComponent', () => {
8
+ const api = {j: jscodeshift, jscodeshift, stats: () => {}, report: () => {}}
9
+
10
+ it('returns undefined when the transform makes no changes', () => {
11
+ const result = transformComponent(
12
+ {source: 'export const value = 1\n', path: 'example.ts'},
13
+ api,
14
+ () => {},
15
+ )
16
+
17
+ expect(result).toBeUndefined()
18
+ })
19
+
20
+ it('returns transformed code when a helper marks changes', () => {
21
+ const result = transformComponent(
22
+ {source: '<Card padding={1} />', path: 'example.tsx'},
23
+ api,
24
+ ({j, root, markChanged}) => {
25
+ if (
26
+ replaceElement(
27
+ j,
28
+ root,
29
+ () => true,
30
+ {
31
+ element: 'Card',
32
+ },
33
+ {
34
+ element: 'Box',
35
+ },
36
+ )
37
+ ) {
38
+ markChanged()
39
+ }
40
+ },
41
+ )
42
+
43
+ expect(result).toContain('<Box padding={1} />')
44
+ })
45
+ })
@@ -0,0 +1,31 @@
1
+ import type {API, Collection, FileInfo} from 'jscodeshift'
2
+
3
+ export type TransformContext = {
4
+ j: API['jscodeshift']
5
+ root: Collection
6
+ markChanged: () => void
7
+ }
8
+
9
+ /**
10
+ * Runs a component transform on a file and only calls `toSource()` when
11
+ * `markChanged()` was invoked. Returns `undefined` for no-op files.
12
+ */
13
+ export function transformComponent(
14
+ fileInfo: FileInfo,
15
+ api: API,
16
+ fn: (ctx: TransformContext) => void,
17
+ ): string | undefined {
18
+ const j = api.jscodeshift
19
+ const root = j(fileInfo.source)
20
+ let hasChanges = false
21
+
22
+ fn({
23
+ j,
24
+ root,
25
+ markChanged: () => {
26
+ hasChanges = true
27
+ },
28
+ })
29
+
30
+ return hasChanges ? root.toSource() : undefined
31
+ }
@@ -0,0 +1,175 @@
1
+ import {type API, type FileInfo} from 'jscodeshift'
2
+
3
+ import type {BaseOptions} from '../types/BaseOptions'
4
+ import {defineInlineTest} from '../utils/testUtils'
5
+ import {transformImport} from './transformImport'
6
+
7
+ function transform(fileInfo: FileInfo, api: API, options: BaseOptions): string {
8
+ const j = api.jscodeshift
9
+ const root = j(fileInfo.source)
10
+
11
+ transformImport(j, root, 'Box', options.fromPackage, options.toPackage)
12
+ return root.toSource()
13
+ }
14
+
15
+ defineInlineTest(
16
+ transform,
17
+ {},
18
+ `
19
+ import {Box} from '@sanity/ui'
20
+
21
+ <Box />
22
+ `,
23
+ `
24
+ import {Box} from '@sanity/ui'
25
+
26
+ <Box />
27
+ `,
28
+ 'does not update import path if fromPackage and toPackage are not set',
29
+ )
30
+
31
+ defineInlineTest(
32
+ transform,
33
+ {fromPackage: '@legacy/ui'},
34
+ `
35
+ import {Box} from '@legacy/ui'
36
+
37
+ <Box />
38
+ `,
39
+ `
40
+ import {Box} from "@sanity/ui"
41
+
42
+ <Box />
43
+ `,
44
+ 'updates import path if only fromPackage is set',
45
+ )
46
+
47
+ defineInlineTest(
48
+ transform,
49
+ {toPackage: '@latest/ui'},
50
+ `
51
+ import {Box} from '@sanity/ui'
52
+
53
+ <Box />
54
+ `,
55
+ `
56
+ import {Box} from "@latest/ui"
57
+
58
+ <Box />
59
+ `,
60
+ 'updates import path if only toPackage is set',
61
+ )
62
+
63
+ defineInlineTest(
64
+ transform,
65
+ {fromPackage: '@legacy/ui', toPackage: '@legacy/ui'},
66
+ `
67
+ import {Box} from '@legacy/ui'
68
+
69
+ <Box />
70
+ `,
71
+ `
72
+ import {Box} from '@legacy/ui'
73
+
74
+ <Box />
75
+ `,
76
+ 'does not update import path if fromPackage and toPackage are the same',
77
+ )
78
+
79
+ defineInlineTest(
80
+ transform,
81
+ {fromPackage: '@legacy/ui', toPackage: '@sanity/ui'},
82
+ `
83
+ import {Box} from '@legacy/ui'
84
+ import {Card} from '@legacy/ui'
85
+
86
+ <>
87
+ <Box />
88
+ <Card />
89
+ </>
90
+ `,
91
+ `
92
+ import {Box} from "@sanity/ui"
93
+ import {Card} from '@legacy/ui'
94
+
95
+ <>
96
+ <Box />
97
+ <Card />
98
+ </>
99
+ `,
100
+ 'only updates import path for current component',
101
+ )
102
+
103
+ defineInlineTest(
104
+ transform,
105
+ {fromPackage: '@legacy/ui', toPackage: '@sanity/ui'},
106
+ `
107
+ import {Box, Card} from '@legacy/ui'
108
+
109
+ <>
110
+ <Box />
111
+ <Card />
112
+ </>
113
+ `,
114
+ `
115
+ import { Card } from '@legacy/ui';
116
+
117
+ import { Box } from "@sanity/ui";
118
+
119
+ <>
120
+ <Box />
121
+ <Card />
122
+ </>
123
+ `,
124
+ 'splits mixed imports and only updates import path for current component',
125
+ )
126
+
127
+ defineInlineTest(
128
+ transform,
129
+ {fromPackage: '@legacy/ui', toPackage: '@sanity/ui'},
130
+ `
131
+ import {Box, BoxProps} from '@legacy/ui'
132
+
133
+ <Box />
134
+ `,
135
+ `
136
+ import {Box, BoxProps} from "@sanity/ui"
137
+
138
+ <Box />
139
+ `,
140
+ 'updates import path for type import',
141
+ )
142
+
143
+ defineInlineTest(
144
+ transform,
145
+ {fromPackage: '@legacy/ui', toPackage: '@sanity/ui'},
146
+ `
147
+ import {Box, type BoxProps} from '@legacy/ui'
148
+
149
+ <Box />
150
+ `,
151
+ `
152
+ import {Box, type BoxProps} from "@sanity/ui"
153
+
154
+ <Box />
155
+ `,
156
+ 'updates import path for with type keyword inside',
157
+ )
158
+
159
+ defineInlineTest(
160
+ transform,
161
+ {fromPackage: '@legacy/ui', toPackage: '@sanity/ui'},
162
+ `
163
+ import {Box} from '@legacy/ui'
164
+ import type {BoxProps} from '@legacy/ui'
165
+
166
+ <Box />
167
+ `,
168
+ `
169
+ import {Box} from "@sanity/ui"
170
+ import type {BoxProps} from "@sanity/ui"
171
+
172
+ <Box />
173
+ `,
174
+ 'updates import path for with type keyword outside',
175
+ )
@@ -0,0 +1,55 @@
1
+ import type {API, Collection} from 'jscodeshift'
2
+
3
+ import {getClonedImportSpecifiers} from './getClonedImportSpecifiers'
4
+ import {getSplitImportSpecifiers} from './getSplitImportSpecifiers'
5
+
6
+ export const DEFAULT_UI_PACKAGE = '@sanity/ui'
7
+
8
+ /**
9
+ * Rewrites imports from `fromPackage` to `toPackage`, splitting imports if needed.
10
+ * Returns whether the AST was updated.
11
+ */
12
+ export function transformImport(
13
+ j: API['jscodeshift'],
14
+ root: Collection,
15
+ componentName: string,
16
+ fromPackage: string = DEFAULT_UI_PACKAGE,
17
+ toPackage: string = DEFAULT_UI_PACKAGE,
18
+ ): boolean {
19
+ if (fromPackage === toPackage) {
20
+ return false
21
+ }
22
+
23
+ let hasChanges = false
24
+
25
+ root.find(j.ImportDeclaration).forEach((path) => {
26
+ const node = path.node
27
+
28
+ if (node.source.value !== fromPackage) {
29
+ return
30
+ }
31
+
32
+ const {componentSpecs, restSpecs} = getSplitImportSpecifiers(node.specifiers, componentName)
33
+
34
+ if (componentSpecs.length === 0) {
35
+ return
36
+ }
37
+
38
+ if (restSpecs.length === 0) {
39
+ if (node.source && typeof node.source === 'object' && 'value' in node.source) {
40
+ const sourceLiteral = node.source as {value: string}
41
+ sourceLiteral.value = toPackage
42
+ }
43
+
44
+ hasChanges = true
45
+ return
46
+ }
47
+
48
+ path.node.specifiers = restSpecs
49
+ const clonedImportSpecifiers = getClonedImportSpecifiers(j, componentSpecs)
50
+ path.insertAfter(j.importDeclaration(clonedImportSpecifiers, j.stringLiteral(toPackage)))
51
+ hasChanges = true
52
+ })
53
+
54
+ return hasChanges
55
+ }
@@ -0,0 +1,40 @@
1
+ import {type API, type FileInfo} from 'jscodeshift'
2
+
3
+ import {defineInlineTest} from './testUtils'
4
+ import {transformImportAlias} from './transformImportAlias'
5
+
6
+ const WARNING = 'Consider renaming LegacyCard to Box'
7
+
8
+ function transform(fileInfo: FileInfo, api: API): string {
9
+ const j = api.jscodeshift
10
+ const root = j(fileInfo.source)
11
+
12
+ transformImportAlias(j, root, 'Card', 'Box', 'LegacyCard', WARNING)
13
+
14
+ return root.toSource()
15
+ }
16
+
17
+ defineInlineTest(
18
+ transform,
19
+ {},
20
+ `
21
+ import {Card as LegacyCard} from '@sanity/ui'
22
+ `,
23
+ `
24
+ // UI-CODEMOD TODO: ${WARNING}
25
+ import { Box as LegacyCard } from '@sanity/ui';
26
+ `,
27
+ 'transforms aliased import and adds todo comment',
28
+ )
29
+
30
+ defineInlineTest(
31
+ transform,
32
+ {},
33
+ `
34
+ import {Card} from '@sanity/ui'
35
+ `,
36
+ `
37
+ import {Card} from '@sanity/ui'
38
+ `,
39
+ 'does not transform non-aliased import',
40
+ )
@@ -0,0 +1,49 @@
1
+ import type {API} from 'jscodeshift'
2
+
3
+ import {insertTodoWarning} from './insertTodoWarning'
4
+
5
+ export function transformImportAlias(
6
+ j: API['jscodeshift'],
7
+ root: ReturnType<API['jscodeshift']>,
8
+ fromExport: string,
9
+ toExport: string,
10
+ localName: string,
11
+ warning?: string,
12
+ ): boolean {
13
+ let updated = 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
+ for (const spec of specs) {
23
+ if (spec.type !== 'ImportSpecifier' || spec.imported.type !== 'Identifier') {
24
+ continue
25
+ }
26
+
27
+ if (spec.imported.name !== fromExport) {
28
+ continue
29
+ }
30
+
31
+ const local = spec.local?.type === 'Identifier' ? spec.local.name : spec.imported.name
32
+
33
+ if (local !== localName) {
34
+ continue
35
+ }
36
+
37
+ spec.imported.name = toExport
38
+ updated = true
39
+
40
+ if (warning) {
41
+ insertTodoWarning(j, path, warning)
42
+ }
43
+
44
+ return
45
+ }
46
+ })
47
+
48
+ return updated
49
+ }