Skip to content

Commit 32f8b8d

Browse files
authored
feat: add icon codemods (#6958)
1 parent 82e10d3 commit 32f8b8d

File tree

4 files changed

+1301
-0
lines changed

4 files changed

+1301
-0
lines changed

packages/dev/codemods/src/s1-to-s2/__tests__/__snapshots__/icon.test.ts.snap

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,50 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`Does not affect existing S2 icons 1`] = `
4+
"import Add from '@react-spectrum/s2/icons/Add';
5+
6+
<Add />;"
7+
`;
8+
9+
exports[`Leave comment if no matching S2 icon found 1`] = `
10+
"import AssetCheck from '@spectrum-icons/workflow/AssetCheck';
11+
12+
// TODO(S2-upgrade): A Spectrum 2 equivalent to 'AssetCheck' was not found. Please update this icon manually.
13+
<AssetCheck />;"
14+
`;
15+
16+
exports[`Migrate S1 icon with different name to S2 1`] = `
17+
"import AlertTriangle from "@react-spectrum/s2/icons/AlertTriangle";
18+
19+
<AlertTriangle />;"
20+
`;
21+
22+
exports[`Migrate S1 icon with different name to S2. Keep name if already taken in scope. 1`] = `
23+
"import Alert from "@react-spectrum/s2/icons/AlertTriangle";
24+
import AlertTriangle from 'elsewhere';
25+
26+
<div>
27+
<Alert />
28+
<AlertTriangle />
29+
</div>"
30+
`;
31+
32+
exports[`Migrate S1 icon with same name to S2 1`] = `
33+
"import Add from "@react-spectrum/s2/icons/Add";
34+
35+
<Add />;"
36+
`;
37+
38+
exports[`Migrate custom-named S1 icon to S2. Keep name as custom name. 1`] = `
39+
"import AlertIcon from "@react-spectrum/s2/icons/AlertTriangle";
40+
import Alert from 'elsewhere';
41+
42+
<div>
43+
<AlertIcon />
44+
<Alert />
45+
</div>"
46+
`;
47+
348
exports[`Remove Icon and tells people to use compiler? import as svg? 1`] = `
449
"import {Icon} from '@adobe/react-spectrum';
550

packages/dev/codemods/src/s1-to-s2/__tests__/icon.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,47 @@ function CustomIcon(props) {
1919
);
2020
}
2121
`);
22+
23+
test('Migrate S1 icon with same name to S2', `
24+
import Add from '@spectrum-icons/workflow/Add';
25+
26+
<Add />;
27+
`);
28+
29+
test('Migrate S1 icon with different name to S2', `
30+
import Alert from '@spectrum-icons/workflow/Alert';
31+
32+
<Alert />;
33+
`);
34+
35+
test('Migrate custom-named S1 icon to S2. Keep name as custom name.', `
36+
import AlertIcon from '@spectrum-icons/workflow/Alert';
37+
import Alert from 'elsewhere';
38+
39+
<div>
40+
<AlertIcon />
41+
<Alert />
42+
</div>
43+
`);
44+
45+
test('Migrate S1 icon with different name to S2. Keep name if already taken in scope.', `
46+
import Alert from '@spectrum-icons/workflow/Alert';
47+
import AlertTriangle from 'elsewhere';
48+
49+
<div>
50+
<Alert />
51+
<AlertTriangle />
52+
</div>
53+
`);
54+
55+
test('Leave comment if no matching S2 icon found', `
56+
import AssetCheck from '@spectrum-icons/workflow/AssetCheck';
57+
58+
<AssetCheck />;
59+
`);
60+
61+
test('Does not affect existing S2 icons', `
62+
import Add from '@react-spectrum/s2/icons/Add';
63+
64+
<Add />;
65+
`);

packages/dev/codemods/src/s1-to-s2/src/codemods/codemod.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {API, FileInfo} from 'jscodeshift';
44
import {changes as changesJSON} from './changes';
55
import {functionMap} from './transforms';
66
import {getComponents} from '../getComponents';
7+
import {iconMap} from '../iconMap';
78
import * as t from '@babel/types';
89
import {transformStyleProps} from './styleProps';
910
import traverse, {Binding, NodePath} from '@babel/traverse';
@@ -39,6 +40,7 @@ export default function transformer(file: FileInfo, api: API, options: Options)
3940
let importedComponents = new Map<string, t.ImportSpecifier | t.ImportNamespaceSpecifier>();
4041
let elements: [string, NodePath<t.JSXElement>][] = [];
4142
let lastImportPath: NodePath<t.ImportDeclaration> | null = null;
43+
let iconImports: Map<string, {path: NodePath<t.ImportDeclaration>, newName: string | null}> = new Map();
4244
const leadingComments = root.find(j.Program).get('body', 0).node.leadingComments;
4345
traverse(root.paths()[0].node, {
4446
ImportDeclaration(path) {
@@ -90,6 +92,22 @@ export default function transformer(file: FileInfo, api: API, options: Options)
9092
}
9193
}
9294
}
95+
} else if (path.node.source.value.startsWith('@spectrum-icons/workflow/')) {
96+
let importSource = path.node.source.value;
97+
let iconName = importSource.split('/').pop();
98+
if (!iconName) {return;}
99+
100+
let specifier = path.node.specifiers[0];
101+
if (!specifier || !t.isImportDefaultSpecifier(specifier)) {return;}
102+
103+
let localName = specifier.local.name;
104+
105+
if (iconMap.has(iconName)) {
106+
let newIconName = iconMap.get(iconName)!;
107+
iconImports.set(localName, {path, newName: newIconName});
108+
} else {
109+
iconImports.set(localName, {path, newName: null});
110+
}
93111
}
94112
},
95113
Import(path) {
@@ -109,6 +127,41 @@ export default function transformer(file: FileInfo, api: API, options: Options)
109127

110128
// TODO: implement this. could be a bit challenging. punting for now.
111129
addComment(call.node, ' TODO(S2-upgrade): check this dynamic import');
130+
},
131+
JSXOpeningElement(path) {
132+
let name = path.node.name;
133+
if (t.isJSXIdentifier(name) && iconImports.has(name.name)) {
134+
let iconInfo = iconImports.get(name.name)!;
135+
if (iconInfo.newName === null) {
136+
addComment(path.node, ` TODO(S2-upgrade): A Spectrum 2 equivalent to '${name.name}' was not found. Please update this icon manually.`);
137+
}
138+
}
139+
}
140+
});
141+
142+
iconImports.forEach((iconInfo, localName) => {
143+
let {path, newName} = iconInfo;
144+
if (newName) {
145+
let newImportSource = `@react-spectrum/s2/icons/${newName}`;
146+
147+
// Check if we can update local name
148+
let newLocalName = localName;
149+
if (localName === path.node.source.value.split('/').pop() && localName !== newName) {
150+
let binding = path.scope.getBinding(localName);
151+
if (binding && !path.scope.hasBinding(newName)) {
152+
newLocalName = newName;
153+
// Rename all references
154+
binding.referencePaths.forEach(refPath => {
155+
if (t.isJSXIdentifier(refPath.node)) {
156+
refPath.node.name = newName;
157+
}
158+
});
159+
}
160+
}
161+
162+
// Update the import
163+
path.node.source = t.stringLiteral(newImportSource);
164+
path.node.specifiers = [t.importDefaultSpecifier(t.identifier(newLocalName))];
112165
}
113166
});
114167

0 commit comments

Comments
 (0)