1
+ import { Node } from 'ts-morph'
2
+
3
+ import { addOrUpdateSourcegraphWildcardImportIfNeeded } from '@sourcegraph/codemod-toolkit-packages'
1
4
import {
2
5
runTransform ,
3
6
getParentUntilOrThrow ,
@@ -9,7 +12,7 @@ import {
9
12
* Convert `<Icon as={MdiIcon} />` element to `<Icon svgPath={mdiIconPath} />` component.
10
13
*/
11
14
export const mdiIconToMdiPath = runTransform ( context => {
12
- const { throwManualChangeError } = context
15
+ const { throwManualChangeError, addManualChangeLog } = context
13
16
14
17
const mdiIconPathsToImport = new Set < string > ( )
15
18
@@ -19,6 +22,76 @@ export const mdiIconToMdiPath = runTransform(context => {
19
22
}
20
23
21
24
return {
25
+ /**
26
+ * Handles converting <MdiIcon /> to <Icon svgPath={mdiIcon} />
27
+ */
28
+ JsxSelfClosingElement ( jsxElement ) {
29
+ const tagElementName = jsxElement . getTagNameNode ( ) . getText ( )
30
+ const iconRegex = / ( \w * .) I c o n /
31
+
32
+ if ( ! tagElementName . match ( iconRegex ) || ! isMdiReactToken ( tagElementName ) ) {
33
+ // Not <MdiIcon component, so we exit
34
+ return
35
+ }
36
+
37
+ const updatedValue = `mdi${ tagElementName . replace ( iconRegex , '$1' ) } `
38
+
39
+ // e.g. update <CloseIcon /> to <Icon /> (we handle correct import later)
40
+ jsxElement . set ( {
41
+ name : 'Icon' ,
42
+ } )
43
+
44
+ // Add updated svgPath attribute
45
+ jsxElement . addAttribute ( {
46
+ name : 'svgPath' ,
47
+ initializer : `{${ updatedValue } }` ,
48
+ } )
49
+
50
+ // Ensure `inline` is set to false to guarantee that we aren't introducing any new CSS with this change.
51
+ jsxElement . addAttribute ( {
52
+ name : 'inline' ,
53
+ initializer : '{false}' ,
54
+ } )
55
+
56
+ // We need to set accessibility attributes on all icons
57
+ // If these aren't already set, we default to `aria-hidden={true}` and leave a message to review.
58
+ if ( ! jsxElement . getAttribute ( 'aria-label' ) && ! jsxElement . getAttribute ( 'aria-hidden' ) ) {
59
+ jsxElement . addAttribute ( {
60
+ name : 'aria-hidden' ,
61
+ initializer : '{true}' ,
62
+ } )
63
+
64
+ addManualChangeLog ( {
65
+ node : jsxElement ,
66
+ message :
67
+ '<MdiIcon /> component did not have accessibility attributes and has been hidden from screen readers automatically. Please review manually' ,
68
+ } )
69
+ }
70
+
71
+ // Our previous icon library supported a `size` prop, which set height and width.
72
+ // We convert this to height and width to be explicit.
73
+ const sizeAttribute = jsxElement . getAttribute ( 'size' )
74
+ if ( sizeAttribute && Node . isJsxAttribute ( sizeAttribute ) ) {
75
+ jsxElement . addAttribute ( {
76
+ name : 'height' ,
77
+ initializer : sizeAttribute . getInitializer ( ) ?. getText ( ) ,
78
+ } )
79
+
80
+ jsxElement . addAttribute ( {
81
+ name : 'width' ,
82
+ initializer : sizeAttribute . getInitializer ( ) ?. getText ( ) ,
83
+ } )
84
+
85
+ // Remove the old attribute
86
+ jsxElement . getAttribute ( 'size' ) ?. remove ( )
87
+ }
88
+
89
+ // Store this value so we can import it once finished with this file.
90
+ mdiIconPathsToImport . add ( updatedValue )
91
+ } ,
92
+ /**
93
+ * Handles converting <Icon as={MdiIcon} /> to <Icon svgPath={mdiIcon} />
94
+ */
22
95
JsxAttribute ( jsxAttribute ) {
23
96
const jsxTagElement = getParentUntilOrThrow ( jsxAttribute , isJsxTagElement )
24
97
if ( jsxTagElement . getTagNameNode ( ) . getText ( ) !== 'Icon' ) {
@@ -75,9 +148,19 @@ export const mdiIconToMdiPath = runTransform(context => {
75
148
namedImports : [ ...mdiIconPathsToImport ] ,
76
149
moduleSpecifier : '@mdi/js' ,
77
150
} )
78
- }
79
151
80
- sourceFile . fixUnusedIdentifiers ( )
152
+ // If we're using the <Icon /> component for the first time,
153
+ // we need to add the import
154
+ addOrUpdateSourcegraphWildcardImportIfNeeded ( {
155
+ sourceFile,
156
+ importStructure : {
157
+ namedImports : [ 'Icon' ] ,
158
+ } ,
159
+ } )
160
+
161
+ // Clean up
162
+ sourceFile . fixUnusedIdentifiers ( )
163
+ }
81
164
} ,
82
165
}
83
166
} )
0 commit comments