@sanity/ui-codemod 1.0.0-alpha.2 → 1.0.0-alpha.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/dist/_chunks-es/box.mods.js +4 -2
- package/dist/_chunks-es/box.mods.js.map +1 -1
- package/dist/_chunks-es/transformComponent.js +39 -10
- 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/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/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/transformStyledComponents.test.ts +80 -14
- package/src/utils/transformStyledComponents.ts +7 -16
- package/src/utils/getComponentJsxNames.ts +0 -17
package/package.json
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {mkdtempSync, readFileSync, rmSync, writeFileSync} from 'node:fs'
|
|
2
|
+
import {tmpdir} from 'node:os'
|
|
3
|
+
import {join} from 'node:path'
|
|
4
|
+
|
|
5
|
+
import {afterEach, describe, expect, it} from 'vitest'
|
|
6
|
+
|
|
7
|
+
const applyTransform = require('jscodeshift/dist/testUtils').applyTransform
|
|
8
|
+
|
|
9
|
+
import {clearModuleParseCache} from '../../../utils/parseModule'
|
|
1
10
|
import {defineInlineTest} from '../../../utils/testUtils'
|
|
2
11
|
import transform from './box'
|
|
3
12
|
|
|
@@ -178,6 +187,38 @@ defineInlineTest(
|
|
|
178
187
|
'replaces Box with Grid when display is grid',
|
|
179
188
|
)
|
|
180
189
|
|
|
190
|
+
defineInlineTest(
|
|
191
|
+
transform,
|
|
192
|
+
{},
|
|
193
|
+
`
|
|
194
|
+
<Box display={['none', undefined, null, 'flex']} />
|
|
195
|
+
`,
|
|
196
|
+
`
|
|
197
|
+
<Flex display={['none', undefined, null, 'flex']} />
|
|
198
|
+
`,
|
|
199
|
+
'replaces Box with Flex when display is an array with flex',
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
defineInlineTest(
|
|
203
|
+
transform,
|
|
204
|
+
{},
|
|
205
|
+
`
|
|
206
|
+
<>
|
|
207
|
+
<Box display={['block', undefined, null, 'flex']} />
|
|
208
|
+
{/* This forces a change to avoid the transform return null */}
|
|
209
|
+
<Box display="flex" />
|
|
210
|
+
</>
|
|
211
|
+
`,
|
|
212
|
+
`
|
|
213
|
+
<>
|
|
214
|
+
<Box display={['block', undefined, null, 'flex']} />
|
|
215
|
+
{/* This forces a change to avoid the transform return null */}
|
|
216
|
+
<Flex display="flex" />
|
|
217
|
+
</>
|
|
218
|
+
`,
|
|
219
|
+
'does not replace Box with Flex when display is a mixed array',
|
|
220
|
+
)
|
|
221
|
+
|
|
181
222
|
defineInlineTest(
|
|
182
223
|
transform,
|
|
183
224
|
{},
|
|
@@ -194,11 +235,168 @@ defineInlineTest(
|
|
|
194
235
|
import {Box} from '@sanity/ui'
|
|
195
236
|
|
|
196
237
|
function Example() {
|
|
197
|
-
|
|
198
|
-
const RootBox = styled(Box)(({theme}) => ({}));
|
|
238
|
+
const RootBox = styled(Box)(({theme}) => ({}))
|
|
199
239
|
|
|
200
|
-
return
|
|
240
|
+
return (
|
|
241
|
+
// UI-CODEMOD TODO: Please double check styled(Box) migration below
|
|
242
|
+
<RootBox display="flex" alignItems="center" />
|
|
243
|
+
);
|
|
201
244
|
}
|
|
202
245
|
`,
|
|
203
|
-
'warns and does not transform attributes if styled Box
|
|
246
|
+
'warns and does not transform attributes if styled Box should be replaced',
|
|
204
247
|
)
|
|
248
|
+
|
|
249
|
+
const tempDirs: string[] = []
|
|
250
|
+
|
|
251
|
+
afterEach(() => {
|
|
252
|
+
clearModuleParseCache()
|
|
253
|
+
|
|
254
|
+
for (const dir of tempDirs.splice(0)) {
|
|
255
|
+
rmSync(dir, {recursive: true, force: true})
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
describe('cross-file styled aliases', () => {
|
|
260
|
+
it('transforms attributes on imported styled Box wrappers', () => {
|
|
261
|
+
const dir = mkdtempSync(join(tmpdir(), 'ui-codemod-box-crossfile-'))
|
|
262
|
+
|
|
263
|
+
tempDirs.push(dir)
|
|
264
|
+
|
|
265
|
+
writeFileSync(
|
|
266
|
+
join(dir, 'Component.styled.tsx'),
|
|
267
|
+
`
|
|
268
|
+
import {Box} from '@sanity/ui'
|
|
269
|
+
|
|
270
|
+
export const RootBox = styled(Box)(({theme}) => ({}))
|
|
271
|
+
`,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
writeFileSync(
|
|
275
|
+
join(dir, 'Component.tsx'),
|
|
276
|
+
`
|
|
277
|
+
import {RootBox} from './Component.styled'
|
|
278
|
+
|
|
279
|
+
export function Component() {
|
|
280
|
+
return <RootBox alignItems="center" />
|
|
281
|
+
}
|
|
282
|
+
`,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
const importerPath = join(dir, 'Component.tsx')
|
|
286
|
+
const source = readFileSync(importerPath, 'utf8')
|
|
287
|
+
const output = applyTransform(transform, {}, {source, path: importerPath}, {parser: 'tsx'})
|
|
288
|
+
|
|
289
|
+
expect(output).toContain('alignItems: "center"')
|
|
290
|
+
expect(output).not.toContain('<RootBox alignItems="center" />')
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
it('adds todo warning when imported styled Box wrapper should be replaced', () => {
|
|
294
|
+
const dir = mkdtempSync(join(tmpdir(), 'ui-codemod-box-crossfile-todo-'))
|
|
295
|
+
|
|
296
|
+
tempDirs.push(dir)
|
|
297
|
+
|
|
298
|
+
writeFileSync(
|
|
299
|
+
join(dir, 'Component.styled.tsx'),
|
|
300
|
+
`
|
|
301
|
+
import {Box} from '@sanity/ui'
|
|
302
|
+
|
|
303
|
+
export const RootBox = styled(Box)(({theme}) => ({}))
|
|
304
|
+
`,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
writeFileSync(
|
|
308
|
+
join(dir, 'Component.tsx'),
|
|
309
|
+
`
|
|
310
|
+
import {RootBox} from './Component.styled'
|
|
311
|
+
|
|
312
|
+
export function Component() {
|
|
313
|
+
return <RootBox display="flex" alignItems="center" />
|
|
314
|
+
}
|
|
315
|
+
`,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
const importerPath = join(dir, 'Component.tsx')
|
|
319
|
+
const source = readFileSync(importerPath, 'utf8')
|
|
320
|
+
const output = applyTransform(transform, {}, {source, path: importerPath}, {parser: 'tsx'})
|
|
321
|
+
|
|
322
|
+
expect(output).toContain('UI-CODEMOD TODO: Please double check styled(Box) migration below')
|
|
323
|
+
expect(output).toContain('<RootBox display="flex" alignItems="center" />')
|
|
324
|
+
expect(output).not.toContain('const RootBox = styled(Box)')
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it('does not rewrite unrelated Box from another package', () => {
|
|
328
|
+
const dir = mkdtempSync(join(tmpdir(), 'ui-codemod-box-crossfile-unrelated-'))
|
|
329
|
+
|
|
330
|
+
tempDirs.push(dir)
|
|
331
|
+
|
|
332
|
+
writeFileSync(
|
|
333
|
+
join(dir, 'Component.styled.tsx'),
|
|
334
|
+
`
|
|
335
|
+
import {Box} from '@sanity/ui'
|
|
336
|
+
|
|
337
|
+
export const RootBox = styled(Box)(({theme}) => ({}))
|
|
338
|
+
`,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
writeFileSync(
|
|
342
|
+
join(dir, 'Component.tsx'),
|
|
343
|
+
`
|
|
344
|
+
import {Box} from 'another-package'
|
|
345
|
+
import {RootBox} from './Component.styled'
|
|
346
|
+
|
|
347
|
+
export function Component() {
|
|
348
|
+
return (
|
|
349
|
+
<>
|
|
350
|
+
<RootBox alignItems="center" />
|
|
351
|
+
<Box display="flex" />
|
|
352
|
+
</>
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
`,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
const importerPath = join(dir, 'Component.tsx')
|
|
359
|
+
const source = readFileSync(importerPath, 'utf8')
|
|
360
|
+
const output = applyTransform(transform, {}, {source, path: importerPath}, {parser: 'tsx'})
|
|
361
|
+
|
|
362
|
+
expect(output).toContain('alignItems: "center"')
|
|
363
|
+
expect(output).not.toContain('<RootBox alignItems="center" />')
|
|
364
|
+
expect(output).toContain('<Box display="flex" />')
|
|
365
|
+
expect(output).not.toContain('<Flex')
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
it('transforms styled Box wrappers imported through barrel re-exports', () => {
|
|
369
|
+
const dir = mkdtempSync(join(tmpdir(), 'ui-codemod-box-crossfile-barrel-'))
|
|
370
|
+
|
|
371
|
+
tempDirs.push(dir)
|
|
372
|
+
|
|
373
|
+
writeFileSync(
|
|
374
|
+
join(dir, 'Component.styled.tsx'),
|
|
375
|
+
`
|
|
376
|
+
import {Box} from '@sanity/ui'
|
|
377
|
+
|
|
378
|
+
export const RootBox = styled(Box)(({theme}) => ({}))
|
|
379
|
+
`,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
writeFileSync(join(dir, 'index.ts'), `export {RootBox} from './Component.styled'`)
|
|
383
|
+
|
|
384
|
+
writeFileSync(
|
|
385
|
+
join(dir, 'Component.tsx'),
|
|
386
|
+
`
|
|
387
|
+
import {RootBox} from './index'
|
|
388
|
+
|
|
389
|
+
export function Component() {
|
|
390
|
+
return <RootBox alignItems="center" />
|
|
391
|
+
}
|
|
392
|
+
`,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
const importerPath = join(dir, 'Component.tsx')
|
|
396
|
+
const source = readFileSync(importerPath, 'utf8')
|
|
397
|
+
const output = applyTransform(transform, {}, {source, path: importerPath}, {parser: 'tsx'})
|
|
398
|
+
|
|
399
|
+
expect(output).toContain('alignItems: "center"')
|
|
400
|
+
expect(output).not.toContain('<RootBox alignItems="center" />')
|
|
401
|
+
})
|
|
402
|
+
})
|
|
@@ -17,7 +17,7 @@ import {BOX_MODS} from './box.mods'
|
|
|
17
17
|
const BOX_TODO_WARNING = 'Please double check the Box migration below'
|
|
18
18
|
const FLEX_TODO_WARNING = 'Please double check the Flex migration below'
|
|
19
19
|
const GRID_TODO_WARNING = 'Please double check the Grid migration below'
|
|
20
|
-
const STYLED_TODO_WARNING = 'Please double check styled(Box) migration
|
|
20
|
+
const STYLED_TODO_WARNING = 'Please double check styled(Box) migration below'
|
|
21
21
|
|
|
22
22
|
/** @internal */
|
|
23
23
|
export default function transform(
|
|
@@ -29,22 +29,48 @@ export default function transform(
|
|
|
29
29
|
|
|
30
30
|
return transformComponent(fileInfo, api, ({j, root, markChanged}) => {
|
|
31
31
|
const localNames = getComponentLocalNames(j, root, 'Box', options)
|
|
32
|
-
const styledAliases = getStyledComponentAliases(
|
|
32
|
+
const styledAliases = getStyledComponentAliases(
|
|
33
|
+
j,
|
|
34
|
+
root,
|
|
35
|
+
'Box',
|
|
36
|
+
fileInfo.path,
|
|
37
|
+
localNames,
|
|
38
|
+
options,
|
|
39
|
+
)
|
|
33
40
|
|
|
34
|
-
if (!shouldTransformComponent(j, root, 'Box', localNames, options)) {
|
|
41
|
+
if (!shouldTransformComponent(j, root, 'Box', localNames, options, styledAliases)) {
|
|
35
42
|
return
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
const matchesDisplayValue = (
|
|
46
|
+
attrs: (JSXAttribute | JSXSpreadAttribute)[],
|
|
47
|
+
suffix: string,
|
|
48
|
+
exclude: string[],
|
|
49
|
+
) => {
|
|
50
|
+
const display = getStaticAttributeExpression(j, attrs, 'display') || []
|
|
51
|
+
const values = Array.isArray(display) ? display : [display]
|
|
52
|
+
let matchesExclude
|
|
53
|
+
|
|
54
|
+
for (const excluded of exclude) {
|
|
55
|
+
if (values.some((value) => typeof value === 'string' && value.endsWith(excluded))) {
|
|
56
|
+
matchesExclude = true
|
|
57
|
+
break
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (matchesExclude) {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
42
64
|
|
|
43
|
-
|
|
44
|
-
const display = getStaticAttributeExpression(j, attrs, 'display')
|
|
45
|
-
return display === 'grid' || display == 'inline-grid'
|
|
65
|
+
return values.some((value) => typeof value === 'string' && value.endsWith(suffix))
|
|
46
66
|
}
|
|
47
67
|
|
|
68
|
+
const replaceWithFlex = (attrs: (JSXAttribute | JSXSpreadAttribute)[]) =>
|
|
69
|
+
matchesDisplayValue(attrs, 'flex', ['block', 'inline', 'grid'])
|
|
70
|
+
|
|
71
|
+
const replaceWithGrid = (attrs: (JSXAttribute | JSXSpreadAttribute)[]) =>
|
|
72
|
+
matchesDisplayValue(attrs, 'grid', ['block', 'inline', 'flex'])
|
|
73
|
+
|
|
48
74
|
if (transformImport(j, root, 'Box', fromPackage, toPackage)) {
|
|
49
75
|
markChanged()
|
|
50
76
|
}
|
|
@@ -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
|
+
}
|