@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.
- package/bin/run.js +5 -0
- package/dist/_chunks-es/addAttribute.js +11 -0
- package/dist/_chunks-es/addAttribute.js.map +1 -0
- package/dist/_chunks-es/box.mods.js +186 -0
- package/dist/_chunks-es/box.mods.js.map +1 -0
- package/dist/_chunks-es/flex.mods.js +74 -0
- package/dist/_chunks-es/flex.mods.js.map +1 -0
- package/dist/_chunks-es/getTransformPath.js +75 -0
- package/dist/_chunks-es/getTransformPath.js.map +1 -0
- package/dist/_chunks-es/grid.mods.js +110 -0
- package/dist/_chunks-es/grid.mods.js.map +1 -0
- package/dist/_chunks-es/layout-mods.js +424 -0
- package/dist/_chunks-es/layout-mods.js.map +1 -0
- package/dist/_chunks-es/transformComponent.js +359 -0
- package/dist/_chunks-es/transformComponent.js.map +1 -0
- package/dist/commands/latest/box.d.ts +47 -0
- package/dist/commands/latest/box.js +17 -0
- package/dist/commands/latest/box.js.map +1 -0
- package/dist/commands/latest/card.d.ts +47 -0
- package/dist/commands/latest/card.js +17 -0
- package/dist/commands/latest/card.js.map +1 -0
- package/dist/commands/latest/code.d.ts +47 -0
- package/dist/commands/latest/code.js +17 -0
- package/dist/commands/latest/code.js.map +1 -0
- package/dist/commands/latest/container.d.ts +47 -0
- package/dist/commands/latest/container.js +17 -0
- package/dist/commands/latest/container.js.map +1 -0
- package/dist/commands/latest/flex.d.ts +47 -0
- package/dist/commands/latest/flex.js +17 -0
- package/dist/commands/latest/flex.js.map +1 -0
- package/dist/commands/latest/grid.d.ts +47 -0
- package/dist/commands/latest/grid.js +17 -0
- package/dist/commands/latest/grid.js.map +1 -0
- package/dist/commands/latest/heading.d.ts +47 -0
- package/dist/commands/latest/heading.js +17 -0
- package/dist/commands/latest/heading.js.map +1 -0
- package/dist/commands/latest/text.d.ts +47 -0
- package/dist/commands/latest/text.js +17 -0
- package/dist/commands/latest/text.js.map +1 -0
- package/dist/transforms/latest/box/box.d.ts +18 -0
- package/dist/transforms/latest/box/box.js +101 -0
- package/dist/transforms/latest/box/box.js.map +1 -0
- package/dist/transforms/latest/card/card.d.ts +18 -0
- package/dist/transforms/latest/card/card.js +618 -0
- package/dist/transforms/latest/card/card.js.map +1 -0
- package/dist/transforms/latest/code/code.d.ts +18 -0
- package/dist/transforms/latest/code/code.js +73 -0
- package/dist/transforms/latest/code/code.js.map +1 -0
- package/dist/transforms/latest/container/container.d.ts +18 -0
- package/dist/transforms/latest/container/container.js +91 -0
- package/dist/transforms/latest/container/container.js.map +1 -0
- package/dist/transforms/latest/flex/flex.d.ts +18 -0
- package/dist/transforms/latest/flex/flex.js +19 -0
- package/dist/transforms/latest/flex/flex.js.map +1 -0
- package/dist/transforms/latest/grid/grid.d.ts +18 -0
- package/dist/transforms/latest/grid/grid.js +19 -0
- package/dist/transforms/latest/grid/grid.js.map +1 -0
- package/dist/transforms/latest/heading/heading.d.ts +18 -0
- package/dist/transforms/latest/heading/heading.js +85 -0
- package/dist/transforms/latest/heading/heading.js.map +1 -0
- package/dist/transforms/latest/text/text.d.ts +18 -0
- package/dist/transforms/latest/text/text.js +88 -0
- package/dist/transforms/latest/text/text.js.map +1 -0
- package/package.json +68 -0
- package/src/baseCommand.ts +74 -0
- package/src/commands/latest/box.ts +21 -0
- package/src/commands/latest/card.ts +21 -0
- package/src/commands/latest/code.ts +21 -0
- package/src/commands/latest/container.ts +21 -0
- package/src/commands/latest/flex.ts +21 -0
- package/src/commands/latest/grid.ts +21 -0
- package/src/commands/latest/heading.ts +21 -0
- package/src/commands/latest/inline.ts +21 -0
- package/src/commands/latest/label.ts +21 -0
- package/src/commands/latest/stack.ts +21 -0
- package/src/commands/latest/text.ts +21 -0
- package/src/constants/latest/layout-mods.ts +422 -0
- package/src/transforms/latest/box/box.mods.ts +88 -0
- package/src/transforms/latest/box/box.test.ts +204 -0
- package/src/transforms/latest/box/box.ts +104 -0
- package/src/transforms/latest/card/card.mods.ts +592 -0
- package/src/transforms/latest/card/card.test.ts +207 -0
- package/src/transforms/latest/card/card.ts +71 -0
- package/src/transforms/latest/code/code.mods.ts +57 -0
- package/src/transforms/latest/code/code.test.ts +61 -0
- package/src/transforms/latest/code/code.ts +53 -0
- package/src/transforms/latest/container/container.mods.ts +77 -0
- package/src/transforms/latest/container/container.test.ts +105 -0
- package/src/transforms/latest/container/container.ts +42 -0
- package/src/transforms/latest/flex/flex.mods.ts +73 -0
- package/src/transforms/latest/flex/flex.test.ts +80 -0
- package/src/transforms/latest/flex/flex.ts +42 -0
- package/src/transforms/latest/grid/grid.mods.ts +109 -0
- package/src/transforms/latest/grid/grid.test.ts +97 -0
- package/src/transforms/latest/grid/grid.ts +42 -0
- package/src/transforms/latest/heading/heading.mods.ts +70 -0
- package/src/transforms/latest/heading/heading.test.ts +87 -0
- package/src/transforms/latest/heading/heading.ts +53 -0
- package/src/transforms/latest/inline/inline.mods.ts +65 -0
- package/src/transforms/latest/inline/inline.test.ts +57 -0
- package/src/transforms/latest/inline/inline.ts +42 -0
- package/src/transforms/latest/label/label.mods.ts +72 -0
- package/src/transforms/latest/label/label.test.ts +113 -0
- package/src/transforms/latest/label/label.ts +67 -0
- package/src/transforms/latest/stack/stack.mods.ts +12 -0
- package/src/transforms/latest/stack/stack.test.ts +160 -0
- package/src/transforms/latest/stack/stack.ts +102 -0
- package/src/transforms/latest/text/text.mods.ts +72 -0
- package/src/transforms/latest/text/text.test.ts +113 -0
- package/src/transforms/latest/text/text.ts +57 -0
- package/src/types/AnyExpression.ts +9 -0
- package/src/types/AttributeMods.ts +16 -0
- package/src/types/BaseOptions.ts +6 -0
- package/src/utils/addAttribute.test.ts +43 -0
- package/src/utils/addAttribute.ts +26 -0
- package/src/utils/addImportSpecifier.test.ts +36 -0
- package/src/utils/addImportSpecifier.ts +42 -0
- package/src/utils/getAttribute.ts +22 -0
- package/src/utils/getAttributeExpression.ts +22 -0
- package/src/utils/getAttributeValue.ts +20 -0
- package/src/utils/getClonedImportSpecifiers.ts +29 -0
- package/src/utils/getComponentJsxNames.ts +17 -0
- package/src/utils/getComponentLocalNames.ts +69 -0
- package/src/utils/getCompositeAttrValue.ts +39 -0
- package/src/utils/getMappingArray.ts +36 -0
- package/src/utils/getMappingExpression.ts +22 -0
- package/src/utils/getMappingValue.ts +52 -0
- package/src/utils/getShorthandAttributes.ts +54 -0
- package/src/utils/getSplitImportSpecifiers.ts +40 -0
- package/src/utils/getStaticAttributeExpression.ts +35 -0
- package/src/utils/getStyleExpression.ts +37 -0
- package/src/utils/getStyledComponentAliases.ts +28 -0
- package/src/utils/getStyledComponentName.ts +52 -0
- package/src/utils/getTransformPath.ts +17 -0
- package/src/utils/insertTodoWarning.ts +48 -0
- package/src/utils/isImportBindingUsed.ts +53 -0
- package/src/utils/isValidStyleType.ts +21 -0
- package/src/utils/mergeStyle.ts +64 -0
- package/src/utils/removeUnusedImport.test.ts +77 -0
- package/src/utils/removeUnusedImport.ts +57 -0
- package/src/utils/replaceElement.test.ts +181 -0
- package/src/utils/replaceElement.ts +106 -0
- package/src/utils/replaceStyledComponent.test.ts +57 -0
- package/src/utils/replaceStyledComponent.ts +60 -0
- package/src/utils/shouldTransformComponent.test.ts +61 -0
- package/src/utils/shouldTransformComponent.ts +40 -0
- package/src/utils/testUtils.ts +54 -0
- package/src/utils/transformAttributes.test.ts +544 -0
- package/src/utils/transformAttributes.ts +248 -0
- package/src/utils/transformComponent.test.ts +45 -0
- package/src/utils/transformComponent.ts +31 -0
- package/src/utils/transformImport.test.ts +175 -0
- package/src/utils/transformImport.ts +55 -0
- package/src/utils/transformImportAlias.test.ts +40 -0
- package/src/utils/transformImportAlias.ts +49 -0
- package/src/utils/transformStyledComponents.test.ts +172 -0
- package/src/utils/transformStyledComponents.ts +58 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {type API} from 'jscodeshift'
|
|
2
|
+
|
|
3
|
+
import type {AnyExpression} from '../types/AnyExpression'
|
|
4
|
+
|
|
5
|
+
export function getMappingExpression(
|
|
6
|
+
j: API['jscodeshift'],
|
|
7
|
+
val: string | boolean | number | undefined,
|
|
8
|
+
): AnyExpression {
|
|
9
|
+
if (val === undefined) {
|
|
10
|
+
return j.identifier('undefined')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (typeof val === 'boolean') {
|
|
14
|
+
return j.booleanLiteral(val)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (typeof val === 'number') {
|
|
18
|
+
return j.numericLiteral(val)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return j.stringLiteral(val)
|
|
22
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type {AnyExpression} from '../types/AnyExpression'
|
|
2
|
+
import type {AttributeMapping} from '../types/AttributeMods'
|
|
3
|
+
|
|
4
|
+
export function getMappingValue(mapping: AttributeMapping, expr: AnyExpression) {
|
|
5
|
+
let matchedVal
|
|
6
|
+
|
|
7
|
+
for (const val in mapping) {
|
|
8
|
+
if (expr.value === mapping[val]) {
|
|
9
|
+
matchedVal = mapping[val]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (matchedVal) {
|
|
14
|
+
return matchedVal
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let key
|
|
18
|
+
|
|
19
|
+
if (
|
|
20
|
+
expr.type === 'NumericLiteral' ||
|
|
21
|
+
expr.type === 'BooleanLiteral' ||
|
|
22
|
+
expr.type === 'StringLiteral' ||
|
|
23
|
+
expr.type === 'Literal'
|
|
24
|
+
) {
|
|
25
|
+
key = String(expr.value)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (expr.type === 'TemplateLiteral') {
|
|
29
|
+
const expressions = expr.expressions as unknown[]
|
|
30
|
+
const quasis = expr.quasis as {value: {cooked: string | null}}[]
|
|
31
|
+
|
|
32
|
+
if (expressions.length === 0 && quasis.length === 1) {
|
|
33
|
+
key = quasis[0]?.value.cooked ?? null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!key) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (Object.prototype.hasOwnProperty.call(mapping, key)) {
|
|
42
|
+
return mapping[key]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const number = Number(key)
|
|
46
|
+
|
|
47
|
+
if (!Number.isNaN(number) && Object.prototype.hasOwnProperty.call(mapping, number)) {
|
|
48
|
+
return mapping[number]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return
|
|
52
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type {API, JSXAttribute} from 'jscodeshift'
|
|
2
|
+
|
|
3
|
+
import type {AnyExpression} from '../types/AnyExpression'
|
|
4
|
+
import type {AttributeMod} from '../types/AttributeMods'
|
|
5
|
+
import {getMappingArray} from './getMappingArray'
|
|
6
|
+
import {getMappingExpression} from './getMappingExpression'
|
|
7
|
+
import {getMappingValue} from './getMappingValue'
|
|
8
|
+
|
|
9
|
+
export function getShorthandAttributes(
|
|
10
|
+
j: API['jscodeshift'],
|
|
11
|
+
expr: AnyExpression,
|
|
12
|
+
mod: AttributeMod,
|
|
13
|
+
) {
|
|
14
|
+
const attributes: JSXAttribute[] = []
|
|
15
|
+
|
|
16
|
+
if (!('props' in mod)) {
|
|
17
|
+
return []
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (expr.type === 'ArrayExpression') {
|
|
21
|
+
for (const prop of mod.props) {
|
|
22
|
+
const styleArray = getMappingArray(j, expr, prop.mapping, true)
|
|
23
|
+
|
|
24
|
+
if (!styleArray) {
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
attributes.push(
|
|
29
|
+
j.jsxAttribute(j.jsxIdentifier(prop.name), j.jsxExpressionContainer(styleArray)),
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const prop of mod.props) {
|
|
35
|
+
const styleValue = getMappingValue(prop.mapping, expr)
|
|
36
|
+
|
|
37
|
+
if (styleValue === undefined) {
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof styleValue === 'string') {
|
|
42
|
+
attributes.push(j.jsxAttribute(j.jsxIdentifier(prop.name), j.stringLiteral(styleValue)))
|
|
43
|
+
} else {
|
|
44
|
+
attributes.push(
|
|
45
|
+
j.jsxAttribute(
|
|
46
|
+
j.jsxIdentifier(prop.name),
|
|
47
|
+
j.jsxExpressionContainer(getMappingExpression(j, styleValue) as never),
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return attributes
|
|
54
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type {ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier} from 'jscodeshift'
|
|
2
|
+
|
|
3
|
+
export function getSplitImportSpecifiers(
|
|
4
|
+
specifiers: (ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier)[] = [],
|
|
5
|
+
componentName: string,
|
|
6
|
+
) {
|
|
7
|
+
const componentNames = new Set([componentName, `${componentName}Props`])
|
|
8
|
+
const specs = {
|
|
9
|
+
componentSpecs: [] as (ImportSpecifier | ImportDefaultSpecifier)[],
|
|
10
|
+
restSpecs: [] as (ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier)[],
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
for (const spec of specifiers) {
|
|
14
|
+
if (spec.type === 'ImportNamespaceSpecifier') {
|
|
15
|
+
specs.restSpecs.push(spec)
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (spec.type === 'ImportSpecifier' || spec.type === 'ImportDefaultSpecifier') {
|
|
20
|
+
let local
|
|
21
|
+
|
|
22
|
+
if (spec.type === 'ImportSpecifier') {
|
|
23
|
+
const loc = spec.local ?? spec.imported
|
|
24
|
+
local = loc.type === 'Identifier' ? loc.name : null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (spec.type === 'ImportDefaultSpecifier') {
|
|
28
|
+
local = spec.local?.type === 'Identifier' ? spec.local.name : null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (local && componentNames.has(local)) {
|
|
32
|
+
specs.componentSpecs.push(spec)
|
|
33
|
+
} else {
|
|
34
|
+
specs.restSpecs.push(spec)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return specs
|
|
40
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type {API, JSXAttribute, JSXSpreadAttribute} from 'jscodeshift'
|
|
2
|
+
|
|
3
|
+
import {getAttribute} from './getAttribute'
|
|
4
|
+
import {getAttributeExpression} from './getAttributeExpression'
|
|
5
|
+
|
|
6
|
+
export type CompositePrimitive = string | number | boolean
|
|
7
|
+
|
|
8
|
+
export function getStaticAttributeExpression(
|
|
9
|
+
j: API['jscodeshift'],
|
|
10
|
+
attrs: (JSXAttribute | JSXSpreadAttribute)[],
|
|
11
|
+
name: string,
|
|
12
|
+
): CompositePrimitive | null {
|
|
13
|
+
const attr = getAttribute(attrs, name)
|
|
14
|
+
|
|
15
|
+
if (!attr) {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const expr = getAttributeExpression(attr, j)
|
|
20
|
+
|
|
21
|
+
if (!expr) {
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
expr.type === 'StringLiteral' ||
|
|
27
|
+
expr.type === 'NumericLiteral' ||
|
|
28
|
+
expr.type === 'BooleanLiteral' ||
|
|
29
|
+
expr.type === 'Literal'
|
|
30
|
+
) {
|
|
31
|
+
return expr.value as string | number | boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {type API} from 'jscodeshift'
|
|
2
|
+
|
|
3
|
+
import {type AnyExpression} from '../types/AnyExpression'
|
|
4
|
+
|
|
5
|
+
export function getStyleExpression(j: API['jscodeshift'], expr: AnyExpression): AnyExpression {
|
|
6
|
+
if (expr.type === 'Literal') {
|
|
7
|
+
if (expr.value === null) {
|
|
8
|
+
return j.literal(null)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (typeof expr.value === 'boolean') {
|
|
12
|
+
return j.booleanLiteral(expr.value)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (typeof expr.value === 'number') {
|
|
16
|
+
return j.numericLiteral(expr.value)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof expr.value === 'string') {
|
|
20
|
+
return j.stringLiteral(expr.value)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (expr.type === 'BooleanLiteral') {
|
|
25
|
+
return j.booleanLiteral(expr.value as boolean)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (expr.type === 'NumericLiteral') {
|
|
29
|
+
return j.numericLiteral(expr.value as number)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (expr.type === 'StringLiteral') {
|
|
33
|
+
return j.stringLiteral(expr.value as string)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return expr
|
|
37
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type {API, Collection} from 'jscodeshift'
|
|
2
|
+
|
|
3
|
+
import {getStyledComponentName} from './getStyledComponentName'
|
|
4
|
+
|
|
5
|
+
export function getStyledComponentAliases(
|
|
6
|
+
j: API['jscodeshift'],
|
|
7
|
+
root: Collection,
|
|
8
|
+
localNames: Iterable<string>,
|
|
9
|
+
): Set<string> {
|
|
10
|
+
const aliases = new Set<string>()
|
|
11
|
+
const names = new Set(localNames)
|
|
12
|
+
|
|
13
|
+
root.find(j.VariableDeclarator).forEach((path) => {
|
|
14
|
+
const {id, init} = path.node
|
|
15
|
+
|
|
16
|
+
if (!init || id.type !== 'Identifier') {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const baseComponent = getStyledComponentName(init)
|
|
21
|
+
|
|
22
|
+
if (baseComponent && names.has(baseComponent)) {
|
|
23
|
+
aliases.add(id.name)
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
return aliases
|
|
28
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function getStyledComponentName(node: unknown): string | null {
|
|
2
|
+
if (!node || typeof node !== 'object' || !('type' in node)) {
|
|
3
|
+
return null
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const current = node as {
|
|
7
|
+
type: string
|
|
8
|
+
callee?: unknown
|
|
9
|
+
tag?: unknown
|
|
10
|
+
object?: unknown
|
|
11
|
+
arguments?: unknown[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (current.type === 'TaggedTemplateExpression') {
|
|
15
|
+
return getStyledComponentName(current.tag)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (current.type === 'CallExpression') {
|
|
19
|
+
const callee = current.callee
|
|
20
|
+
|
|
21
|
+
if (
|
|
22
|
+
callee &&
|
|
23
|
+
typeof callee === 'object' &&
|
|
24
|
+
'type' in callee &&
|
|
25
|
+
callee.type === 'Identifier' &&
|
|
26
|
+
'name' in callee &&
|
|
27
|
+
callee.name === 'styled'
|
|
28
|
+
) {
|
|
29
|
+
const arg = current.arguments?.[0]
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
arg &&
|
|
33
|
+
typeof arg === 'object' &&
|
|
34
|
+
'type' in arg &&
|
|
35
|
+
arg.type === 'Identifier' &&
|
|
36
|
+
'name' in arg
|
|
37
|
+
) {
|
|
38
|
+
return arg.name as string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return getStyledComponentName(callee)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (current.type === 'MemberExpression' && current.object) {
|
|
48
|
+
return getStyledComponentName(current.object)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {existsSync} from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
export function getTransformPath(root: string, version: string, name: string) {
|
|
5
|
+
const jsPath = path.join(root, `dist/transforms/${version}/${name}/${name}.js`)
|
|
6
|
+
const tsPath = path.join(root, `src/transforms/${version}/${name}/${name}.ts`)
|
|
7
|
+
|
|
8
|
+
if (existsSync(jsPath)) {
|
|
9
|
+
return jsPath
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (existsSync(tsPath)) {
|
|
13
|
+
return tsPath
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
throw new Error(`Could not find ${name} codemod`)
|
|
17
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type API,
|
|
3
|
+
type ASTPath,
|
|
4
|
+
type ImportDeclaration,
|
|
5
|
+
type JSXOpeningElement,
|
|
6
|
+
type VariableDeclarator,
|
|
7
|
+
} from 'jscodeshift'
|
|
8
|
+
|
|
9
|
+
type CommentableNode = {
|
|
10
|
+
comments?: {type: string; value: string; leading?: boolean}[] | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Inserts a `UI-CODEMOD TODO` comment.
|
|
15
|
+
* Returns whether the AST was updated.
|
|
16
|
+
*/
|
|
17
|
+
export function insertTodoWarning(
|
|
18
|
+
j: API['jscodeshift'],
|
|
19
|
+
path: ASTPath<JSXOpeningElement | ImportDeclaration | VariableDeclarator>,
|
|
20
|
+
warning: string,
|
|
21
|
+
): boolean {
|
|
22
|
+
let target: CommentableNode | null = null
|
|
23
|
+
|
|
24
|
+
if (path.node.type === 'ImportDeclaration') {
|
|
25
|
+
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
|
+
} else if (path.parent?.node.type === 'JSXElement') {
|
|
33
|
+
target = path.parent.node
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!target) {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
target.comments ??= []
|
|
41
|
+
|
|
42
|
+
if (target.comments.some((comment) => comment.value.includes(warning))) {
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
target.comments.unshift(j.commentLine(` UI-CODEMOD TODO: ${warning}`, true))
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type {API, ASTPath, ImportSpecifier} from 'jscodeshift'
|
|
2
|
+
|
|
3
|
+
export function isImportBindingUsed(
|
|
4
|
+
j: API['jscodeshift'],
|
|
5
|
+
root: ReturnType<API['jscodeshift']>,
|
|
6
|
+
name: string,
|
|
7
|
+
specifierPath: ASTPath<ImportSpecifier>,
|
|
8
|
+
): boolean {
|
|
9
|
+
const jsxOpening = root.find(j.JSXOpeningElement).some(({node}) => {
|
|
10
|
+
return node.name.type === 'JSXIdentifier' && node.name.name === name
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
if (jsxOpening) {
|
|
14
|
+
return true
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const jsxClosing = root.find(j.JSXClosingElement).some(({node}) => {
|
|
18
|
+
return node.name.type === 'JSXIdentifier' && node.name.name === name
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
if (jsxClosing) {
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const importedPath = specifierPath.get('imported')
|
|
26
|
+
const localPath = specifierPath.get('local')
|
|
27
|
+
|
|
28
|
+
return root.find(j.Identifier).some((idPath) => {
|
|
29
|
+
if (idPath.node.name !== name) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (idPath === importedPath || idPath === localPath) {
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const parent = idPath.parent?.node
|
|
38
|
+
|
|
39
|
+
if (!parent) {
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (parent.type === 'MemberExpression' && !parent.computed && parent.property === idPath.node) {
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (parent.type === 'TSQualifiedName' && parent.right === idPath.node) {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return true
|
|
52
|
+
})
|
|
53
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {type AnyExpression} from '../types/AnyExpression'
|
|
2
|
+
|
|
3
|
+
export function isValidStyleType(expr: AnyExpression) {
|
|
4
|
+
if (expr.type === 'TemplateLiteral') {
|
|
5
|
+
const expressions = expr.expressions as unknown[]
|
|
6
|
+
const quasis = expr.quasis as {value: {cooked: string | null}}[]
|
|
7
|
+
return expressions.length === 0 && quasis.length === 1
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (
|
|
11
|
+
expr.type === 'StringLiteral' ||
|
|
12
|
+
expr.type === 'Literal' ||
|
|
13
|
+
expr.type === 'NumericLiteral' ||
|
|
14
|
+
expr.type === 'BooleanLiteral' ||
|
|
15
|
+
expr.type === 'NullLiteral'
|
|
16
|
+
) {
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {type API, type JSXAttribute, type JSXSpreadAttribute} from 'jscodeshift'
|
|
2
|
+
|
|
3
|
+
import {type AnyExpression} from '../types/AnyExpression'
|
|
4
|
+
|
|
5
|
+
export function mergeStyle(
|
|
6
|
+
j: API['jscodeshift'],
|
|
7
|
+
attrs: (JSXAttribute | JSXSpreadAttribute)[],
|
|
8
|
+
styleKey: string,
|
|
9
|
+
styleExpression: AnyExpression,
|
|
10
|
+
) {
|
|
11
|
+
const styleProp = j.objectProperty(j.identifier(styleKey), styleExpression as never)
|
|
12
|
+
|
|
13
|
+
const styleIndex = attrs.findIndex(
|
|
14
|
+
(a): a is JSXAttribute =>
|
|
15
|
+
a.type === 'JSXAttribute' && a.name.type === 'JSXIdentifier' && a.name.name === 'style',
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
if (styleIndex === -1) {
|
|
19
|
+
attrs.push(
|
|
20
|
+
j.jsxAttribute(
|
|
21
|
+
j.jsxIdentifier('style'),
|
|
22
|
+
j.jsxExpressionContainer(j.objectExpression([styleProp])),
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
return true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const styleAttr = attrs[styleIndex] as JSXAttribute
|
|
30
|
+
|
|
31
|
+
if (styleAttr.value?.type !== 'JSXExpressionContainer') {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const expr = styleAttr.value.expression
|
|
36
|
+
|
|
37
|
+
if (expr.type === 'ObjectExpression') {
|
|
38
|
+
expr.properties = expr.properties.filter((prop) => {
|
|
39
|
+
if (prop.type !== 'ObjectProperty' && prop.type !== 'Property') {
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const key = prop.key
|
|
44
|
+
|
|
45
|
+
const name =
|
|
46
|
+
key.type === 'Identifier'
|
|
47
|
+
? key.name
|
|
48
|
+
: key.type === 'StringLiteral' || key.type === 'Literal'
|
|
49
|
+
? String(key.value)
|
|
50
|
+
: null
|
|
51
|
+
|
|
52
|
+
return name !== styleKey
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
expr.properties.push(styleProp)
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
styleAttr.value = j.jsxExpressionContainer(
|
|
60
|
+
j.objectExpression([j.spreadElement(expr as never), styleProp]),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {type API, type FileInfo} from 'jscodeshift'
|
|
2
|
+
|
|
3
|
+
import {removeUnusedImport} from './removeUnusedImport'
|
|
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
|
+
removeUnusedImport(j, root, 'Card')
|
|
11
|
+
return root.toSource()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
defineInlineTest(
|
|
15
|
+
transform,
|
|
16
|
+
{},
|
|
17
|
+
`
|
|
18
|
+
import {Card} from '@sanity/ui'
|
|
19
|
+
|
|
20
|
+
<Card density="regular" />
|
|
21
|
+
`,
|
|
22
|
+
`
|
|
23
|
+
import {Card} from '@sanity/ui'
|
|
24
|
+
|
|
25
|
+
<Card density="regular" />
|
|
26
|
+
`,
|
|
27
|
+
'preserves import if element is in jsx',
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
defineInlineTest(
|
|
31
|
+
transform,
|
|
32
|
+
{},
|
|
33
|
+
`
|
|
34
|
+
import {Card} from '@sanity/ui'
|
|
35
|
+
import {Box} from '@sanity/ui'
|
|
36
|
+
|
|
37
|
+
<Box />
|
|
38
|
+
`,
|
|
39
|
+
`
|
|
40
|
+
import {Box} from '@sanity/ui'
|
|
41
|
+
|
|
42
|
+
<Box />
|
|
43
|
+
`,
|
|
44
|
+
'removes import if element is not in jsx',
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
defineInlineTest(
|
|
48
|
+
transform,
|
|
49
|
+
{},
|
|
50
|
+
`
|
|
51
|
+
import {Box, Card} from '@sanity/ui'
|
|
52
|
+
|
|
53
|
+
<Box />
|
|
54
|
+
`,
|
|
55
|
+
`
|
|
56
|
+
import { Box } from '@sanity/ui';
|
|
57
|
+
|
|
58
|
+
<Box />
|
|
59
|
+
`,
|
|
60
|
+
'updates import with multiple specifiers if element is not in jsx',
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
defineInlineTest(
|
|
64
|
+
transform,
|
|
65
|
+
{},
|
|
66
|
+
`
|
|
67
|
+
import {Card} from '@sanity/ui'
|
|
68
|
+
|
|
69
|
+
const RootCard = styled(Card)(({theme}) => {})
|
|
70
|
+
`,
|
|
71
|
+
`
|
|
72
|
+
import {Card} from '@sanity/ui'
|
|
73
|
+
|
|
74
|
+
const RootCard = styled(Card)(({theme}) => {})
|
|
75
|
+
`,
|
|
76
|
+
'preserves import when component is used with styled()',
|
|
77
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type {API, ASTPath, ImportSpecifier} from 'jscodeshift'
|
|
2
|
+
|
|
3
|
+
import {isImportBindingUsed} from './isImportBindingUsed'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Removes unused import specifier or the whole import if no specifiers remain.
|
|
7
|
+
* Returns whether the AST was updated.
|
|
8
|
+
*/
|
|
9
|
+
export function removeUnusedImport(
|
|
10
|
+
j: API['jscodeshift'],
|
|
11
|
+
root: ReturnType<API['jscodeshift']>,
|
|
12
|
+
name: string,
|
|
13
|
+
): boolean {
|
|
14
|
+
let hasChanges = false
|
|
15
|
+
|
|
16
|
+
root.find(j.ImportDeclaration).forEach((path) => {
|
|
17
|
+
const specs = path.node.specifiers
|
|
18
|
+
|
|
19
|
+
if (!specs?.length) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const next = specs.filter((spec, i) => {
|
|
24
|
+
if (spec.type !== 'ImportSpecifier' || spec.imported.type !== 'Identifier') {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (spec.imported.name !== name) {
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if ('importKind' in spec && spec.importKind === 'type') {
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const localName = spec.local?.type === 'Identifier' ? spec.local.name : spec.imported.name
|
|
37
|
+
const specifierPath = path.get('specifiers', i) as ASTPath<ImportSpecifier>
|
|
38
|
+
|
|
39
|
+
return isImportBindingUsed(j, root, localName, specifierPath)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
if (next.length === specs.length) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (next.length === 0) {
|
|
47
|
+
j(path).remove()
|
|
48
|
+
hasChanges = true
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
path.node.specifiers = next
|
|
53
|
+
hasChanges = true
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return hasChanges
|
|
57
|
+
}
|