@sanity/ui-codemod 1.0.0-alpha.2 → 1.0.0-alpha.4
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/_chunks-es/box.mods.js +4 -2
- package/dist/_chunks-es/box.mods.js.map +1 -1
- package/dist/_chunks-es/layout-mods.js +13 -1
- package/dist/_chunks-es/layout-mods.js.map +1 -1
- package/dist/_chunks-es/transformComponent.js +51 -22
- package/dist/_chunks-es/transformComponent.js.map +1 -1
- package/dist/transforms/latest/box/box.js +133 -18
- package/dist/transforms/latest/box/box.js.map +1 -1
- package/package.json +1 -1
- package/src/constants/latest/layout-mods.ts +12 -0
- package/src/transforms/latest/box/box.test.ts +202 -4
- package/src/transforms/latest/box/box.ts +36 -10
- package/src/utils/getElementMatchNames.ts +7 -0
- package/src/utils/getMappingValue.ts +18 -21
- package/src/utils/getNamedExportInit.ts +111 -0
- package/src/utils/getStaticAttributeExpression.ts +62 -9
- package/src/utils/getStyledComponentAliases.ts +85 -1
- package/src/utils/insertTodoWarning.ts +2 -14
- package/src/utils/parseModule.ts +29 -0
- package/src/utils/replaceElement.test.ts +28 -26
- package/src/utils/replaceElement.ts +4 -5
- package/src/utils/replaceStyledComponent.test.ts +40 -14
- package/src/utils/replaceStyledComponent.ts +2 -5
- package/src/utils/resolveRelativeModulePath.ts +37 -0
- package/src/utils/shouldTransformComponent.test.ts +14 -0
- package/src/utils/shouldTransformComponent.ts +5 -0
- package/src/utils/transformAttributes.test.ts +2 -2
- package/src/utils/transformStyledComponents.test.ts +80 -14
- package/src/utils/transformStyledComponents.ts +7 -16
- package/src/utils/getComponentJsxNames.ts +0 -17
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type {API, Expression, ExportSpecifier} from 'jscodeshift'
|
|
2
|
+
|
|
3
|
+
import {parseModule} from './parseModule'
|
|
4
|
+
import {resolveRelativeModulePath} from './resolveRelativeModulePath'
|
|
5
|
+
|
|
6
|
+
export type NamedExportInit = {
|
|
7
|
+
init: Expression
|
|
8
|
+
modulePath: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function findVariableInit(
|
|
12
|
+
j: API['jscodeshift'],
|
|
13
|
+
root: ReturnType<API['jscodeshift']>,
|
|
14
|
+
name: string,
|
|
15
|
+
): Expression | null {
|
|
16
|
+
let init: Expression | null = null
|
|
17
|
+
|
|
18
|
+
root.find(j.VariableDeclarator).forEach((path) => {
|
|
19
|
+
const {id} = path.node
|
|
20
|
+
|
|
21
|
+
if (id.type === 'Identifier' && id.name === name && path.node.init) {
|
|
22
|
+
init = path.node.init
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return init
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getExportSpecifierName(exported: ExportSpecifier['exported']): string | null {
|
|
30
|
+
if (exported.type === 'Identifier' || exported.type === 'JSXIdentifier') {
|
|
31
|
+
return exported.name
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getNamedExportInit(
|
|
38
|
+
j: API['jscodeshift'],
|
|
39
|
+
exportName: string,
|
|
40
|
+
filePath: string,
|
|
41
|
+
visited: Set<string> = new Set(),
|
|
42
|
+
): NamedExportInit | null {
|
|
43
|
+
if (visited.has(filePath)) {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
visited.add(filePath)
|
|
48
|
+
|
|
49
|
+
const root = parseModule(j, filePath)
|
|
50
|
+
|
|
51
|
+
if (!root) {
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const path of root.find(j.ExportNamedDeclaration).paths()) {
|
|
56
|
+
const {declaration, specifiers, source} = path.node
|
|
57
|
+
const reexportSource = typeof source?.value === 'string' ? source.value : null
|
|
58
|
+
|
|
59
|
+
if (declaration?.type === 'VariableDeclaration') {
|
|
60
|
+
for (const declarator of declaration.declarations) {
|
|
61
|
+
if (declarator.type !== 'VariableDeclarator') {
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
declarator.id.type === 'Identifier' &&
|
|
67
|
+
declarator.id.name === exportName &&
|
|
68
|
+
declarator.init
|
|
69
|
+
) {
|
|
70
|
+
return {init: declarator.init, modulePath: filePath}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const spec of specifiers ?? []) {
|
|
76
|
+
if (spec.type !== 'ExportSpecifier') {
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const exported = getExportSpecifierName(spec.exported)
|
|
81
|
+
|
|
82
|
+
if (!exported || exported !== exportName) {
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const local = spec.local?.type === 'Identifier' ? spec.local.name : exported
|
|
87
|
+
|
|
88
|
+
if (reexportSource) {
|
|
89
|
+
const resolvedPath = resolveRelativeModulePath(filePath, reexportSource)
|
|
90
|
+
|
|
91
|
+
if (resolvedPath) {
|
|
92
|
+
const followed = getNamedExportInit(j, local, resolvedPath, visited)
|
|
93
|
+
|
|
94
|
+
if (followed) {
|
|
95
|
+
return followed
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const init = findVariableInit(j, root, local)
|
|
103
|
+
|
|
104
|
+
if (init) {
|
|
105
|
+
return {init, modulePath: filePath}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
import type {API, JSXAttribute, JSXSpreadAttribute} from 'jscodeshift'
|
|
2
2
|
|
|
3
|
+
import type {AnyExpression} from '../types/AnyExpression'
|
|
3
4
|
import {getAttribute} from './getAttribute'
|
|
4
5
|
import {getAttributeExpression} from './getAttributeExpression'
|
|
5
6
|
|
|
6
7
|
export type CompositePrimitive = string | number | boolean
|
|
7
8
|
|
|
9
|
+
export type StaticAttributeValue = CompositePrimitive | (CompositePrimitive | null | undefined)[]
|
|
10
|
+
|
|
11
|
+
function getStaticPrimitive(expr: AnyExpression): CompositePrimitive | undefined {
|
|
12
|
+
if (
|
|
13
|
+
expr.type === 'StringLiteral' ||
|
|
14
|
+
expr.type === 'NumericLiteral' ||
|
|
15
|
+
expr.type === 'BooleanLiteral' ||
|
|
16
|
+
expr.type === 'Literal'
|
|
17
|
+
) {
|
|
18
|
+
const {value} = expr
|
|
19
|
+
|
|
20
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
21
|
+
return value
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
8
28
|
export function getStaticAttributeExpression(
|
|
9
29
|
j: API['jscodeshift'],
|
|
10
30
|
attrs: (JSXAttribute | JSXSpreadAttribute)[],
|
|
11
31
|
name: string,
|
|
12
|
-
):
|
|
32
|
+
): StaticAttributeValue | null {
|
|
13
33
|
const attr = getAttribute(attrs, name)
|
|
14
34
|
|
|
15
35
|
if (!attr) {
|
|
@@ -22,14 +42,47 @@ export function getStaticAttributeExpression(
|
|
|
22
42
|
return null
|
|
23
43
|
}
|
|
24
44
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
45
|
+
const primitive = getStaticPrimitive(expr as AnyExpression)
|
|
46
|
+
|
|
47
|
+
if (primitive !== undefined) {
|
|
48
|
+
return primitive
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (expr.type !== 'ArrayExpression') {
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const elements = expr.elements as ((AnyExpression & {name?: string}) | null)[]
|
|
56
|
+
const values: (CompositePrimitive | null | undefined)[] = []
|
|
57
|
+
|
|
58
|
+
for (const element of elements) {
|
|
59
|
+
if (element === null || element.type === 'NullLiteral') {
|
|
60
|
+
values.push(null)
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (element.type === 'SpreadElement') {
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (element.type === 'Literal' && element.value === null) {
|
|
69
|
+
values.push(null)
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (element.type === 'Identifier' && element.name === 'undefined') {
|
|
74
|
+
values.push(undefined)
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const elementValue = getStaticPrimitive(element)
|
|
79
|
+
|
|
80
|
+
if (elementValue === undefined) {
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
values.push(elementValue)
|
|
32
85
|
}
|
|
33
86
|
|
|
34
|
-
return
|
|
87
|
+
return values
|
|
35
88
|
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type {API, Collection} from 'jscodeshift'
|
|
2
2
|
|
|
3
|
+
import type {BaseOptions} from '../types/BaseOptions'
|
|
4
|
+
import {getComponentLocalNames} from './getComponentLocalNames'
|
|
5
|
+
import {getNamedExportInit} from './getNamedExportInit'
|
|
3
6
|
import {getStyledComponentName} from './getStyledComponentName'
|
|
7
|
+
import {parseModule} from './parseModule'
|
|
8
|
+
import {resolveRelativeModulePath} from './resolveRelativeModulePath'
|
|
4
9
|
|
|
5
|
-
export function
|
|
10
|
+
export function getSameFileStyledComponentAliases(
|
|
6
11
|
j: API['jscodeshift'],
|
|
7
12
|
root: Collection,
|
|
8
13
|
localNames: Iterable<string>,
|
|
@@ -26,3 +31,82 @@ export function getStyledComponentAliases(
|
|
|
26
31
|
|
|
27
32
|
return aliases
|
|
28
33
|
}
|
|
34
|
+
|
|
35
|
+
export function getImportedStyledComponentAliases(
|
|
36
|
+
j: API['jscodeshift'],
|
|
37
|
+
root: Collection,
|
|
38
|
+
componentName: string,
|
|
39
|
+
filePath: string | undefined,
|
|
40
|
+
options?: BaseOptions,
|
|
41
|
+
): Set<string> {
|
|
42
|
+
const aliases = new Set<string>()
|
|
43
|
+
|
|
44
|
+
if (!filePath) {
|
|
45
|
+
return aliases
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
root.find(j.ImportDeclaration).forEach((path) => {
|
|
49
|
+
const source = path.node.source.value
|
|
50
|
+
|
|
51
|
+
if (typeof source !== 'string') {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const resolvedPath = resolveRelativeModulePath(filePath, source)
|
|
56
|
+
|
|
57
|
+
if (!resolvedPath) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const spec of path.node.specifiers ?? []) {
|
|
62
|
+
if (spec.type !== 'ImportSpecifier') {
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if ('importKind' in spec && spec.importKind === 'type') {
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (spec.imported.type !== 'Identifier') {
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const exportName = spec.imported.name
|
|
75
|
+
const localName = spec.local?.type === 'Identifier' ? spec.local.name : exportName
|
|
76
|
+
const namedExport = getNamedExportInit(j, exportName, resolvedPath)
|
|
77
|
+
|
|
78
|
+
if (!namedExport) {
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const sourceRoot = parseModule(j, namedExport.modulePath)
|
|
83
|
+
|
|
84
|
+
if (!sourceRoot) {
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const exportLocalNames = getComponentLocalNames(j, sourceRoot, componentName, options)
|
|
89
|
+
const baseComponent = getStyledComponentName(namedExport.init)
|
|
90
|
+
|
|
91
|
+
if (baseComponent && exportLocalNames.has(baseComponent)) {
|
|
92
|
+
aliases.add(localName)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
return aliases
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function getStyledComponentAliases(
|
|
101
|
+
j: API['jscodeshift'],
|
|
102
|
+
root: Collection,
|
|
103
|
+
componentName: string,
|
|
104
|
+
filePath: string | undefined,
|
|
105
|
+
localNames: Set<string>,
|
|
106
|
+
options?: BaseOptions,
|
|
107
|
+
): Set<string> {
|
|
108
|
+
const sameFile = getSameFileStyledComponentAliases(j, root, localNames)
|
|
109
|
+
const imported = getImportedStyledComponentAliases(j, root, componentName, filePath, options)
|
|
110
|
+
|
|
111
|
+
return new Set([...sameFile, ...imported])
|
|
112
|
+
}
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type API,
|
|
3
|
-
type ASTPath,
|
|
4
|
-
type ImportDeclaration,
|
|
5
|
-
type JSXOpeningElement,
|
|
6
|
-
type VariableDeclarator,
|
|
7
|
-
} from 'jscodeshift'
|
|
1
|
+
import {type API, type ASTPath, type ImportDeclaration, type JSXOpeningElement} from 'jscodeshift'
|
|
8
2
|
|
|
9
3
|
type CommentableNode = {
|
|
10
4
|
comments?: {type: string; value: string; leading?: boolean}[] | null
|
|
@@ -16,19 +10,13 @@ type CommentableNode = {
|
|
|
16
10
|
*/
|
|
17
11
|
export function insertTodoWarning(
|
|
18
12
|
j: API['jscodeshift'],
|
|
19
|
-
path: ASTPath<JSXOpeningElement | ImportDeclaration
|
|
13
|
+
path: ASTPath<JSXOpeningElement | ImportDeclaration>,
|
|
20
14
|
warning: string,
|
|
21
15
|
): boolean {
|
|
22
16
|
let target: CommentableNode | null = null
|
|
23
17
|
|
|
24
18
|
if (path.node.type === 'ImportDeclaration') {
|
|
25
19
|
target = path.node
|
|
26
|
-
} else if (path.node.type === 'VariableDeclarator') {
|
|
27
|
-
const parent = path.parent
|
|
28
|
-
|
|
29
|
-
if (parent?.node.type === 'VariableDeclaration') {
|
|
30
|
-
target = parent.node
|
|
31
|
-
}
|
|
32
20
|
} else if (path.parent?.node.type === 'JSXElement') {
|
|
33
21
|
target = path.parent.node
|
|
34
22
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {readFileSync} from 'node:fs'
|
|
2
|
+
|
|
3
|
+
import type {API, Collection} from 'jscodeshift'
|
|
4
|
+
|
|
5
|
+
const parseCache = new Map<string, Collection>()
|
|
6
|
+
|
|
7
|
+
export function parseModule(j: API['jscodeshift'], filePath: string): Collection | null {
|
|
8
|
+
const cached = parseCache.get(filePath)
|
|
9
|
+
|
|
10
|
+
if (cached) {
|
|
11
|
+
return cached
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const source = readFileSync(filePath, 'utf8')
|
|
16
|
+
const root = j(source)
|
|
17
|
+
|
|
18
|
+
parseCache.set(filePath, root)
|
|
19
|
+
|
|
20
|
+
return root
|
|
21
|
+
} catch {
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @internal */
|
|
27
|
+
export function clearModuleParseCache(): void {
|
|
28
|
+
parseCache.clear()
|
|
29
|
+
}
|
|
@@ -21,9 +21,21 @@ const TO_MOD: AttributeMods = {
|
|
|
21
21
|
},
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function transform(
|
|
24
|
+
function transform(
|
|
25
|
+
fileInfo: FileInfo,
|
|
26
|
+
api: API,
|
|
27
|
+
options?: {
|
|
28
|
+
setLocalNames?: boolean
|
|
29
|
+
unsetLocalNames?: boolean
|
|
30
|
+
},
|
|
31
|
+
): string {
|
|
25
32
|
const j = api.jscodeshift
|
|
26
33
|
const root = j(fileInfo.source)
|
|
34
|
+
const localNames = options?.unsetLocalNames
|
|
35
|
+
? (new Set() as Set<string>)
|
|
36
|
+
: options?.setLocalNames
|
|
37
|
+
? getComponentLocalNames(j, root, 'Card')
|
|
38
|
+
: undefined
|
|
27
39
|
|
|
28
40
|
replaceElement(
|
|
29
41
|
j,
|
|
@@ -31,6 +43,7 @@ function transform(fileInfo: FileInfo, api: API): string {
|
|
|
31
43
|
(attrs) => !getAttribute(attrs, 'margin'),
|
|
32
44
|
{
|
|
33
45
|
element: 'Card',
|
|
46
|
+
...(localNames !== undefined ? {localNames} : {}),
|
|
34
47
|
callback: (path) => transformAttributes(j, path, FROM_MOD, 'Warning'),
|
|
35
48
|
},
|
|
36
49
|
{
|
|
@@ -140,32 +153,9 @@ defineInlineTest(
|
|
|
140
153
|
'replaces element and combines imports',
|
|
141
154
|
)
|
|
142
155
|
|
|
143
|
-
function transformWithLocalNames(fileInfo: FileInfo, api: API): string {
|
|
144
|
-
const j = api.jscodeshift
|
|
145
|
-
const root = j(fileInfo.source)
|
|
146
|
-
const localNames = getComponentLocalNames(j, root, 'Card')
|
|
147
|
-
|
|
148
|
-
replaceElement(
|
|
149
|
-
j,
|
|
150
|
-
root,
|
|
151
|
-
(attrs) => !getAttribute(attrs, 'margin'),
|
|
152
|
-
{
|
|
153
|
-
element: 'Card',
|
|
154
|
-
localNames,
|
|
155
|
-
callback: (path) => transformAttributes(j, path, FROM_MOD, 'Warning'),
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
element: 'Box',
|
|
159
|
-
callback: (path) => transformAttributes(j, path, TO_MOD, 'Warning'),
|
|
160
|
-
},
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
return root.toSource()
|
|
164
|
-
}
|
|
165
|
-
|
|
166
156
|
defineInlineTest(
|
|
167
|
-
|
|
168
|
-
{},
|
|
157
|
+
transform,
|
|
158
|
+
{setLocalNames: true},
|
|
169
159
|
`
|
|
170
160
|
import {Card as LegacyCard} from '@sanity/ui'
|
|
171
161
|
|
|
@@ -179,3 +169,15 @@ defineInlineTest(
|
|
|
179
169
|
`,
|
|
180
170
|
'preserves aliased jsx name and rewrites import',
|
|
181
171
|
)
|
|
172
|
+
|
|
173
|
+
defineInlineTest(
|
|
174
|
+
transform,
|
|
175
|
+
{unsetLocalNames: true},
|
|
176
|
+
`
|
|
177
|
+
<Card padding={1} />
|
|
178
|
+
`,
|
|
179
|
+
`
|
|
180
|
+
<Card padding={1} />
|
|
181
|
+
`,
|
|
182
|
+
'does not match bare element when localNames is empty',
|
|
183
|
+
)
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type {API, ASTPath, JSXAttribute, JSXOpeningElement, JSXSpreadAttribute} from 'jscodeshift'
|
|
2
2
|
|
|
3
3
|
import {addImportSpecifier} from './addImportSpecifier'
|
|
4
|
+
import {getElementMatchNames} from './getElementMatchNames'
|
|
4
5
|
import {removeUnusedImport} from './removeUnusedImport'
|
|
5
6
|
import {transformImportAlias} from './transformImportAlias'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Replaces JSX component if `filter` passes. Runs callbacks and updates imports.
|
|
10
|
+
* If `localNames` is omitted, matches `from.element`. `localNames` may be an empty set,
|
|
11
|
+
* to avoid rewriting unrelated JSX when only cross-file styled aliases are present.
|
|
9
12
|
* Returns whether the AST was updated.
|
|
10
13
|
*/
|
|
11
14
|
export function replaceElement(
|
|
@@ -22,11 +25,7 @@ export function replaceElement(
|
|
|
22
25
|
callback?: (path: ASTPath<JSXOpeningElement>) => boolean | void
|
|
23
26
|
},
|
|
24
27
|
): boolean {
|
|
25
|
-
const names =
|
|
26
|
-
|
|
27
|
-
if (names.size === 0) {
|
|
28
|
-
names.add(from.element)
|
|
29
|
-
}
|
|
28
|
+
const names = getElementMatchNames(from.element, from.localNames)
|
|
30
29
|
|
|
31
30
|
let hasChanges = false
|
|
32
31
|
let needsDefaultImportUpdate = false
|
|
@@ -4,21 +4,31 @@ import {getComponentLocalNames} from './getComponentLocalNames'
|
|
|
4
4
|
import {replaceStyledComponent} from './replaceStyledComponent'
|
|
5
5
|
import {defineInlineTest} from './testUtils'
|
|
6
6
|
|
|
7
|
-
function transform(
|
|
7
|
+
function transform(
|
|
8
|
+
fileInfo: FileInfo,
|
|
9
|
+
api: API,
|
|
10
|
+
options?: {
|
|
11
|
+
setLocalNames?: boolean
|
|
12
|
+
unsetLocalNames?: boolean
|
|
13
|
+
},
|
|
14
|
+
): string {
|
|
8
15
|
const j = api.jscodeshift
|
|
9
16
|
const root = j(fileInfo.source)
|
|
17
|
+
const localNames = options?.unsetLocalNames
|
|
18
|
+
? (new Set() as Set<string>)
|
|
19
|
+
: options?.setLocalNames
|
|
20
|
+
? getComponentLocalNames(j, root, 'Card')
|
|
21
|
+
: undefined
|
|
10
22
|
|
|
11
|
-
replaceStyledComponent(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
replaceStyledComponent(j, root, {element: 'Card', localNames}, 'Box')
|
|
23
|
+
replaceStyledComponent(
|
|
24
|
+
j,
|
|
25
|
+
root,
|
|
26
|
+
{
|
|
27
|
+
element: 'Card',
|
|
28
|
+
...(localNames !== undefined ? {localNames} : {}),
|
|
29
|
+
},
|
|
30
|
+
'Box',
|
|
31
|
+
)
|
|
22
32
|
|
|
23
33
|
return root.toSource()
|
|
24
34
|
}
|
|
@@ -40,8 +50,8 @@ defineInlineTest(
|
|
|
40
50
|
)
|
|
41
51
|
|
|
42
52
|
defineInlineTest(
|
|
43
|
-
|
|
44
|
-
{},
|
|
53
|
+
transform,
|
|
54
|
+
{setLocalNames: true},
|
|
45
55
|
`
|
|
46
56
|
import {Card as LegacyCard} from '@sanity/ui'
|
|
47
57
|
|
|
@@ -55,3 +65,19 @@ defineInlineTest(
|
|
|
55
65
|
`,
|
|
56
66
|
'preserves aliased styled import and rewrites import',
|
|
57
67
|
)
|
|
68
|
+
|
|
69
|
+
defineInlineTest(
|
|
70
|
+
transform,
|
|
71
|
+
{unsetLocalNames: true},
|
|
72
|
+
`
|
|
73
|
+
import {Card} from '@sanity/ui'
|
|
74
|
+
|
|
75
|
+
const RootCard = styled(Card)(({theme}) => {})
|
|
76
|
+
`,
|
|
77
|
+
`
|
|
78
|
+
import {Card} from '@sanity/ui'
|
|
79
|
+
|
|
80
|
+
const RootCard = styled(Card)(({theme}) => {})
|
|
81
|
+
`,
|
|
82
|
+
'does not match styled component when localNames is empty',
|
|
83
|
+
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {API} from 'jscodeshift'
|
|
2
2
|
|
|
3
3
|
import {addImportSpecifier} from './addImportSpecifier'
|
|
4
|
+
import {getElementMatchNames} from './getElementMatchNames'
|
|
4
5
|
import {removeUnusedImport} from './removeUnusedImport'
|
|
5
6
|
import {transformImportAlias} from './transformImportAlias'
|
|
6
7
|
|
|
@@ -13,11 +14,7 @@ export function replaceStyledComponent(
|
|
|
13
14
|
},
|
|
14
15
|
to: string,
|
|
15
16
|
): void {
|
|
16
|
-
const names =
|
|
17
|
-
|
|
18
|
-
if (names.size === 0) {
|
|
19
|
-
names.add(from.element)
|
|
20
|
-
}
|
|
17
|
+
const names = getElementMatchNames(from.element, from.localNames)
|
|
21
18
|
|
|
22
19
|
let needsDefaultImportUpdate = false
|
|
23
20
|
const aliases = new Set<string>()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {existsSync} from 'node:fs'
|
|
2
|
+
import {dirname, join, resolve} from 'node:path'
|
|
3
|
+
|
|
4
|
+
const EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'] as const
|
|
5
|
+
|
|
6
|
+
export function resolveRelativeModulePath(
|
|
7
|
+
fromFilePath: string,
|
|
8
|
+
moduleSpecifier: string,
|
|
9
|
+
): string | null {
|
|
10
|
+
if (!moduleSpecifier.startsWith('.')) {
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const base = resolve(dirname(fromFilePath), moduleSpecifier)
|
|
15
|
+
|
|
16
|
+
for (const ext of EXTENSIONS) {
|
|
17
|
+
const filePath = `${base}${ext}`
|
|
18
|
+
|
|
19
|
+
if (existsSync(filePath)) {
|
|
20
|
+
return filePath
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const ext of EXTENSIONS) {
|
|
25
|
+
const indexPath = join(base, `index${ext}`)
|
|
26
|
+
|
|
27
|
+
if (existsSync(indexPath)) {
|
|
28
|
+
return indexPath
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (existsSync(base)) {
|
|
33
|
+
return base
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
@@ -18,6 +18,20 @@ describe('shouldTransformComponent', () => {
|
|
|
18
18
|
expect(shouldTransformComponent(j, root, 'Box', new Set())).toBe(false)
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
+
it('returns true when styled aliases are passed in', () => {
|
|
22
|
+
const root = j(`
|
|
23
|
+
import {RootBox} from './Component.styled'
|
|
24
|
+
|
|
25
|
+
export function Component() {
|
|
26
|
+
return <RootBox />
|
|
27
|
+
}
|
|
28
|
+
`)
|
|
29
|
+
|
|
30
|
+
expect(
|
|
31
|
+
shouldTransformComponent(j, root, 'Box', new Set(), undefined, new Set(['RootBox'])),
|
|
32
|
+
).toBe(true)
|
|
33
|
+
})
|
|
34
|
+
|
|
21
35
|
it('returns false when fromPackage and toPackage are the same', () => {
|
|
22
36
|
const root = j(`
|
|
23
37
|
import {Box} from '@legacy/ui'
|
|
@@ -14,11 +14,16 @@ export function shouldTransformComponent(
|
|
|
14
14
|
componentName: string,
|
|
15
15
|
localNames: Set<string>,
|
|
16
16
|
options?: BaseOptions,
|
|
17
|
+
styledAliases?: Set<string>,
|
|
17
18
|
): boolean {
|
|
18
19
|
if (localNames.size > 0) {
|
|
19
20
|
return true
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
if (styledAliases && styledAliases.size > 0) {
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
const fromPackage = options?.fromPackage ?? DEFAULT_UI_PACKAGE
|
|
23
28
|
const toPackage = options?.toPackage ?? DEFAULT_UI_PACKAGE
|
|
24
29
|
|
|
@@ -290,7 +290,7 @@ defineInlineTest(
|
|
|
290
290
|
transform,
|
|
291
291
|
{},
|
|
292
292
|
`
|
|
293
|
-
<div flex=
|
|
293
|
+
<div flex={1} />
|
|
294
294
|
`,
|
|
295
295
|
`
|
|
296
296
|
<div flexGrow={1} />
|
|
@@ -314,7 +314,7 @@ defineInlineTest(
|
|
|
314
314
|
transform,
|
|
315
315
|
{},
|
|
316
316
|
`
|
|
317
|
-
<div flex={[
|
|
317
|
+
<div flex={[2, "none"]} />
|
|
318
318
|
`,
|
|
319
319
|
`
|
|
320
320
|
<div
|