1+ import { Node } from 'ts-morph'
2+
3+ import { addOrUpdateSourcegraphWildcardImportIfNeeded } from '@sourcegraph/codemod-toolkit-packages'
14import {
25 runTransform ,
36 getParentUntilOrThrow ,
@@ -9,7 +12,7 @@ import {
912 * Convert `<Icon as={MdiIcon} />` element to `<Icon svgPath={mdiIconPath} />` component.
1013 */
1114export const mdiIconToMdiPath = runTransform ( context => {
12- const { throwManualChangeError } = context
15+ const { throwManualChangeError, addManualChangeLog } = context
1316
1417 const mdiIconPathsToImport = new Set < string > ( )
1518
@@ -19,6 +22,76 @@ export const mdiIconToMdiPath = runTransform(context => {
1922 }
2023
2124 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+ */
2295 JsxAttribute ( jsxAttribute ) {
2396 const jsxTagElement = getParentUntilOrThrow ( jsxAttribute , isJsxTagElement )
2497 if ( jsxTagElement . getTagNameNode ( ) . getText ( ) !== 'Icon' ) {
@@ -75,9 +148,19 @@ export const mdiIconToMdiPath = runTransform(context => {
75148 namedImports : [ ...mdiIconPathsToImport ] ,
76149 moduleSpecifier : '@mdi/js' ,
77150 } )
78- }
79151
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+ }
81164 } ,
82165 }
83166} )
0 commit comments